开始使用instancetype而不是id是否有益?

Clang添加了一个关键字instancetype ,就我所见, -alloc idreplace为-allocinit的返回types。

使用instancetype而不是id有什么好处?

肯定有一个好处。 当你使用'id'时,你基本上不会进行types检查。 使用instancetype,编译器和IDE知道返回什么types的东西,并且可以更好地检查你的代码并自动完成。

只有在有意义的地方使用它(即返回该类的一个实例的方法); ID仍然有用。

是的,在所有情况下使用instancetype都有好处。 我会更详细地解释一下,但让我从这个大胆的说法开始:只要适当的时候使用instancetype ,每当一个类返回同一个类的实例时。

事实上,苹果公司现在就这个问题发表了看法:

在您的代码中,在适当的instancetype下,将id出现次数replace为instancetype的返回值。 init方法和类工厂方法通常是这种情况。 即使编译器自动转换以“alloc”,“init”或“new”开始的方法,并且返回idtypes以返回instancetypetypes,但不会转换其他方法。 Objective-C约定是为所有方法显式编写instancetype

  • 强调我的。 来源: 采用现代Objective-C

这样做,让我们继续前进,并解释为什么这是一个好主意。

首先是一些定义:

  @interface Foo:NSObject - (id)initWithBar:(NSInteger)bar; // initializer + (id)fooWithBar:(NSInteger)bar; // class factory @end 

对于一个类工厂,你应该总是使用instancetype 。 编译器不会自动将id转换为instancetype 。 该id是一个通用的对象。 但是如果你把它作为一个instancetypetypes,那么编译器知道该方法返回什么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 ,虽然我认为你应该。 这个答案的其余部分处理这个。

有三个好处:

  1. 明确。 你的代码正在做它所说的,而不是别的。
  2. 模式。 你在build立良好习惯的时候,它确实存在。
  3. 一致性。 你已经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或对象参数时不要包含*。