Nullability俺泣、__kindof祥款、Generics

索引


  1. 關(guān)鍵字
    1.1 id
    1.2 instancetype
    1.3 __kindof
    1.4 nullable
    1.5 nonnull
    1.6 null_resettable
    1.7 _Null_unspecified
  2. 泛型
    2.1 系統(tǒng)類中的泛型
    2.2 泛型的使用
    2.3 自定義泛型
  3. 逆變 && 協(xié)變
    3.1 __covariant(協(xié)變)
    3.2 __contravariant(逆變)

引言


Xcode7 和 iOS9 已經(jīng)出來很久了, 關(guān)于新特性這些東西, 我認(rèn)為大家肯定也已經(jīng)了解的很透徹了. 很明顯一方面是為了迎合Swift, 另一方面則是提高我們開發(fā)人員的開發(fā)規(guī)范, 減少程序員之間的交流. 在這里算是做一個(gè)總結(jié)吧. 好腦子不如爛筆頭, 畢竟有些東西不是天天在用. Note: 相關(guān)文章很多, 純手打, 不喜歡可以路過. OK, Let's Go.

1、關(guān)鍵字


1.0器腋、前奏: 了解 Demo

開始之前先來介紹一下 Demo. 這個(gè) Demo 很簡單, 就不放到 Github 上了.

1. MLPerson 類: 繼承自 NSObject. 其中提供了4種構(gòu)造方法, 一個(gè)屬性, 和一個(gè)實(shí)例方法, 來看看 MLPerson.h:

@interface MLPerson : NSObject

/** 返回 id 指針的 構(gòu)造方法 */
+ (id) person_id;

/** 返回 MLPerson *指針的 構(gòu)造方法 */
+ (MLPerson *) person_MLPerson;

/** 返回 instancetype 的 構(gòu)造方法 */
+ (instancetype) person_instancetype;

/** 返回 __kindof MLPerson *指針的 構(gòu)造方法 */
+ (__kindof MLPerson *) person_kindof;

/** 姓名 */
@property (nonatomic, copy) NSString * name;

/** 奔跑 */
- (void) run;

/** 獲取最好的朋友 */
- (instancetype) obtainBestFriend;

@end
2. MLMan 類: 繼承自 MLPerson. 其中一個(gè)屬性, 和一個(gè)實(shí)例方法, 來看看 MLMan.h:
@interface MLMan : MLPerson

/** 擅長運(yùn)動(dòng) */
@property (nonatomic, copy) NSString * sport;

/** 踢足球 */
- (void) playFootball;

@end
3. MLWoman 類: 繼承自 MLPerson. 其中一個(gè)屬性, 和一個(gè)實(shí)例方法, 來看看 MLWoman.h:
@interface MLWoman : MLPerson

/** 喜歡的電影 */
@property (nonatomic, copy) NSString *movie;

/** 跳舞 */
- (void) dance;

@end

1.1溪猿、id

一個(gè)類的實(shí)例對(duì)象的指針, 萬能指針. 在 objc.h 文件中的定義如下:

typedef struct objc_object *id;

可以看到, id 實(shí)際上是一個(gè)指向objc_object 結(jié)構(gòu)體的指針, 那objc_object 這個(gè)結(jié)構(gòu)體又是什么鬼呢? 查看objc.h 文件中的聲明, 看到如下定義:

// 描述一個(gè)類的實(shí)例對(duì)象
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

頭文件中已經(jīng)寫的很清楚了, 這個(gè)結(jié)構(gòu)體用來描述一個(gè)類的實(shí)例對(duì)象, 那id 是一個(gè)指向這個(gè)結(jié)構(gòu)體的指針, 所以我們可以理解為 id 實(shí)際上就是一個(gè)泛型指針, 可以指向所有Objective-C 對(duì)象. 所以我們可以有以下代碼:

- (void) testMethod {  
    id array = [NSArray new];
    id string = [NSString new];
    id person = [MLPerson new];
}

以上只是對(duì) id 指針做一個(gè)介紹, 本文中主要討論當(dāng)id 指針作為構(gòu)造方法返回值時(shí)的情況.

說說 id 指針作為返回值時(shí)的弊端, 我會(huì)用代碼實(shí)例對(duì)以下問題作出解釋:

