决定新开一坑,在不断学习的同时分享自己的学习历程给大家,既是对自己学习的记录,又希望能对大家提供些微的帮助。
这一篇文章主要来介绍泛型的意义、使用与声明方法等。
1.泛型:限制类型
1.1.泛型使用场景:
1.在集合(数组NSArray、字典NSDictionary、集合NSSet)中使用泛型比较常见。
2.当声明一个类,但是类里面的某些属性的类型不确定的时候,我们才使用泛型。
1.2.泛型书写规范
在类型后面定义泛型:NSMutableArray<UITouch *> dataArray
1.3.泛型修饰
只能修饰方法的调用。
1.4.泛型好处:
1.提高开发的规范,减少程序员之间的交流。
2.通过集合取出来的对象,可以直接当做泛型对象使用。这样我们就可以直接使用.点语法。
2.代码使用泛型:
2.1.声明一个泛型为NSString的数组
1 // 具体做法就是在 NSMutableArray 后带一个 <NSString *> ,尖括号内部即为泛型类型
2 @property (nonatomic, strong, nullable) NSMutableArray<NSString *> *dataArray;
2.2.当我们要给数组添加对象或取出对象的时候,系统就会自动提示应该传入或者取出来的对象的类型,这个类型就是你刚才声明的泛型类型
1 // 1.没有使用泛型 2 // [self.dataArray addObject:<#(nonnull id)#>]; 3 4 // 2.使用泛型 5 // [self.dataArray addObject:<#(nonnull NSString *)#>]; 6 7 // 3.添加不正确的类型,会出现警告 8 // [self.dataArray addObject:@1]; 9 10 // 4.我们可以直接将集合中取出来的对象当做泛型使用 11 NSInteger length = [self.dataArray.firstObject length];
基本上实现了使用泛型过程中可能出现的情况。
3.泛型的自定义
刚才我们只是实现了系统类NSMutableArray的泛型。接下来我们要考虑下,我们怎么样在我们自己的类中声明一个泛型的属性呢?
为了这个目的,我们创建一个 Language 的类表示 “语言”。并且创建两个 Language 的子类,分别叫 Java 和 IOS 。很明显这两个是“某一个类型的语言”。我们创建一个Person的类,给类声明一个泛型,在类的 .h 文件中声明一个声明一个属性,这个属性表示这个人会的语言,即为 IOS 或者 Java 。那么我们有以下两种声明方式:
1 // 语言 2 // 1.id:任何对象都能传进来 3 //@property (nonatomic, strong, null_unspecified) id language; 4 // 2.Language:在外面调用的时候不能提示具体是哪种语言 5 //@property (nonatomic, strong, null_unspecified) Language *language;
因为 Language 这个语言并不能代表这个 Person 究竟会什么语言,我们需要的属性时 IOS 和 Java。这两种都可以在调用的时候传入 Java 和 IOS 两种对象,但它们的缺点也非常明显,若使用 id 则我们可以传入任何对象,而不只是 Java 和 IOS ;使用 Language * 呢,我们没有办法在编译的时候确定这个人究竟会什么语言,而只能在运行时判断。有没有办法让我们在编译的时候就能知道 Person 具体会哪种 Language 呢?
办法就是泛型。
声明自定义类的泛型,我们需要做这样一步:
1 // (给类)声明一个泛型 2 @interface JTPerson<ObjectType> : NSObject
看出区别了吗?我们在 interface 类名之后加了一对尖括号 <> ,中间是 ObjectType 。这个就代表泛型。这样我们在声明属性的时候就可以这么写:
1 @property (nonatomic, strong, null_unspecified) ObjectType language;
也就是,我们现在不指定具体的类型,而在实例化这个类的时候确定这个泛型。若不确定,那么所有的 ObjectType 会自动变成 id 。像这样:
1 // iOS 2 JTPerson<IOS *> *iOSP = [[JTPerson alloc] init]; 3 4 // [iOSP setLanguage:@"123"]; 5 6 // [iOSP setLanguage:<#(IOS * _Null_unspecified)#>]; 7 8 // [iOSP setLanguage:[[Java alloc] init]]; 9 10 // Java 11 JTPerson<Java *> *javaP = [[JTPerson alloc] init]; 12 13 // [javaP setLanguage:@"123"]; 14 15 // [javaP setLanguage:<#(Java * _Null_unspecified)#>]; 16 17 // [javaP setLanguage:[[IOS alloc] init]];
这样,我们在声明 Person这个类的时候,就顺便声明了这个类的泛型。这样系统就会在你使用到泛型的属性与方法的时候,自动提醒你声明的泛型类型了。
4.泛型的协变与逆变
首先我们来看一个方法:
1 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 2 3 [touches anyObject]; 4 }
大家应该很熟悉这个方法了。这就是 UIViewController 继承自 UIResponder 的方法,是点击这个视图控制器之后事件链的最后一环。这时候经过上面的学习,我们会看到这其中就使用了泛型: (NSSet<UITouch *> *) 。这样我们点进集合 NSSet 会发现它是这样写的:
前后我们都很清楚: interface:声明;NSSet:集合;ObjectType:泛型;NSObject:是NSSet的父类,也是根类;后面尖括号中表示的是遵守的协议。
那么 __covariant 代表什么?
这里我们先来学习两个单词: covariant:协变的 contravariant:逆变的。
那么 __covariant协变 代表什么?
我们来试一下。在刚才的自定义类 Person 的泛型 ObjectType 前加这样一条修饰: __covariant
1 @interface JTPerson<__covariant ObjectType> : NSObject
然后在调用的时候,我们声明两个实例,泛型分别为父类 Language 和子类 IOS。这样若泛型为 IOS 的 Person 想给泛型为 Language 的 Person 赋值,会怎么样:
1 // 1.协变 2 // iOS 3 JTPerson<IOS *> *iOSP = [[JTPerson alloc] init]; 4 5 // Language 6 JTPerson<Language *> *p = [[JTPerson alloc] init]; 7 8 // 如果子类想给父类赋值,协变 9 p = iOSP;
并没有报错。
也就是说,若泛型为子类的对象想给泛型为父类的对象赋值的时候,我们需要在泛型前面添加 协变__convariant ,告诉编译器,这样转换没有问题。
同理,__contravariant逆变就是若泛型为父类的对象想给泛型为子类的对象赋值的时候,我们需要在泛型前面添加 逆变__contravariant ,告诉编译器,这样转换没有问题。
这就是协变与逆变的含义。
协变与逆变本身是面向对象继承的语言的一个特性,也并不只应用在泛型这里。