系统库范型
Objective C支持轻量级的范型。在编写自定义的范型类之前,我们先来看看Cocoa Touch的集合类(NSArray,NSDictionary,NSSet)对于范型的支持。
首先创建一个数组,这个数组只应该用来存储字符串:
NSMutableArray * array = [[NSMutableArray alloc] init];
[array addObject:@"1"];
//误加了一个非字符串类型进去
[array addObject:@(0)];
这时候,对数组中元素进行遍历,Crash:
for (NSString *string in array) {
NSInteger length = [string length];
}
Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[__NSCFNumber length]: unrecognized selector sent to instance’
如果编译器能帮助我们来确保加入数组中的元素都是String就好了
范型可以帮助我们解决这个问题:
//创建一个保存字符串的数组
NSMutableArray<NSString *> * array = [[NSMutableArray alloc] init];
[array addObject:@"1"];
[array addObject:@(0)];
同样的,NSDictionary和NSSet也支持范型:
NSDictionary<NSString *, NSString *> *dictionary;
NSSet<NSString *> *set;
自定义范型类型
自定义一个范型类型很简单,和其他语言类似,在声明类的时候声明占位符即可:
声明一个Cup容器,用来存储一个值:
// Box.h
@interface Box<ObjectType> : NSObject
@property (nonatomic, readonly) ObjectType value;
- (void)seal:(ObjectType)value;
@end
// Box.m
@interface Box()
@property (strong, nonatomic) id value;
@end
@implementation Box
- (void)seal:(id)value{
_value = value;
}
@end
接着,我们就可以这样使用,
Box<NSString *> * box = [[Box<NSString *> alloc] init];
[box seal:@"1234"];
[box seal:@(1)]; // Warning
关于Objective C范型的几点说明:
- 在头文件的类声明中添加占位符ObjectType
- 在.m文件中无法使用范型占位符,用id类型替代即可
约束
Objective 可以为范型增加轻量级的约束,比如要求ObjectType实现NSCopying协议:
@interface Box<ObjectType:id<NSCopying>> : NSObject
那么,只有实现NSCopying的类型才能够通过编译:
//Error
Box<NSObject *> * box1 = [[Box<NSObject *> alloc] init];
//Work
Box<NSString *> * box2 = [[Box<NSString *> alloc] init];
逆变与协变
创建一个ViewBox和LabelBox,并且把LabelBox赋值给ViewBox
Box<UIView *> * viewBox = [[Box alloc] init];
Box<UILabel *> * labelBox = [[Box alloc] init];
viewBox = labelBox; // Warning
这看似合理,又不合理。
- 合理是因为两个Box中容纳的类型不一样,赋值的时候类型检查不通过。
- 不合理是因为根据里氏替换原则,一个容纳UILabel的Box,那么也应该是一个容纳UIView的Box。
协变
协变由关键字__covariant声明。一个协变类型的范型占位符,如果变量A的占位符类型是子类,那么可以把它赋值给占位符类型是父类的B。
也就是说,通过协变,我们能够解决:一个容纳UILabel的Box,那么也应该是一个容纳UIView的Box。
// Box.h
@interface Box<__covariant ObjectType> : NSObject
@property (nonatomic, readonly) ObjectType value;
- (void)seal:(ObjectType)value;
@end
协变常常用于容器类型,像系统的NSArray,NSDictionary,NSSet都采用了协变:
逆变
逆变(__contravariant),一个逆变类型的范型占位符,如果变量A的占位符类型是父类,那么可以把它赋值给占位符类型是子类的B。
逆变更侧重类型的行为.
举个例子:
//用来解析字符串中的数字
@interface StringPaser<__contravariant ObjectType:NSString *> : NSObject
- (NSString *)paseNumber;
@end
那么以下代码则不会有编译器警告
StringPaser<NSString *> * stringPaser = [[StringPaser alloc] init];
StringPaser<NSMutableString *> * mutableStringPaser = [[StringPaser alloc] init];
//父类赋值给子类
mutableStringPaser = stringPaser;
逆变要求父类和子类能够提供同样的行为,所以通过父类的接口创建的范型类,可以用来处理子类。