1钩杰、不能使用 "點(diǎn)語法": 不做過多解釋, 看代碼
- (void) testMethod {
 /**
  *  這樣寫是不可以的, 因?yàn)椴豢梢允褂?'點(diǎn)語法'.
  *  報(bào)錯(cuò): Property 'name' not found on object of type 'id'
  *  很明顯, 編譯器告訴我們, 在 'id' 類型的對(duì)象中, 沒有找到 'name' 這個(gè)屬性.
  */
  [MLPerson person_id].name;
}
2、不能在編譯的時(shí)候檢查真實(shí)類型:
3诊县、id 可以使用任何對(duì)象的方法
4讲弄、返回值得時(shí)候沒有提示, 也就是說可以用任何指針指向該對(duì)象.

這三個(gè)弊端放在一起說, 是因?yàn)樗麄儗?dǎo)致的問題很像.
其實(shí)對(duì)于構(gòu)造方法中返回 id 類型還不夠明顯, 因?yàn)槟愕耐率褂媚愕念惓跏蓟臅r(shí)候, 他肯定知道該用什么指針去接收. 試想: 如果你有一個(gè)工具類, 其中提供了一個(gè)方法(非構(gòu)造方法), 返回了一個(gè)id 類型的指針, 當(dāng)他調(diào)用這個(gè)方法, 看到返回值是一個(gè) id 類型時(shí), 他當(dāng)時(shí)的反應(yīng)一定是懵逼的, 因?yàn)樗恢烙檬裁粗羔樔ソ邮漳惴祷氐倪@個(gè)對(duì)象. 這會(huì)帶來一個(gè)非常嚴(yán)重的問題, 就是他可以用任意的一個(gè)指針去接收你返回的對(duì)象。Objective-C的運(yùn)行時(shí)特性依痊, 導(dǎo)致在編譯階段并不會(huì)拋出異常避除, 但是在運(yùn)行時(shí)階段則會(huì)導(dǎo)致 Crash 這種嚴(yán)重的問題。舉個(gè)例子來說明:

- (void) testMethod {

  // 弊端3: id 指針可以使用任何對(duì)象的方法
  id object = [MLPerson person_id];
  [object reloadData]; // Objective-C 的運(yùn)行時(shí)特性胸嘁, 導(dǎo)致在編譯階段并不會(huì)拋出異常驹饺, 但是在運(yùn)行時(shí)階段, 則會(huì)導(dǎo)致 Crash缴渊。
  
  // 弊端4: 任何指針指向該對(duì)象
  NSArray *aArray = [MLPerson person_id]; 
  [aArray objectAtIndex: 0]; // Objective-C 的運(yùn)行時(shí)特性赏壹, 導(dǎo)致在編譯階段并不會(huì)拋出異常, 但是在運(yùn)行時(shí)階段衔沼, 則會(huì)導(dǎo)致 Crash蝌借。
}

1.2、instancetype

instancetype 這個(gè)關(guān)鍵字的用法和 id 其實(shí)區(qū)別不是很大, 但是要注意一點(diǎn): instancetype 只能作為返回值, 不能用來定義一個(gè)變量. 代碼如下:

- (void) testMethod {
  // 這里會(huì)拋出一個(gè)異常: Use of undeclared identifier 'instancetype'.
  // 使用了一個(gè)未定義的標(biāo)識(shí)符 'instancetype'.
  instancetype object = [MLPerson person_instencetype];
}

來看看 instancetype 的好處:

1. 會(huì)自動(dòng)識(shí)別當(dāng)前類的對(duì)象.
- (void) testMethod {

  // 識(shí)別當(dāng)前類對(duì)象, 直接調(diào)用對(duì)象方法
  [[MLPerson person_instancetype] run];

  // 調(diào)用其他對(duì)象方法, 將會(huì)拋出異常:
  // No visible @interface for 'MLPerson' declares the selector 'reloadData'
  // 'MLPerson' 類未定義 'reloadData' 方法
  [[MLPerson person_instancetype] reloadData];
}
2. 可以使用 '點(diǎn)語法'
- (void) testMethod {
  
  // 直接使用 '點(diǎn)語法'
  [MLPerson person_instancetype].name;
}
3. 如果用任意的指針指向該對(duì)象, 系統(tǒng)會(huì)在編譯階段就拋出警告
- (void) testMethod {

  // 任意指針指向 `instancetype` 返回的對(duì)象, 將會(huì)拋出警告:
  // ?? Incompatible pointer types initializing 'NSArray *' with an expression of type 'MLPerson *'
  NSArray *array = [MLPerson person_instancetype];
}

