Clang添加了一个关键字instancetype
,据我所知,该关键字将id
替换为-alloc
和init
中的返回类型。
使用instancetype
代替id
是否有好处?
答案 0 :(得分:330)
是的,在适用的所有情况下使用instancetype
都有好处。我将更详细地解释,但让我从这个大胆的陈述开始:只要合适,只要一个类返回同一个类的实例,就使用instancetype
。
事实上,这就是Apple现在就此问题所说的话:
在您的代码中,将
id
的匹配项替换为instancetype
的返回值。这通常是init
方法和类工厂方法的情况。即使编译器自动转换以“alloc”,“init”或“new”开头且返回类型为id
的方法返回instancetype
,它也不会转换其他方法。 Objective-C约定是为所有方法明确编写instancetype
。
有了这个,让我们继续前进并解释为什么这是一个好主意。
首先,一些定义:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
对于班级工厂,您应始终使用instancetype
。编译器不会自动将id
转换为instancetype
。 id
是一个通用对象。但是如果你使它成为instancetype
,编译器就会知道该方法返回的对象类型。
这是不一个学术问题。例如,[[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]
将在Mac OS X上生成错误(仅)找到名为'writeData:'的多个方法,其中包含不匹配的结果,参数类型或属性。原因是NSFileHandle和NSURLHandle都提供writeData:
。由于[NSFileHandle fileHandleWithStandardOutput]
返回id
,因此编译器无法确定正在调用哪个类writeData:
。
您需要使用以下任一方法解决此问题:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
或:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
当然,更好的解决方案是将fileHandleWithStandardOutput
声明为返回instancetype
。然后不需要演员表或作业。
(请注意,在iOS上,此示例不会产生错误,因为只有NSFileHandle
在那里提供writeData:
。存在其他示例,例如length
,它返回CGFloat
1}}来自UILayoutSupport
,NSUInteger
来自NSString
。)
注意 :由于我写了这篇文章,因此修改了macOS标头以返回NSFileHandle
而不是id
。
对于初始化程序,它更复杂。当你输入这个:
- (id)initWithBar:(NSInteger)bar
...编译器会伪装你输入:
- (instancetype)initWithBar:(NSInteger)bar
这对ARC来说是必要的。这在Clang语言扩展Related result types中有所描述。这就是为什么人们会告诉你没有必要使用instancetype
,尽管我认为你应该这样做。本回答的其余部分涉及此问题。
有三个好处:
从instancetype
返回init
确实没有技术好处。但这是因为编译器会自动将id
转换为instancetype
。你依靠这个怪癖;当您写init
返回id
时,编译器会将其解释为返回instancetype
。
这些与编译器等效:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
这些并不等同于你的眼睛。充其量,你将学会忽略差异并略过它。 这不是你应该学会忽视的事情。
虽然与init
和其他方法没有区别,但只要您定义了一个类工厂, 就会有差异。
这两个不等同:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
你想要第二种形式。如果您习惯于键入instancetype
作为构造函数的返回类型,那么每次都可以正确使用它。
最后,想象一下如果你把它们放在一起:你想要一个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
。
答案 1 :(得分:190)
肯定有一个好处。当你使用'id'时,你基本上没有任何类型检查。使用instancetype,编译器和IDE知道返回的是什么类型的东西,并且可以更好地检查代码并更好地自动完成。
仅在理所当然的地方使用它(即返回该类实例的方法); id仍然有用。
答案 2 :(得分:10)
以上答案足以解释这个问题。我想为读者添加一个例子,以便在编码方面理解它。
<强> 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 i.e. the type of the receiver
}
答案 3 :(得分:1)
您还可以在The Designated Initializer
获取详细信息**
** 此关键字只能用于返回类型,它与接收方的返回类型匹配。始终声明init方法返回instancetype。 例如,为什么不为派对实例制作返回类型的派对? 如果Party类被子类化,那将导致问题。子类将继承Party的所有方法,包括初始化器及其返回类型。如果子类的一个实例发送了这个初始化器消息,那将返回?不是指向Party实例的指针,而是指向子类实例的指针。您可能认为没问题,我将覆盖子类中的初始化程序以更改返回类型。但是在Objective-C中,你不能有两个具有相同选择器和不同返回类型(或参数)的方法。通过指定初始化方法返回&#34;接收对象的实例,&#34;你永远不必担心在这种情况下会发生什么。 **
** 在Objective-C中引入instancetype之前,初始化程序返回id(eye-dee)。此类型定义为&#34;指向任何对象的指针&#34;。 (id与C中的void *非常相似。)在撰写本文时,XCode类模板仍然使用id作为样板代码中添加的初始化器的返回类型。 与instancetype不同,id不仅可以用作返回类型。当您不确定变量最终指向哪种类型的对象时,您可以声明类型为id的变量或方法参数。 使用快速枚举迭代多个或未知类型的对象数组时,可以使用id。请注意,因为id未定义为&#34;指向任何对象的指针,&#34;在声明此类型的变量或对象参数时,不包含*。
答案 4 :(得分:0)
特殊类型 instancetype
表示 init
方法的返回类型将与它正在初始化的对象类型(即 init
信息)。这是对编译器的帮助,以便它可以检查您的程序并标记潜力
类型不匹配——它根据上下文确定返回对象的类;也就是说,如果您将 init
消息发送到新 alloc
的 Fraction
对象,编译器将推断该 init
方法返回的值(其返回类型已声明为类型 instancetype) 将是一个 Fraction
对象。过去,初始化方法的返回类型被声明为类型 id
。当您考虑子类化时,这种新类型更有意义,因为继承的初始化方法无法明确定义它们将返回的对象类型。
初始化对象,Stephen G. Kochan,Objective-C 编程,第 6 版