是的,在所有适用的情况下使用instancetype 都有好处。我将更详细地解释,但让我从这个大胆的声明开始:在适当的时候使用instancetype,即每当一个类返回同一个类的实例时。
事实上,苹果现在在这个问题上是这样说的:
在您的代码中,将出现的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,它从 UILayoutSupport 返回一个 CGFloat,但是来自NSString 的NSUInteger。)
注意:由于我写了这篇文章,macOS 标头已被修改为返回 NSFileHandle 而不是 id。
对于初始化程序,它更复杂。当您输入以下内容时:
- (id)initWithBar:(NSInteger)bar
…编译器会假装你输入了这个:
- (instancetype)initWithBar:(NSInteger)bar
这对于 ARC 来说是必要的。这在 Clang 语言扩展 Related result types 中有描述。这就是为什么人们会告诉你没有必要使用instancetype,尽管我认为你应该这样做。这个答案的其余部分涉及这个。
有三个优点:
-
显式。您的代码正在做它所说的,而不是其他的。
-
模式。您正在为确实存在的重要时期养成良好的习惯。
-
一致性。您已经为代码建立了某种一致性,这使其更具可读性。
显式
确实,从init 返回instancetype 没有技术 好处。但这是因为编译器会自动将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。