索引
-
關(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.1 系統(tǒng)類中的泛型
2.2 泛型的使用
2.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)該就是:
- 雖然
instancetype
和id
相比有很多便捷之處, 但是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
使用效果:
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
使用效果:
大部分情況下, 我們自定義了一個(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), nonnull
的property
, 在.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瞧哟、泛型的好處
泛型
很顯然可以提高開發(fā)人員的開發(fā)規(guī)范, 減少開發(fā)人員之間的一些不必要的交流.- __從集合中取出來的對(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
類型, 看下圖:
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 方法的返回值情況, 看下圖:
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í)的樣子, 看截圖:
簡單的模仿了一下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ù)類型的, 例如:NSArray
、NSDictionary
秘蛔、NSSet
. 沒錯(cuò), 泛型
的使用場景就是:
- 在集合(數(shù)組, 字典, NSSet) 中使用泛型比較常見
- 當(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)能夠更快速的成長
上一篇: ??