[Objective-C] id类型和instancetype类型

id类型

id数据类型可以存储任何类型的对象。可以说,它是一般对象类型。 例如可以声明一个为id类型的变量:

id graphicObject

也可声明方法使其具有id类型的返回值:

- (id)newObject:(int)type;

id类型是Objective-C中十分重要的特性,它是多态和动态绑定的基础。


instancetype类型

instancetype是clang3.5开始提供的一个关键字,表示一个未知的Objective-C对象,类似于id

按照Cocoa的惯例,Objective-C里所有使用initalloc等名称的方法都会返回一个接受类类型的实例。这些方法被称为“有一个关联的返回类型”的方法,也就是说发给这些方法中的任意一个的消息都会返回一个以相同的静态类型代替接收类类型的一个实例,例如:

@interface NSObject
+ (id)alloc;
- (id)init;
@end

@interface NSArray : NSObject
@end

和下面的通用初始化代码:

NSArray *array = [[NSArray alloc] init];

该表达式[NSArray alloc]NSArray *类型,因为alloc拥有一个隐式的关联的返回类型。类似的,表达式[[NSArray alloc] init]也是NSArray *类型,因为init的返回类型也是一个关联的返回类型,同时也知道它的接收器有一个NSArray *的类型。如果allocinit都没有一个关联的返回类型,表达式就会返回一个id类型,如同方法签名里声明的一样。

iOS 8 里很多以前返回id的方法现在都改为了instancetype,甚至initalloc。另外考虑兼容swift,还是用instancetype

可以通过声明instancetype类型作为一个拥有关联类型的方法的返回类型。instancetype这个上下文关键字只允许用在Objective-C方法的返回类型中。例如:

注意只能用在Objective-C的方法中,变量不行的哦。常见于构造方法。 @implementation oneObject + (instancetype)initOneObject { oneObject *obj = [oneObject new]; return oneObject; } @end

一个关联返回类型也可以通过一些方法推断出来。要确定一个方法是否有一个可以被推断出的关联的返回类型,首先要参考驼峰命名法命名的selector中的第一个单词(如initWithObjects中的init),其次要看其返回类型与自己的类的类型是否兼容,并且:

  • 第一个单词是allocnew,并且方法是一个类方法(+开头)
  • 第一个单词是autoreleaseinitretain或者self,且方法是一个实例方法(-开头)

如果一个拥有关联返回类型的方法被子类方法复写了,那么子类方法必须返回一个与子类类型兼容的类型。比如:

@interface NSString : NSObject
- (NSUnrelated *)init; // incorrect usage: NSUnrelated is not NSString or a superclass of NSString
@end

关联的返回类型只会影响发送的消息的类型或者通过指定方法访问属性的类型。在其他方面,拥有关联返回类型的方法与返回id类型的方法是一致的。


用instancetype代替id有什么好处?

instancetype可以给自定义方法一个类似alloc/init的行为,这个主要方便于构造函数

当使用id时,本质上不会有任何类型检查。使用instancetype,编译器和IDE知道返回的是什么类型的东西,并且更好地检查你的代码和自动补全代码。举个例子:

//Class A
@interface ClassA : NSObject

- (id)methodA;
- (instancetype)methodB;

@end

//Class B
@interface ClassB : NSObject

- (id)methodX;

@end

//Main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //这一行编译器不会产生报错,因为methodA方法返回的是id。但在运行时会出现异常
        [[[[ClassA alloc] init] methodA] methodX];
        //这一行不会通过编译器的检查,错误为"No visible @interface ClassA declares selector methodX",因为methodB返回instancetype,即接收器的类型。
        [[[[ClassA alloc] init] methodB] methodX];
    }
    return 0;
}

也可以说,在所有可以使用instancetype的情形中都有其好处。在详细解释之前,先声明:在一个类返回一个与自己类型一致的实例时,就适合使用instancetype。 实际上,Apple对于这个主题是这么解释的:

在你的代码中,在合适的地方用返回类型instancetype代替id类型。这通常出现在init方法和类的工厂方法。即使编译器会自动的把以initallocnew开头和返回类型为id的方法转换成返回instancetype类型,除此之外它并不会转换其他方法。Objectice-C 明确约定对所有方法都写instancetype。来源Adopting Modern Objective-C

下面继续,首先看几个定义:

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