其實(shí) instancetype 這個(gè)關(guān)鍵字的弊端不是很明顯, 如果非要吹毛求疵的話, 我想應(yīng)該就是:

  1. 雖然 instancetypeid 相比有很多便捷之處, 但是instancetype 依然不能明確返回值的類型, 需要讀取警告信息, 才能明確知道應(yīng)該用什么指針來接收該對(duì)象, 在構(gòu)造方法中, 返回值為 instancetype 類型, 如果你用子類指針去接收, 依然會(huì)拋出警告.
- (void) testMethod {
  
  // 父類中統(tǒng)一定義了構(gòu)造方法, 用子類指針接受, 依然拋出異常:
  // ?? Incompatible pointer types initializing 'MLMan *' with an expression of type 'MLPerson *'
   MLMan *man = [MLPerson person_instancetype];
}

不可否認(rèn), instancetype 相比id 而言強(qiáng)了不少, 也減少了很多隱在的風(fēng)險(xiǎn)(例如: Crash), 但是個(gè)人認(rèn)為, 還是 __kindof 用起來感覺更友好一些. 接下來說說 __kindof 關(guān)鍵字.

1.3指蚁、__kindof

在說 __kindof 關(guān)鍵字之前, 先說另外一種返回值類型, 就是明確給出返回的類. 代碼如下:

+ (MLPerson *) person_MLPerson;

這種寫法弊端在于, 你只能用 MLPerson *指針去接收返回的對(duì)象, 使用子類去接收, 依然會(huì)拋出警告. 代碼如下:

- (void) testMethod {
  
  // 父類中統(tǒng)一定義了構(gòu)造方法, 用子類指針接受, 依然拋出異常:
  // ?? Incompatible pointer types initializing 'MLMan *' with an expression of type 'MLPerson *'
  MLMan *man = [MLPseron person_MLPerson];
}

重點(diǎn)來了, __kindof 關(guān)鍵字完美的解決了問題. 從字面上來看kindof 的意思就是有點(diǎn)兒, 相當(dāng), 差不多 的意思, 你也可以理解為看起來像的意思, 那在我們這里, 當(dāng)返回值是一個(gè) __kindof MLPerson *的時(shí)候, 我們可以理解為看起來像 MLPerson 的對(duì)象.
__kindof 的好處, 包含了instancetype 的所有便捷之處, 并且也解決的instancetype 無法解決的問題. 代碼如下:

- (void) testMethod {
  
  // 問題完美解決, 并不會(huì)拋出異常, 也不會(huì)在運(yùn)行時(shí)階段導(dǎo)致 Crash.
  MLPerson *person = [MLPerson person_kindof];
  MLMan *man = [MLPerson person_kindof];
  MLWoman *woman = [MLPerson person_kindof];
}

來看看Apple 系統(tǒng)類中使用__kindof關(guān)鍵字的情況. 例如:

