首页 > 代码库 > iOS学习笔记(01) - 泛型

iOS学习笔记(01) - 泛型

  决定新开一坑,在不断学习的同时分享自己的学习历程给大家,既是对自己学习的记录,又希望能对大家提供些微的帮助。

  

  这一篇文章主要来介绍泛型的意义、使用与声明方法等。

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     // Java11     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     // iOS3     JTPerson<IOS *> *iOSP = [[JTPerson alloc] init];4     5     // Language6     JTPerson<Language *> *p = [[JTPerson alloc] init];7     8     // 如果子类想给父类赋值,协变9     p = iOSP;

  并没有报错。

  也就是说,若泛型为子类的对象想给泛型为父类的对象赋值的时候,我们需要在泛型前面添加 协变__convariant ,告诉编译器,这样转换没有问题。

  同理,__contravariant逆变就是若泛型为父类的对象想给泛型为子类的对象赋值的时候,我们需要在泛型前面添加 逆变__contravariant ,告诉编译器,这样转换没有问题。

  这就是协变与逆变的含义。

  协变与逆变本身是面向对象继承的语言的一个特性,也并不只应用在泛型这里。

iOS学习笔记(01) - 泛型