在学习安卓的过程中,发现java的泛型机制特别的好用,Objective-C是一门动态性弱类型语言,例如毫无关系的两个类ABA *a = [[B alloc] init]Objective-C编译和运行中都不会出错。但是A a = new B()java中编译失败。这样,可以强制编程人员注意类型转换。

在不了解Objective-C的泛型之前,我一直以为Objective-C的泛型是鸡肋,例如:

@property (nonatomic, strong) NSMutableArray<NSString *> *times;
[self.times addObject:[[NSObject alloc] init]];

我声明一个NSString类型的泛型数组,但是我往数组中添加的是一个NSObject类型的对象,虽然编译器会警告,但并不会报错(大部分程序猿也会忽略这个警告)。所以,我认为Objective-C的泛型是鸡肋。当然,这只是我之前的自以为(还是自己学的不深入)。但是今天看过一篇介绍Objective-C的泛型以后,才发现Objective-C也能做到编译时报错。

这篇文章就来说说泛型

  1. 泛型是什么
  2. 为什么要用泛型
  3. 泛型怎么用
  4. 泛型进阶
  5. 泛型的延伸使用
泛型是什么

泛型可以让你使用定义的类型来编写灵活的、可重用的函数和类型,可以避免重复,以清晰,抽象的方式表达其意图。用人话来说,泛型给予我们更抽象的封装函数或类的能力,不严谨的来讲,一门语言越抽象使用越方便。Objective-C中的NSArrayNSDictionary都是基于泛型编写的集合类型,如果不太理解也没关系,下面讲几个例子理解下。

1. Objective-C中的泛型

在2015年WWDC上苹果推出了Swift2.0版本,为了让开发者从Objective-C更好得过度到Swift上,苹果也为Objective-C带来了Generics泛型支持

所以,我们经常看到的OC中的泛型比如:

// 实例化一个元素类型为`NSString`的数组
NSArray <NSString *> *array = [NSArray new];
// 或者字典
NSDictionary <NSString *, NSNumber *> *dict = @{@"manoboo": @1}

或者:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}

我们先看看OC中的泛型大概做了些什么:
打开NSArray.h可以看到:

@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
- (ObjectType)objectAtIndex:(NSUInteger)index;
- (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects count:(NSUInteger)cnt NS_DESIGNATED_INITIALIZER;
@end

我们会发现,无论是查找还是初始化,都使用了ObjectType

声明一个Generics的格式如下:

@interface 类名 <占位类型名称>
@end

占位类型后也可以加入类型限制,比如:

@interface MBCollection <T: NSString *>
@end

若不加入类型限制,则表示接受id即任意类型。我们先来看看一个简单使用泛型的例子:

@interface MyCollection <__covariant T> : NSObject
@property (nonatomic, readonly, strong) NSArray<T> *elements;
- (void)addObject:(T)object;
- (T)getObjectInIndex:(NSInteger)index;
@end

其中T为我们提现声明好的占位类型名称,可自定义(如ObjectType等等),需要注意的是该T的作用域只限于@interface MyCollection@end之间,.m文件则不能使用。至于泛型占位名称之前的修饰符则可分为两种:__covariant(协变)__contravariant(逆变)

两者的区别如下:

__covariant意味协变,意思是指子类可以强制转换为父类,遵从的是SOLID中的L即里氏替换原则,大概可以描述为:程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的。

__contravariant意为逆变,意思是指父类可以强制转换为子类。

用我们上面自定义的泛型来解释:

MyCollection *collection;
MyCollection <NSString *> *string_collection;
MyCollection <NSMutableString *> *mString_collection;
collection = string_collection;
string_collection = collection;
collection = mString_collection;

默认不指定泛型类型的情况下,不同类型的泛型可以相互转换。

这个时候就可以在占位泛型名称之前加入修饰符__covariant__contravariant来控制转换关系,像NSArray就使用了__covariant修饰符。

引申:

在上面的这个例子中,声明属性时,还可以在泛型钱添加__kindof关键词,表示其中的类型为该类型或者其子类,如:

@property (nonatomic, readonly, strong) NSArray<__kindof T> *elements;

之后就可以这样调用了

MyCollection<NSString *> *c = [[MyCollection alloc] init];
NSMutableString *d = c.elements.lastObject;

也就不会有警告了。