- (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;

- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;

@property (nonatomic, readonly) NSArray<__kindof UITableViewCell *> *visibleCells;

1.4菩佑、nullable

nullable: the value can be nil; bridges to an optional.
nullable 關(guān)鍵字用來修飾一個(gè)變量, 用nullable 修飾的變量預(yù)示著該變量可以(有可能)為空.

書寫格式:

@interface MLTestModel : NSObject

@property (nonatomic, strong, nullable) NSString *aName;
@property (nonatomic, strong) NSString *_Nullable bName;
@property (nonatomic, strong) NSString *__nullable cName;

- (nullable NSString *) obtainDName;
- (NSString *__nullable) obtainEName;
- (NSString *_Nullable) obtainFName;

@end

使用效果:

@property (nonatomic, strong, nullable) NSString *aName

@property (nonatomic, strong) NSString *_Nullable bName
@property (nonatomic, strong) NSString *__nullable cName
- (nullable NSString *) obtainDName
- (NSString *__nullable) obtainEName
- (NSString *_Nullable) obtainFName

1.5、nonnull

nonnull: the value won’t be nil; bridges to a regular reference.
nonnull 關(guān)鍵字用來修飾一個(gè)變量, 用nonnull 修飾的變量預(yù)示著該變量不能為空.

書寫格式:

@interface MLTestModel : NSObject

@property (nonatomic, strong, nonnull) NSString *aName;
@property (nonatomic, strong) NSString *_Nonnull bName;
@property (nonatomic, strong) NSString *__nonnull cName;

- (nonnull NSString *) obtainDName;
- (NSString *__nonnull) obtainEName;
- (NSString *_Nonnull) obtainFName;

@end

使用效果:

@property (nonatomic, strong, nonnull) NSString *aName
@property (nonatomic, strong) NSString *_Nonnull bName
@property (nonatomic, strong) NSString *__nonnull cName
- (nonnull NSString *) obtainDName
- (NSString *__nonnull) obtainEName
- (NSString *_Nonnull) obtainFName
向 nonnull 關(guān)鍵字修飾的變量傳入空值時(shí), 編譯器會(huì)直接拋出警告

大部分情況下, 我們自定義了一個(gè)類, 這個(gè)類里面的很多屬性都是不能為空的, 但是如果每一個(gè)變量都加上 nonnull 去修飾, 未免有些太過于繁瑣, 所以這邊有兩個(gè)宏定義NS_ASSUME_NONNULL_BEGIN凝化、NS_ASSUME_NONNULL_END, 這兩個(gè)宏定義之間的所有變量, 默認(rèn)都以nonnull 關(guān)鍵字修飾. 如果某些屬性可以為空, 那么直接用nullable 關(guān)鍵字去修飾該屬性就可以了. 代碼如下:

NS_ASSUME_NONNULL_BEGIN
@interface MLTestModel : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, copy, nullable) NSString *nickName;

+ (NSString *) obtainNameFromPerson:(MLPerson *)person;

// NickName 有可能為空
+ (nullable NSString *) obtainNickNameFromPerson:(MLPerson *)person;

@end
NS_ASSUME_NONNULL_END

PS: 注意一點(diǎn), nonnullproperty, 在.m 文件中, 最好在初始化的時(shí)候賦初值, 或override getter 方法, 否則明明你用nonnull 去修飾的變量, 到頭來返回了一個(gè)空, 那你這即坑隊(duì)友,也坑自己.

1.6稍坯、null_resettable

null_resettable: the value can never be nil when read, but you can set it to nil to reset it. Applies to properties only.
null_resettable 關(guān)鍵字用來修飾一個(gè)變量, 用null_resettable 修飾的變量預(yù)示著該變量的 getter 方法不可以為空, 但是 setter 方法可以為空.

書寫格式:

@interface MLTestModel : NSObject

/**
  * Setter: 可以為空
  * Getter: 不能為空
  * Note: 使用這個(gè)關(guān)鍵字修飾的變量, 需要 override getter 方法, 保證 getter 不返回空值.
  */
@property (nonatomic, copy, null_resettable) IDCard *idCard;

@end


@implementation MLTestModel

#pragma mark - Override Set/Get Methods
#pragma mark -
#pragma mark Get IDCard
- (IDCard *) idCard {
  return _idCard ? _idCard : [IDCard new];
}

@end

1.7、_Null_unspecified

__ Null_unspecified: bridges to a Swift implicitly-unwrapped optional. This is the default.
_Null_unspecified__ 關(guān)鍵字用來修飾一個(gè)變量, 用_Null_unspecified__ 修飾的變量預(yù)示著該變量
不確定是否_為空. 變量的默認(rèn)修飾符. 為了迎合 Swift 的可選類型隱式拆包.
這默認(rèn)的就沒什么可說的了, 例子:

/*
    _Null_unspecified: 不確定是否為空
 */
@property (nonatomic, copy) NSString *_Null_unspecified name_unspecified01;
@property (nonatomic, copy) NSString *__null_unspecified name_unspecified02;

2搓劫、泛型


