Id vs Instancetype

id存在的问题

根据Cocoa的命名惯例,Objective-C中init, alloc这类开头的方法,如果以id作为返回类型,会返回消息接收类的实例对象。这些方法有一个相关返回类型,LLVM会进行静态的类型安全检查,但是不是按照这类命名规则命名的方法如果也要返回id,LLVM就不会进行类型安全检查,我们在编译的时候不会发现,而在运行的时候很可能出错,我们现在定义一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface Person : NSObject
- (id)initWithName:(NSString *)name;
+ (id)personName:(NSString *)name;
@end
@implementation Person
- (id)initWithName:(NSString *)name
{
    self = [super init];
    if (self){
    }
    return self;
}
+ (id)personName:(NSString *)name
{
    Person *person = [[Person alloc] init];
    return person;
}
@end

由于personName返回id类型,LLVM不会进行类型安全检查,这样我们就有可能写出错误代码而在编译阶段却发现不了,例如:

1
2
[[[Person alloc] initWithName:@"sss"] count];
[[Person personName:@"sss"] count];

第一行代码就会给出count方法找不到的错误,而第二行不会,由于count不是Person的方法,而personName返回的是id类型,编译器是不会发现问题,只有当运行的时候才会报错。但是initWithName是按照内存管理命名规则定义的方法,同样是返回id,但是该方法就会进行类型安全检查,在编译的时候就会发现问题。

instancetype的出现

instancetype只能用作返回类型,使用instancetype,编译器会进行类型安全检查,就会解决以上问题。

1
2
- (instancetype)initWithName:(NSString *)name;
+ (instancetype)persionName:(NSString *)name;

如果方法的返回类型不是该消息接收类的实例,编译器同样会报错。

1
2
3
4
5
+ (instancetype)personName:(NSString *)name
{
    UIView *view = [UIView alloc] init];
    return person;
}

如果personName方法的返回类型使用id,编译器是认为该代码是没有问题的,而实际上我们想要的是Person的实例。所以,返回类型实例的方法的返回类型尽量用instancetype,以便尽早的在代码的编译阶段发现问题。