轻量泛型(Lightweight Generics)是 Xocde 7 开始增加的特性。
同时增加的另外两个新特性:可空性(Nullability)和 __kindof。
可空性
可空性是声明传入或输出的类型是否可以是 nil
值:nullable
表示可以是 nil,nonnull
表示不会是 nil,对应 Swift 里的可选类型。
在编写提供给 Swift 使用的 ObjC 类的时候,可空性是必要的。可以防止 Swift 引用的时候可以正确的使用可选类型处理 nil
值,而不是直接解包导致崩溃。
__kindof
__kindof
标识变量是可能是某个类型或其子类型。
轻量泛型
类似于 C++ 中的模板,不过是轻量级的,轻量到它其实只是一个类型占位符。
@interface Base<ObjectType>: NSObject
@end
以上定义了一个类 Base<ObjectType>
,叫参数化类(parameterized class),ObjectType
是类型参数(Type arguments)。
类型参数实际上是个类型占位符,是编译器在编译期间进行类型检查的,而且只能定义接口声明 @interface
中,在实现 @implementation
里需要使用 id
来替代类型参数。
类型参数只能是 Objective-C 对象或者 Block 类型,否则编译会报错。
类型参数可以有多个, 比如声明两个类型参数 ObjectType 和 KeyType:
@interface Base<ObjectType, KeyType>: NSObject
@end
在扩展里声明类型参数的数量必须一致,名称可以不同,编译器是按顺序来做类型对应的:
@interface Base<T1, T2> (Extension) // T1 对应 ObjectType, T2 对应 KeyType
@end
如果主类没有类型参数,扩展里不能指定类型参数;反过来如果主类有类型参数,扩展里可以不写类型参数。
可以限定类型参数的范围,例如可以限定为实现了 NSCopying
协议:
@interface Base<ObjectType: id<NSCopying>>: NSObject
@end
Base<NSString *> *a1; // 编译正确
Base<UIView *> *a2; // 编译错误:type argument 'UIView *' does not satisfy the bound ('id<NSCopying>') of type parameter 'ObjectType'
限定类型范围只能写在主类里,在扩展里想实现类似 C++ 的偏特化是不允许的,编译会报错。
@interface Base<ObjectType: id<NSCopying>> (Extension) // 在扩展里限定类型范围会报错
@end
但是,可以在属性和方法中,进一步限定类型范围,例如限定 akey
是一个遵守 NSCopying
协议的对象:
@interface Base<ObjectType, KeyType>: NSObject
- (nullable ObjectType)objectForKey:(KeyType<NSCopying>)aKey;
@end
因为使用泛型后,使用不同占位符实例化的对象,它们的类型是不一样的:
Base<UIView *> *a1;
Base<NSString *> *a2;
a1
的类型是 Base<UIView *> *
, a2
的类型是 Base<NSString *> *
,它们是两个不同的类型,而不是都是 Base *
类型,
可以理解为 Base
只是类模板,只有指定了具体类型后才确定最终的类型。
这种类型不匹配问题在某些情况下会限制泛型的使用,为了打破这个限制,增加了两个用于标识类型限定的关键字:
__covariant
:协变性,子类型可以转到父类型。
__contravariant
:逆变性,父类型可以转到子类型。
@interface NSDictionary<__covariant KeyType, __covariant ObjectType> : NSObject
@end
如果声明了一个类型:NSDictionary<NSString *, NSString *> *
,那么可以使用任何 NSString *
的类型或子类型做为它的 Key 和 Value。