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

gri*_*eak 225 objective-c instancetype

铛添加关键字instancetype的是,据我所看到的,替换id为返回类型-allocinit.

使用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.编译器不会自动转换idinstancetype.这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,返回CGFloatfrom UILayoutSupport但是NSUIntegerfrom 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,尽管我认为你应该这样做.这个答案的其余部分涉及到这一点.

有三个好处:

  1. 明确.你的代码正在做它所说的,而不是别的东西.
  2. 图案.你正在建立良好的习惯,因为它确实存在.
  3. 一致性.您已经为代码建立了一些一致性,这使它更具可读性.

明确的

确实,从一个人那里回来并没有技术上的好处.但这是因为编译器自动将转换为.你依靠这个怪癖; 当你写的是返回a时,编译器会将其解释为它返回一个.instancetypeinitidinstancetypeinitidinstancetype

这些等同于编译器:

- (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功能和一个类工厂.

如果你使用idinit,你结束了这样的代码:

- (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会更频繁地使用.

  • 要明确:我相信Catfish_Man的答案是正确的**只在第一句话**.hooleyhoop的答案是正确的**除了第一句**.这是一个两难的境地; 我想提供一些东西,随着时间的推移,它会比任何一种都更有用,更正确.(尽管对这两者有充分的尊重;毕竟,现在这比回答他们的答案时要明显得多.) (6认同)
  • 我问这个问题已经有一段时间了,我早就采取了像你提出的那样的立场.我让它放在这里,因为我认为,不幸的是,这将被认为是初始风格的问题,早期回复中提供的信息确实很清楚地回答了这个问题. (4认同)
  • `instancetype` vs`id`真的不是一个样式决定.`instancetype`最近的变化确实清楚地表明我们应该在`-init`之类的地方使用`instancetype`,我们的意思是'我的类的一个实例' (4认同)
  • 你说有使用id的原因,你能详细说明吗?我能想到的只有两个是简洁性和向后兼容性; 考虑到两者都是以表达你的意图为代价,我认为这些都是不好的论点. (2认同)

Cat*_*Man 191

肯定有一个好处.当你使用'id'时,你基本上没有任何类型检查.使用instancetype,编译器和IDE知道返回的是什么类型的东西,并且可以更好地检查代码并更好地自动完成.

只在理所当然的地方使用它(即返回该类实例的方法); id仍然有用.

  • 它主要用于方便构造者 (10认同)
  • `alloc`,`init`等由编译器自动提升为`instancetype`.这并不是说没有好处; 有,但不是这个. (8认同)
  • 随着2011年Lion的发布,它被引入(并帮助支持)ARC.在Cocoa中广泛采用的一个原因是必须审核所有候选方法以查看它们是否[self alloc]而不是[NameOfClass如果[SomeSubClass convenienceConstructor],使用+ convenienceConstructor声明返回instancetype,并且不返回SomeSubClass的实例,那将是超级混乱的. (5认同)
  • 请注意,在iOS 7中,Foundation中的许多方法已转换为instancetype.我个人已经看到这个至少3个不正确的预先存在的代码的案例. (4认同)
  • 仅当编译器可以推断方法族时,“ id”才转换为实例类型。对于便利构造函数或其他id返回方法,它肯定不会这样做。 (2认同)

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)