gri*_*eak 225 objective-c instancetype
铛添加关键字instancetype
的是,据我所看到的,替换id
为返回类型-alloc
和init
.
使用instancetype
而不是id
?
Ste*_*her 334
是的,instancetype
在适用的所有情况下使用都有好处.我将更详细地解释,但让我从这个大胆的声明开始:instancetype
在适当的时候使用,这是每当一个类返回同一个类的实例时.
事实上,这就是Apple现在就此问题所说的话:
在您的代码中,将适当的
id
值替换为返回值instancetype
.这通常是init
方法和类工厂方法的情况.即使编译器自动转换以"alloc","init"或"new"开头且返回类型id
为return的方法instancetype
,它也不会转换其他方法.Objective-C约定是instancetype
为所有方法明确写入.
有了这个,让我们继续前进并解释为什么这是一个好主意.
首先,一些定义:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
Run Code Online (Sandbox Code Playgroud)
对于一个类工厂,你应该总是使用instancetype
.编译器不会自动转换id
为instancetype
.这id
是一个通用的对象.但是如果你使它成为一个instancetype
编译器知道该方法返回什么类型的对象.
这不是学术问题.例如,[[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]
将在Mac OS X上生成错误(仅)使用不匹配的结果,参数类型或属性找到名为"writeData:"的多个方法.原因是NSFileHandle和NSURLHandle都提供了一个writeData:
.由于[NSFileHandle fileHandleWithStandardOutput]
返回a id
,编译器不确定writeData:
调用哪个类.
您需要使用以下任一方法解决此问题:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
Run Code Online (Sandbox Code Playgroud)
要么:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Run Code Online (Sandbox Code Playgroud)
当然,更好的解决方案是声明fileHandleWithStandardOutput
为返回instancetype
.然后没有必要进行演员表或作业.
(请注意,在iOS上,此示例不会产生错误,因为只NSFileHandle
提供了一个错误writeData:
.存在其他示例,例如length
,返回CGFloat
from UILayoutSupport
但是NSUInteger
from NSString
.)
注意:自从我写这篇文章以来,macOS头文件已被修改为返回NSFileHandle
而不是id
.
对于初始化程序,它更复杂.当你输入这个:
- (id)initWithBar:(NSInteger)bar
Run Code Online (Sandbox Code Playgroud)
...编译器会伪装你输入:
- (instancetype)initWithBar:(NSInteger)bar
Run Code Online (Sandbox Code Playgroud)
这对ARC来说是必要的.这在Clang语言扩展相关结果类型中描述.这就是为什么人们会告诉你没有必要使用instancetype
,尽管我认为你应该这样做.这个答案的其余部分涉及到这一点.
有三个好处:
确实,从一个人那里回来并没有技术上的好处.但这是因为编译器自动将转换为.你依靠这个怪癖; 当你写的是返回a时,编译器会将其解释为它返回一个.instancetype
init
id
instancetype
init
id
instancetype
这些等同于编译器:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
Run Code Online (Sandbox Code Playgroud)
这些并不等同于你的眼睛.充其量,你将学会忽略差异并略过它.这不是你应该学会忽视的东西.
虽然有没有区别init
等方法,也就是只要你定义一个类工厂的差异.
这两个不等同:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Run Code Online (Sandbox Code Playgroud)
你想要第二种形式.如果您习惯于键入instancetype
作为构造函数的返回类型,那么每次都会正确.
最后,想象一下,如果你把它们放在一起:你想要一个init
功能和一个类工厂.
如果你使用id
了init
,你结束了这样的代码:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Run Code Online (Sandbox Code Playgroud)
但如果你使用instancetype
,你得到这个:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Run Code Online (Sandbox Code Playgroud)
它更一致,更易读.他们返回相同的东西,现在这很明显.
除非您有意为旧编译器编写代码,否则应instancetype
在适当时使用.
在写一条返回的消息之前,你应该犹豫不决id
.问问自己:这是否会返回此类的实例?如果是这样,那就是了instancetype
.
肯定存在需要返回的情况id
,但您可能instancetype
会更频繁地使用.
Cat*_*Man 191
肯定有一个好处.当你使用'id'时,你基本上没有任何类型检查.使用instancetype,编译器和IDE知道返回的是什么类型的东西,并且可以更好地检查代码并更好地自动完成.
只在理所当然的地方使用它(即返回该类实例的方法); id仍然有用.
Evo*_*ate 10
以上答案足以解释这个问题.我想为读者添加一个示例,以便在编码方面理解它.
ClassA的
@interface ClassA : NSObject
- (id)methodA;
- (instancetype)methodB;
@end
Run Code Online (Sandbox Code Playgroud)
B级
@interface ClassB : NSObject
- (id)methodX;
@end
Run Code Online (Sandbox Code Playgroud)
TestViewController.m
#import "ClassA.h"
#import "ClassB.h"
- (void)viewDidLoad {
[[[[ClassA alloc] init] methodA] methodX]; //This will NOT generate a compiler warning or error because the return type for methodA is id. Eventually this will generate exception at runtime
[[[[ClassA alloc] init] methodB] methodX]; //This will generate a compiler error saying "No visible @interface ClassA declares selector methodX" because the methodB returns instanceType i.e. the type of the receiver
}
Run Code Online (Sandbox Code Playgroud)