# 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://philm.gitbook.io/philm-ios-wiki/mei-zhou-yue-du/xcode-7-xin-de-te-xing-lightweight-generics-qing-liang-ji-fan-xing-yu-kindof-xiu-shi-fu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