对于一个类工厂方法,你应该总是使用instancetype类型。编译器不会自动将id转换为instancetype。这个id是一个通用对象。不过你一旦将其改为instancetype,编译器就知道这个方法返回的是一个什么类型的对象。

这并不是一个学术问题。举例来说,以前在Mac OS下[[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]会产生如下错误:Multiple methods named ‘writeData:’ found with mismatched result, parameter type or attributes。原因就在于NSFileHandleNSURLHandle都提供一个writeData:方法。由于[NSFileHandle fileHandleWithStandardOutput]返回的是id,编译器就不确定writeData:是哪个类调用的。 解决这个问题就需要做下列方法中的一个:

[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];

或者

NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];

当然,更好的方法就是声明fileHandleWithStandardOutput的返回类型为instancetype。这样,就不需要声明类型或赋值了。

现在版本的O-C源码里对上述例子有了修改:+ (NSFileHandle *)fileHandleWithStandardOutput;,所以不会有报错。不过,还是有其他例子存在,比如length方法,在UILayoutSupport中返回CGFloat,在NSString里返回NSUInteger

对于初始化器,这个就更加复杂了。当你写出如下代码:

- (id)initWithBar:(NSInteger)bar

编译器就会假设你输入的是这样的代码

- (instancetype)initWithBar:(NSInteger)bar

这对于ARC来说很重要。见前面instancetype的定义。 这也就是为什么很多人会说使用instancetype不是必须的。当然我认为你还是应该去这么写。下面会解释为什么:

这有三个好处:

  1. 明确性。你的代码的行为如同你写的那样,而不是其他行为。
  2. 模式化。你为此养成了一个好的代码习惯,这有时的确很重要。
  3. 一致性。你写的代码前后会保持一致,增加其可读性。

明确性: 不得不承认,一个初始化方法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方法和一个类工厂。 如果你习惯于对init使用id类型,你的代码看起来是这样:

- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

但是,如果你使用instancetype,你的代码就好看的多

- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

这样更明确也更可读。而且更清楚的看到他们返回同样的东西。

结论: 除非你是故意为旧的编译器写代码,否则在合适的地方你应该用instancetype。 以后当你写id之前应该三思:这个方法返回的是否是这个类的实例,如果是,就用instancetype。 当然,还是会有很多需要写id类型的情形,但你可能用instancetype会更多一些。


参考 http://clang.llvm.org/docs/LanguageExtensions.html#related-result-types http://stackoverflow.com/questions/8972221/would-it-be-beneficial-to-begin-using-instancetype-instead-of-id http://nshipster.com/instancetype/ Written with StackEdit.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏iOS技术杂谈

iOS block探究(一): 基础详解你要知道的block都在这里

你要知道的block都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 本文大纲 blo...

34180
来自专栏xx_Cc的学习总结专栏

iOS底层原理总结 - 探寻OC对象的本质

41650
来自专栏androidBlog

笔试题—字符串常见的算法题集锦

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/...

19410
来自专栏大数据学习笔记

Java程序设计(Java9版):第2章 数据类型与运算符(Data types and Operators)

第2章 数据类型与运算符(Data types and Operators) I think everybody in this country should ...

28050
来自专栏liukaili_666888999

@property (copy) NSMutableArray *array; 这个写法会出什么问题

11260
来自专栏静晴轩

类数组借用数组方法

于JavaScript如何将对象转化为数组对象,其用法写法已经很常见且完善,比如JQuery中的makeArray函数对此的实现,也是跟大家想的差不多,只是考虑...

37790
来自专栏GIS讲堂

面向对象的三个基本特征

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

19530
来自专栏个人分享

Scala第一章学习笔记

  面向对象编程是一种自顶向下的程序设计方法。用面向对象方法构造软件时,我们将代码以名词(对象)做切割,每个对象有某种形式的表示服(self/this)、行为(...

11620
来自专栏数据结构与算法

4927 线段树练习5

时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解  查看运行结果 题目描述 Description 有n个数和5种操作...

371140
来自专栏软件开发

JavaSE学习总结(八)—— 异常处理(Exception)

一、理解异常及异常处理的概念 异常就是在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序。 异常不是错误 程序中关键的位置有异常处理,提高程序的稳定...

23690

扫码关注云+社区

领取腾讯云代金券