在写一个单例方法的时候,被问到为什么返回值类型用instancetype
而不用id
。
+ (instancetype)shareManager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_shareManager = [[super allocWithZone:NULL] init];
});
return _shareManager;
}
其实听到这个问题,懵了一下,一直以为就是这么用。至于为什么,没有深入考虑过,只是觉得这么用好,那就继续用吧。所以用了很长时间之后,依旧不知道为什么。
一、 id
与instancetype
概念问题
首先确认instancetype
是什么:instancetype是clang 3.5开始,clang提供的一个关键字,表示某个方法返回的未知类型的Objective-C对象。
其次,id
是什么:id 是一种通用的对象类型,它可以指向属于任何类的对象,也可以理解为万能指针 ,相当于C语言的 void *
id
和instancetype
都可以用来表示未知类型的对象,instancetype
只是用于返回值,但是id
可以用于返回值、参数和变量。
二、相关概念:关联返回类型(related result types)方法,非关联返回类型方法。
根据Cocoa的命名规则,满足下述规则的方法:1、类方法中,以alloc
或new
开头,2、实例方法中,以autorelease
,init
,retain
或self
开头。会返回一个方法所在类类型的对象,这些方法就被称为是关联返回类型的方法。(这些方法的返回结果以方法所在的类为类型)。
除此之外的方法,返回的类型就是方法声明的返回类型。
做一个简单的试验:
// 声明一个实例 继承自NSObject
@interface TestObject : NSObject
+ (id)shareWithId; // 非关联方法,返回类型id
+ (instancetype)shareWIthInstancetype; // 非关联方法,返回类型为当前类TestObject。
@end
>>>>
// 调用两个类方法,并同时使用获得的对象调用任意一个系统方法
[[TestObject shareWithId] removeAllObjects];
// TestObject shareWithId]返回类型为id通用类型,所以编译器不会验证TestObject是否实现removeAllObjects方法。不会报错。
// 但是在运行时,会产生崩溃:-[TestObject objCType]: unrecognized selector sent to instance 0x600000cec570
[[TestObject shareWIthInstancetype] removeAllObjects];
// [TestObject shareWIthInstancetype]返回TestObject类型对象,编译器会验证该方法是否实现,未实现则会报错。
// 错误:No visible @interface for 'TestObject' declares the selector 'removeAllObjects'
这里主要说明两个问题:
1、非cocoa
命名规则下的关联返回类型的方法(自定义快速构造方法)返回的类型是方法声明的返回类型。
2、instancetype
的作用,就是使那些非关联返回类型的方法返回所在类的类型。即可以将非关联返回类型方法,在编译的时候确定对象的真实类型。避免一些问题在运行处才会暴露。
三、结论和建议
id
和instancetype
都可以用来表示未知类型的对象。id
在编译的时候不能确定对象的真实类型,instancetype
在编译的时候能确定对象的真实类型。id
可以用来定义变量、参数、返回值,instancetype
只能用于作为返回值。- 在调用系统类的初始化方法
alloc
、init
会自动将id
转换成instancetype
。不过在自定义构造方法时,建议直接使用instancetype
。 - 单例中的单例方法使用
instancetype
。
参考:
https://blog.csdn.net/yaoliangjun306/article/details/53726282
https://blog.csdn.net/baidu_28787811/article/details/80545552
http://tewha.net/2013/02/why-you-should-use-instancetype-instead-of-id/