# Xcode 7新的特性Lightweight Generics 轻量级泛型与\_\_kindof修饰符

Lightweight Generics 轻量级泛型，轻量是因为这是个纯编译器的语法支持（llvm 7.0），和 Nullability 一样，没有借助任何 objc runtime 的升级，也就是说，这个新语法在 Xcode 7 上可以使用且完全向下兼容（更低的 iOS 版本）

## 带泛型的容器

这无疑是本次最重大的改进，有了泛型后终于可以指定容器类中对象的类型了：

```
NSArray<NSString *> *strings = @[@"sun", @"yuan"];
NSDictionary<NSString *, NSNumber *> *mapping = @{@"a": @1, @"b": @2};
```

返回值的 id 被替换成具体的类型后，令人感动的代码提示也出来了： ![](http://ww3.sinaimg.cn/large/51530583jw1et1s9igr0wj20jc03i74z.jpg)

假如向泛型容器中加入错误的对象，编译器会不开心的：![](http://ww4.sinaimg.cn/large/51530583jw1et1sf4799fj20oo02ywfh.jpg)

系统中常用的一系列容器类型都增加了泛型支持，甚至连 NSEnumerator 都支持了，这是非常 Nice 的改进。和 Nullability 一样，我认为最大的意义还是丰富了接口描述信息，对比下面两种写法：

```
@property (readonly) NSArray *imageURLs;
@property (readonly) NSArray<NSURL *> *imageURLs;
```

不用多想就清楚下面的数组中存的是什么，避免了 NSString 和 NSURL 的混乱。

## 自定义泛型类

比起使用系统的泛型容器，更好玩的是自定义一个泛型类，目前这里还没什么文档，但拦不住我们写测试代码，假设我们要自定义一个 Stack 容器类：

```
@interface Stack<ObjectType> : NSObject
- (void)pushObject:(ObjectType)object;
- (ObjectType)popObject;
@property (nonatomic, readonly) NSArray<ObjectType> *allObjects;
@end
```

这个 ObjectType 是传入类型的 placeholder，它只能在 @interface 上定义（类声明、类扩展、Category），如果你喜欢用 T 表示也 ok，这个类型在 @interface 和 @end 区间的作用域有效，可以把它作为入参、出参、甚至内部 NSArray 属性的泛型类型，应该说一切都是符合预期的。我们还可以给 ObjectType 增加类型限制，比如：

```
// 只接受 NSNumber * 的泛型
@interface Stack<ObjectType: NSNumber *> : NSObject
// 只接受满足 NSCopying 协议的泛型
@interface Stack<ObjectType: id<NSCopying>> : NSObject
```

若什么都不加，表示接受任意类型 ( id )；当类型不满足时编译器将产生 error。 实例化一个 Stack，一切工作正常：

![](http://ww4.sinaimg.cn/large/51530583jw1et2eqtxt07j20n6040wft.jpg)

对于多参数的泛型，用逗号隔开，其他都一样，可以参考 NSDictionary 的头文件。

## 协变性和逆变性

当类支持泛型后，它们的 Type 发生了变化，比如下面三个对象看上去都是 Stack，但实际上属于三个 Type：

```
Stack *stack; // Stack *
Stack<NSString *> *stringStack; // Stack<NSString *>
Stack<NSMutableString *> *mutableStringStack; // Stack<NSMutableString *>
```

当其中两种类型做类型转化时，编译器需要知道哪些转化是允许的，哪些是禁止的，比如，默认情况下：

![](http://ww3.sinaimg.cn/large/51530583jw1et2fajoo7bj210o09y78w.jpg)

我们可以看到，不指定泛型类型的 Stack 可以和任意泛型类型转化，但指定了泛型类型后，两个不同类型间是不可以强转的，假如你希望主动控制转化关系，就需要使用泛型的协变性和逆变性修饰符了：

**covariant - 协变性，子类型可以强转到父类型（里氏替换原则）** contravariant - 逆变性，父类型可以强转到子类型（WTF?）

协变：

```
@interface Stack<__covariant ObjectType> : NSObject
```

效果：

![](http://ww2.sinaimg.cn/large/51530583jw1et2frpvgzpj212q060q5f.jpg)

逆变：

```
@interface Stack<__contravariant ObjectType> : NSObject
```

效果：

![](http://ww1.sinaimg.cn/large/51530583jw1et2fsyrpfej212m05emzl.jpg)

协变是非常好理解的，像 NSArray 的泛型就用了协变的修饰符，而逆变我还没有想到有什么实际的使用场景。

## \_\_kindof

\_\_kindof 这修饰符还是很实用的，解决了一个长期以来的小痛点，拿原来的 UITableView 的这个方法来说：

```
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
```

使用时前面基本会使用 UITableViewCell 子类型的指针来接收返回值，所以这个 API 为了让开发者不必每次都蛋疼的写显式强转，把返回值定义成了 id 类型，而这个 API 实际上的意思是返回一个 UITableViewCell 或 UITableViewCell 子类的实例，于是新的 \_\_kindof 关键字解决了这个问题：

```
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
```

既明确表明了返回值，又让使用者不必写强转。再举个带泛型的例子，UIView 的 subviews 属性被修改成了：

```
@property (nonatomic, readonly, copy) NSArray<__kindof UIView *> *subviews;
```

这样，写下面的代码时就没有任何警告了：

UIButton \*button = view\.subviews.lastObject;

## Where to go

有了上面介绍的这些新特性以及如 instancetype 这样的历史更新，Objective-C 这门古老语言的类型检测和类型推断终于有所长进，现在不论是接口还是代码中的 id 类型都越来越少，更多潜在的类型错误可以被编译器的静态检查发现。 同时，个人感觉新版的 Xcode 对继承链构造器的检测也加强了，NS\_DESIGNATED\_INITIALIZER 这个宏并不是新面孔，可以使用它标志出像 Swift 一样的指定构造器和便捷构造器。

最后，附上一段用上了所有新特性的代码，swift 是发展趋势，如果你暂时依然要写 objective-c 代码，把所有新特性都用上，或许能让你到新语言的迁移更无痛一点。

![](http://ww2.sinaimg.cn/large/51530583jw1et2iirmu7bj20ze0bojvm.jpg)