2.0瞧哟、泛型的好處

  1. 泛型很顯然可以提高開發(fā)人員的開發(fā)規(guī)范, 減少開發(fā)人員之間的一些不必要的交流.
  2. __從集合中取出來的對(duì)象, 會(huì)有類型檢測, 并且直接當(dāng)做泛型的對(duì)象使用, 調(diào)用方法, '點(diǎn)語法'等. __

2.1、泛型的使用

先來看看泛型的書寫規(guī)范: 在類型的后面定義泛型: NSArray<NSString *> *datas
再來看看泛型的用法, 我們以NSArray 為例:

普通 NSArray:

- (void) testMethod {
  
  // 先定義一個(gè)普通數(shù)組
  NSArray *arr = @[@"1"];
  NSString *string = [arr objectAtIndex: 0];
  NSInteger length = string.length;
}

上面這個(gè)代碼塊, 看上去沒問題, 但是他存在隱患. 原因在于: 在未聲明泛型的情況下, objectAtIndex 方法的返回值是 id類型, 看下圖:

objectAtIndex 方法返回的 id 類型

id 類型意味著可以調(diào)用任何對(duì)象的方法并在編譯階段不會(huì)產(chǎn)生任何的警告, 這會(huì)導(dǎo)致運(yùn)行時(shí)階段產(chǎn)生 Crash. 看下面這段代碼:

- (void) testMethod {
  
    // 這段代碼, 編譯器不會(huì)拋出任何警告, 但是在運(yùn)行時(shí)階段, 就 Crash 掉了, 因?yàn)?NSString 沒有 reloadData 這個(gè)方法.
    NSArray *arr = @[@"1"];
    UITableView *tableView = [arr objectAtIndex: 0];
    [tableView reloadData];
}

泛型 NSArray:

- (void) testMethod {
  
  // 聲明一個(gè) NSString 泛型的 NSArray
  NSArray<NSString *> *arr = @[@"1"]; 

  // 可以直接當(dāng)做泛型的類型來調(diào)用方法和點(diǎn)語法
  NSInteger length = [arr objectAtIndex: 0].length; 

  // 類型檢測: 此時(shí)編譯器會(huì)拋出警告
  // ?? Incompatible pointer types initializing 'UITableView *' with an expression of type 'NSString *'
  UITableView *tableView = [arr objectAtIndex: 0]; 
}

再來看看我們定義了泛型之后, objectAtIndex 方法的返回值情況, 看下圖:

objectAtIndex 返回的 泛型 類型

2.2枪向、自定義泛型

當(dāng)我們自己聲明一個(gè)類的時(shí)候, 我們也想自己自定義泛型, 應(yīng)該怎么辦呢?
其實(shí)遇到了不會(huì)的問題, 我們最快速的解決辦法, 就是看看蘋果是怎么寫的, 我們仿照他的寫法, 基本上就能解決問題. 先來看看 NSArray 的頭文件:


@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>

@property (readonly) NSUInteger count;

- (ObjectType)objectAtIndex:(NSUInteger)index;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithObjects:(const ObjectType [])objects count:(NSUInteger)cnt NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

@end

我們看這句話 @interface NSArray<__covariant ObjectType>, 這句話實(shí)際上就是定義了一個(gè)泛型. __covariant先不用去管, 文章的后半部分會(huì)做說明,
再來看 - (ObjectType)objectAtIndex:(NSUInteger)index; 這個(gè)方法, 返回值的類型, 就是泛型的類型.
光說不練假把式, 我們自己嘗試著去寫一個(gè)泛型試一下:

@interface MLRepository<ObjectType> : NSObject

/**
 *  向倉庫中放入對(duì)象
 */
- (void) addObject:(ObjectType)object;

/**
 *  從倉庫中獲取對(duì)象
 */
- (ObjectType) obtainObject;

@end

我們來嘗試著去使用一下

- (void) testMethod {

  MLRepository<NSString *> *rep = [[MLRepository alloc] init];
  [rep addObject: @"1"];
  NSString *string = [rep obtainObject];
}

來看看 addObject 方法 和 obtainObject 使用時(shí)的樣子, 看截圖:

addObject, 這個(gè)方法現(xiàn)在的參數(shù)類型就是我們定義的泛型
obtainObject, 這個(gè)方法現(xiàn)在的返回值類型也是我們定義的泛型

