instancetype使用总结
Objective-C 一直有一些敏感的子类化问题。思考下面的情况:
@interface Foo : NSObject
+ (Foo *)fooWithInt:(int)x;
@end
@interface SpecialFoo : Foo
@end
...
SpecialFoo *sf = [SpecialFoo fooWithInt:1];
这段代码会产生一个警告:
Incompatible pointer types initializing ’SpecialFoo *’ with an expression of type ’Foo *.
这个问题是因为fooWithInt方法返回了一个Foo对象,所以编译器不知道这个返回的类型实际是一个更明确的类(SpecialFoo),这是一个相当普遍的情形.
思考:[NSMutableArray array]假如返回的是一个NSArray,编译器会不允许你分配到一个子类上(NSMutableArray).
这个情况有几种可能解决的办法。首先,你可以重载fooWithInt:这个方法:
@interface SpecialFoo : Foo
+ (SpecialFoo *)fooWithInt:(int)x;
@end
@implementation SpecialFoo
+ (SpecialFoo *)fooWithInt:(int)x {
return (SpecialFoo *)[super fooWithInt:x];
}
这个方法虽然有效,但是很不方便。你必须要覆盖很多方法来添加类型转换。你也可以执行这个类型转对在调用中: SpecialFoo *sf = (SpecialFoo *)[SpecialFoo fooWithInt:1]; 这样的话,在SpecialFoo中是更方便了,但是对于调用者有产生了许多书写工作了。而且添加大量的强制类型转换会消除了类型检查,所以它也更容易出错。
大多数的解决方法是让他的返回类型为id:
@interface Foo : NSObject
+ (id)fooWithInt:(int)x;
@end
@interface SpecialFoo : Foo
@end
...
SpecialFoo *sf = [SpecialFoo fooWithInt:1];
这个方法相当方便,还消除了类型检查。可用的解决方法中,它是最好的选择,直到最近。这就是为什么cocoa中大多数的构造函数返回id。
Cocoa有着非常一致的命名习惯。任何以init开头的方法都要假定返回一个那个类型的对象。能让编译器来做这些吗?答案是肯定的,并且最新版本的Clang就可以做到。
所以现在,假如你有一个方法叫做initWithFoo:,并返回id,编译器会假设返回的类型是实际上这个类的对象,并且在你类型不匹配的时候给出一个警告。
这个自动转换对于init方法是极好的,但是这个例子是一个简单的构造函数,+fooWithInt:。这种情况下编译器还能帮忙吗?当然可以,但是不是自动的。这个便利构造函数的命名习惯没有init方法一样的强大。SpecialFoo可以有一个便利构造函数像+fooWithInt:specialThing:。
编译器没有更好的办法去从这个命名中发现这是要返回一个SpecialFoo,所以它不会尝试。相反,Clang添加了一个新的类型instancetype。作为返回类型,instancetype表示着”当前的类”。所以现在,你可以声明你的方法如下:
@interface Foo : NSObject
+ (instancetype)fooWithInt:(int)x;
@end
@interface SpecialFoo : Foo
@end
...
SpecialFoo *sf = [SpecialFoo fooWithInt:1];
为了一致性,最好在init方法和遍历构造函数内都使用convenience constructors做返回类型。