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

时间:2012-01-23 13:15:17

标签: objective-c instancetype

Clang添加了一个关键字instancetype,据我所知,该关键字将id替换为-allocinit中的返回类型。

使用instancetype代替id是否有好处?

5 个答案:

答案 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转换为instancetypeid是一个通用对象。但是如果你使它成为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}}来自UILayoutSupportNSUInteger来自NSString。)

注意 :由于我写了这篇文章,因此修改了macOS标头以返回NSFileHandle而不是id

对于初始化程序,它更复杂。当你输入这个:

- (id)initWithBar:(NSInteger)bar

...编译器会伪装你输入:

- (instancetype)initWithBar:(NSInteger)bar

这对ARC来说是必要的。这在Clang语言扩展Related result types中有所描述。这就是为什么人们会告诉你没有必要使用instancetype,尽管我认为你应该这样做。本回答的其余部分涉及此问题。

有三个好处:

  1. 明确。您的代码正在执行其所说的内容,而不是其他内容。
  2. 模式。你正在建立良好的习惯,它确实存在。确实存在。
  3. 一致性。您已经为代码建立了一定的一致性,这使其更具可读性。
  4. 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

获取详细信息

**

INSTANCETYPE

** 此关键字只能用于返回类型,它与接收方的返回类型匹配。始终声明init方法返回instancetype。 例如,为什么不为派对实例制作返回类型的派对? 如果Party类被子类化,那将导致问题。子类将继承Party的所有方法,包括初始化器及其返回类型。如果子类的一个实例发送了这个初始化器消息,那将返回?不是指向Party实例的指针,而是指向子类实例的指针。您可能认为没问题,我将覆盖子类中的初始化程序以更改返回类型。但是在Objective-C中,你不能有两个具有相同选择器和不同返回类型(或参数)的方法。通过指定初始化方法返回&#34;接收对象的实例,&#34;你永远不必担心在这种情况下会发生什么。 **

ID

** 在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 消息发送到新 allocFraction 对象,编译器将推断该 init 方法返回的值(其返回类型已声明为类型 instancetype) 将是一个 Fraction 对象。过去,初始化方法的返回类型被声明为类型 id。当您考虑子类化时,这种新类型更有意义,因为继承的初始化方法无法明确定义它们将返回的对象类型。

初始化对象,Stephen G. Kochan,Objective-C 编程,第 6 版