开始使用instancetype而不是id是否有益?
Clang添加了一个关键字instancetype
,就我所见, -alloc
id
replace为-alloc
和init
的返回types。
使用instancetype
而不是id
有什么好处?
肯定有一个好处。 当你使用'id'时,你基本上不会进行types检查。 使用instancetype,编译器和IDE知道返回什么types的东西,并且可以更好地检查你的代码并自动完成。
只有在有意义的地方使用它(即返回该类的一个实例的方法); ID仍然有用。
是的,在所有情况下使用instancetype
都有好处。 我会更详细地解释一下,但让我从这个大胆的说法开始:只要适当的时候使用instancetype
,每当一个类返回同一个类的实例时。
事实上,苹果公司现在就这个问题发表了看法:
在您的代码中,在适当的
instancetype
下,将id
出现次数replace为instancetype
的返回值。init
方法和类工厂方法通常是这种情况。 即使编译器自动转换以“alloc”,“init”或“new”开始的方法,并且返回id
types以返回instancetype
types,但不会转换其他方法。 Objective-C约定是为所有方法显式编写instancetype
。
- 强调我的。 来源: 采用现代Objective-C
这样做,让我们继续前进,并解释为什么这是一个好主意。
首先是一些定义:
@interface Foo:NSObject - (id)initWithBar:(NSInteger)bar; // initializer + (id)fooWithBar:(NSInteger)bar; // class factory @end
对于一个类工厂,你应该总是使用instancetype
。 编译器不会自动将id
转换为instancetype
。 该id
是一个通用的对象。 但是如果你把它作为一个instancetype
types,那么编译器知道该方法返回什么types的对象。
这不是一个学术问题。 例如, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]
将在Mac OS X( 仅 )上生成一个错误。 find多个名为'writeData:'的方法,其结果,参数types或属性不匹配 。 原因是NSFileHandle和NSURLHandle都提供了writeData:
:。 由于[NSFileHandle fileHandleWithStandardOutput]
返回一个id
,因此编译器不确定正在调用哪个类writeData:
[NSFileHandle fileHandleWithStandardOutput]
你需要解决这个问题,使用:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
要么:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput]; [fileHandle writeData:formattedData];
当然,更好的解决scheme是将fileHandleWithStandardOutput
声明为返回instancetype
。 那么演员或任务是没有必要的。
(请注意,在iOS上,这个例子不会产生错误,因为只有NSFileHandle
提供了writeData:
there。还有其他的例子,比如length
,它从UILayoutSupport
返回一个CGFloat
,而从NSString
返回一个CGFloat
。)
对于初始化,它更复杂。 当你input这个:
- (id)initWithBar:(NSInteger)bar
…编译器会假装你input这个:
- (instancetype)initWithBar:(NSInteger)bar
这是ARC所必需的。 这在Clang语言扩展相关结果types中进行了描述 。 这就是为什么人们会告诉你没有必要使用instancetype
,虽然我认为你应该。 这个答案的其余部分处理这个。
有三个好处:
- 明确。 你的代码正在做它所说的,而不是别的。
- 模式。 你在build立良好习惯的时候,它确实存在。
- 一致性。 你已经build立了一些你的代码的一致性,这使得它更具可读性。
明确的
确实,从init
返回instancetype
没有技术上的好处。 但是这是因为编译器自动将id
转换为instancetype
。 你依靠这个怪癖, 当你写这个init
返回一个id
,编译器正在解释它,就像它返回一个instancetype
。
这些相当于编译器:
- (id)initWithBar:(NSInteger)bar; - (instancetype)initWithBar:(NSInteger)bar;
这些不等于你的眼睛。 充其量,你会学会忽略它的差异。 这不是你应该学会忽略的。
模式
尽pipeinit
和其他方法没有什么区别,但是一旦定义了一个类工厂,就会有所不同。
这两个不相同:
+ (id)fooWithBar:(NSInteger)bar; + (instancetype)fooWithBar:(NSInteger)bar;
你想要第二个表格。 如果您习惯于将instancetype
作为构造函数的返回types进行input,那么您每次都会正确地使用它。
一致性
最后想象一下,如果你把它放在一起:你需要一个init
函数,也是一个类工厂。
如果你使用id
作为init
,你最终得到这样的代码:
- (id)initWithBar:(NSInteger)bar; + (instancetype)fooWithBar:(NSInteger)bar;
但是,如果你使用instancetype
,你会得到这个:
- (instancetype)initWithBar:(NSInteger)bar; + (instancetype)fooWithBar:(NSInteger)bar;
它更一致,更可读。 他们返回相同的东西,现在很明显。
结论
除非有意为旧的编译器编写代码,否则应该在适当的时候使用instancetype
。
在写一个返回id
的消息之前,你应该犹豫。 问问自己:这是否返回这个类的一个实例? 如果是这样,这是一个instancetype
。
当然有些情况下你需要返回id
,但是你可能会更频繁地使用instancetype
。
以上答案足以解释这个问题。 我只想给读者增加一个例子,让他们从编码的angular度来理解它。
ClassA的
@interface ClassA : NSObject - (id)methodA; - (instancetype)methodB; @end
B类
@interface ClassB : NSObject - (id)methodX; @end
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 ie the type of the receiver }
您也可以在指定的初始化程序中获得详细信息
**
INSTANCETYPE
**该关键字只能用于返回types,与返回types相匹配。 init方法总是声明为返回instancetype。 为什么不举办派对实例返回types的党,例如? 如果党的阶级被分类,那就会产生问题。 子类将inheritanceParty的所有方法,包括初始化方法和返回types。 如果一个子类的实例被发送了这个初始化信息,那将会返回吗? 不是指向Party实例的指针,而是指向子类实例的指针。 你可能会认为这没有问题,我会重写子类中的初始化器来改变返回types。 但在Objective-C中,不能有两个具有相同select器和不同返回types(或参数)的方法。 通过指定初始化方法返回“接收对象的一个实例”,您将不必担心在这种情况下会发生什么。 **
ID
**在Objective-C中引入实例types之前,初始值设定项返回id(eye-dee)。 这种types被定义为“指向任何对象的指针”。 (id和C中的void *非常类似)在本文中,XCode类模板仍然使用id作为样板代码中添加的初始化程序的返回types。 与instancetype不同的是,id可以不仅仅是一个返回types。 如果不确定variables最终指向什么types的对象,则可以声明types为id的variables或方法参数。 使用快速枚举来遍历多个或未知types的对象时,可以使用id。 请注意,因为id未定义为“指向任何对象的指针”,所以在声明此types的variables或对象参数时不要包含*。