簡單的模仿了一下NSArray 的寫法, 我們也自定義出來了我們自己的泛型. 還是比較簡單的. 再舉兩個(gè)例子, 也是真實(shí)項(xiàng)目中會(huì)遇到的:

情景1: 如果這個(gè)倉庫中, 只允許放入 MLCar 和 MLCar 子類的情況.
@interface MLRepository<ObjectType : MLCar*> : NSObject
@end
情景2: 如果這個(gè)倉庫中, 只要是遵守某協(xié)議的對(duì)象都可以放入的情況
@interface MLRepository<ObjectType : id<NSCopying>> : NSObject
@end

2.3勤揩、系統(tǒng)類中的泛型

來看看系統(tǒng)人家是怎么使用泛型的.


- (NSArray<ObjectType> *)arrayByAddingObject:(ObjectType)anObject;
- (NSArray<ObjectType> *)arrayByAddingObjectsFromArray:(NSArray<ObjectType> *)otherArray;

- (void)removeObjectsForKeys:(NSArray<KeyType> *)keyArray;
- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;

- (nullable ObjectType)anyObject;
- (BOOL)containsObject:(ObjectType)anObject;

細(xì)心的同學(xué)肯定發(fā)現(xiàn)了, 這幾個(gè)方法都是出自集合數(shù)據(jù)類型的, 例如:NSArrayNSDictionary秘蛔、NSSet. 沒錯(cuò), 泛型的使用場景就是:

  1. 在集合(數(shù)組, 字典, NSSet) 中使用泛型比較常見
  2. 當(dāng)聲明一個(gè)類的時(shí)候, 類里面的某些屬性的類型不確定, 這時(shí)候我們才是會(huì)用泛型

3陨亡、逆變 && 協(xié)變


3.1、__covariant(協(xié)變)

__covariant: 協(xié)變, 用于泛型的數(shù)據(jù)強(qiáng)轉(zhuǎn)類型, 可以向上強(qiáng)轉(zhuǎn), 子類 可以 轉(zhuǎn)成 父類. 文字往往看起來很抽象, 很枯燥, 所以還是直接上代碼吧. 依然用我們剛才定義的那個(gè)泛型:

@interface MLRepository<__covariant ObjectType> : NSObject

/**
 *  向倉庫中放入對(duì)象
 */
- (void) addObject:(ObjectType)object;

/**
 *  從倉庫中獲取對(duì)象
 */
- (ObjectType) obtainObject;

@end

然后定義三個(gè)類

第一個(gè)是 MLCar, 繼承自 NSObject

@interface MLCar : NSObject
@end

第二個(gè)是 MLBus, 繼承自 MLCar

@interface MLBus : MLCar
@end

第三個(gè)是 MLTaxi, 繼承自MLCar

@interface MLTaxi : MLCar
@end

然后我們寫一個(gè)測試方法, 來看看什么是協(xié)變

- (void) testMethod {
  
  // 聲明三個(gè)泛型的 MLRepository 倉庫
  MLRepository<MLCar *> *carRep = [[MLRepository alloc] init];
  MLRepository<MLBus *> *busRep = [[MLRepository alloc] init];
  MLRepository<MLTaxi *> *taxiRep = [[MLRepository alloc] init];

  // 剛才說過, 協(xié)變用于泛型的數(shù)據(jù)強(qiáng)轉(zhuǎn)類型, 可以向上強(qiáng)轉(zhuǎn), 子類 可以 轉(zhuǎn)成 父類.
  // 然后來看看相互之間賦值的情況
  carRep = busRep; // MLBus --> MLCar, 符合協(xié)變規(guī)則.
  carRep = taxiRep; // MLTaxi --> MLCar, 符合協(xié)變規(guī)則.

  // MLTaxi --> MLBus, 不符合協(xié)變規(guī)則. 編譯器將會(huì)拋出警告: 
  // ?? Incompatible pointer types assigning to 'MLRepository<MLBus *> *` from `MLRepository<MLTaxi *> *`
  busRep = taxiRep; 
  // 同理,  MLCar --> MLBus, 也是不符合協(xié)變規(guī)則的, 編譯器依然會(huì)拋出警告
  busRep = carRep;
}

3.2深员、__contravariant(逆變)

__contravariant: 逆變, 用于泛型的數(shù)據(jù)強(qiáng)轉(zhuǎn)類型, 可以向下強(qiáng)轉(zhuǎn), 父類 可以 轉(zhuǎn)成 子類. 我還依然使用 MLRepository 這個(gè)例子:

@interface MLRepository<__contravariant ObjectType> : NSObject

/**
 *  向倉庫中放入對(duì)象
 */
- (void) addObject:(ObjectType)object;

/**
 *  從倉庫中獲取對(duì)象
 */
- (ObjectType) obtainObject;

@end

再來一段測試代碼, 看看效果:

- (void) testMethod {
  
  // 聲明三個(gè)泛型的 MLRepository 倉庫
  MLRepository<MLCar *> *carRep = [[MLRepository alloc] init];
  MLRepository<MLBus *> *busRep = [[MLRepository alloc] init];
  MLRepository<MLTaxi *> *taxiRep = [[MLRepository alloc] init];

  // 剛才說過, 逆變用于泛型的數(shù)據(jù)強(qiáng)轉(zhuǎn)類型, 可以向下強(qiáng)轉(zhuǎn), 父類 可以 轉(zhuǎn)成 子類
  // 然后來看看相互之間賦值的情況
  busRep = carRep; //MLCar -->  MLBus, 符合逆變規(guī)則.
  taxiRep = carRep; // MLCar --> MLTaxi, 符合逆變規(guī)則.

  // MLTaxi --> MLBus, 不符合逆變規(guī)則. 編譯器將會(huì)拋出警告: 
  // ?? Incompatible pointer types assigning to 'MLRepository<MLBus *> *` from `MLRepository<MLTaxi *> *`
  busRep = taxiRep; 
  // 同理,  MLCar --> MLBus, 也是不符合逆變規(guī)則的, 編譯器依然會(huì)拋出警告
  busRep = carRep;
}

協(xié)變逆變這東西, 有興趣的小伙伴可以自己寫寫測試代碼. 很快就理解了.


Lemon龍說:

如果您在文章中看到了錯(cuò)誤 或 誤導(dǎo)大家的地方, 請(qǐng)您幫我指出, 我會(huì)盡快更改

如果您有什么疑問或者不懂的地方, 請(qǐng)留言給我, 我會(huì)盡快回復(fù)您

如果您覺得本文對(duì)您有所幫助, 您的喜歡是對(duì)我最大的鼓勵(lì)

如果您有好的文章, 可以投稿給我, 讓更多的 iOS Developer 在簡書這個(gè)平臺(tái)能夠更快速的成長


上一篇: ??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末负蠕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子倦畅,更是在濱河造成了極大的恐慌遮糖,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滔迈,死亡現(xiàn)場離奇詭異止吁,居然都是意外死亡被辑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門敬惦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盼理,“玉大人,你說我怎么就攤上這事俄删『暾” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵畴椰,是天一觀的道長臊诊。 經(jīng)常有香客問我,道長斜脂,這世上最難降的妖魔是什么抓艳? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮帚戳,結(jié)果婚禮上玷或,老公的妹妹穿的比我還像新娘。我一直安慰自己片任,他們只是感情好偏友,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著对供,像睡著了一般位他。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上产场,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天鹅髓,我揣著相機(jī)與錄音,去河邊找鬼涝动。 笑死迈勋,一個(gè)胖子當(dāng)著我的面吹牛炬灭,可吹牛的內(nèi)容都是我干的醋粟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼重归,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼米愿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鼻吮,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤育苟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后椎木,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體违柏,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡博烂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漱竖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禽篱。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖馍惹,靈堂內(nèi)的尸體忽然破棺而出躺率,到底是詐尸還是另有隱情,我是刑警寧澤万矾,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布悼吱,位于F島的核電站,受9級(jí)特大地震影響良狈,放射性物質(zhì)發(fā)生泄漏后添。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一薪丁、第九天 我趴在偏房一處隱蔽的房頂上張望吕朵。 院中可真熱鬧,春花似錦窥突、人聲如沸努溃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梧税。三九已至,卻和暖如春称近,著一層夾襖步出監(jiān)牢的瞬間第队,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來泰國打工刨秆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凳谦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓衡未,卻偏偏與公主長得像尸执,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缓醋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容