OC的泛型和__covariant __contravariant

Created by 大劉 liuxing8807@126.com

什么是泛型

泛型彤蔽,即“參數(shù)化類型”让禀。一提到參數(shù)挑社,最熟悉的就是定義方法時(shí)有形參,然后調(diào)用此方法時(shí)傳遞實(shí)參

比如:

@interface Computer : NSObject

@property (nonatomic, copy) NSString *name;
@end

@implementation Computer

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [Test test1];
        
        NSArray<Computer *> *computerArray = [NSArray arrayWithObjects:Computer.new, Computer.new, nil];
        computerArray[0].name = @"Apple"; // OK
        
        NSArray *computerArray2 = [NSArray arrayWithObjects:Computer.new, Computer.new, nil];
        computerArray2[0].name = @"華碩"; // Error: Property 'name' not found on object of type 'id'
    }
    return 0;
}

computerArray2由于沒(méi)有指定“參數(shù)類型”, 因?yàn)榫幾g器認(rèn)為是id, 而id是沒(méi)有一個(gè)名字叫做name的屬性, 因此編譯器報(bào)錯(cuò).

泛型的用途肯定不僅僅是為了編譯提示, 但是OC中的泛型并沒(méi)有Java和Swift中的泛型簡(jiǎn)單易用, OC中的泛型更像是一種偽泛型, 為了說(shuō)明, 我們先來(lái)看一下Swift中的泛型:

// 定義一個(gè)交換兩個(gè)變量的函數(shù)
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
    
    Array<String> a = []
}

var numb1 = 100
var numb2 = 200
 
print("交換前數(shù)據(jù):  \(numb1) 和 \(numb2)")
swapTwoValues(&numb1, &numb2)
print("交換后數(shù)據(jù): \(numb1) 和 \(numb2)")
 
var str1 = "A"
var str2 = "B"
 
print("交換前數(shù)據(jù):  \(str1) 和 \(str2)")
swapTwoValues(&str1, &str2)
print("交換后數(shù)據(jù): \(str1) 和 \(str2)")

這里的T就是“參數(shù)化類型”, 即泛型.
Swift的Array這種集合類型支持泛型:

image.png

但是在OC中默認(rèn)是不可以這樣的:

- (void)swapTwoValues<T>(T a, T b); // 編譯器報(bào)錯(cuò)

這就需要用到協(xié)變和逆變

OC中 用于泛型的關(guān)鍵字 __covariant(協(xié)變) 和 __contravariant(逆變)

在OC中要想直接使用泛型聲明成員變量, 需要額外使用關(guān)鍵字 __covariant(協(xié)變) 和 __contravariant(逆變, 有的叫裂變), 這兩個(gè)單詞翻譯的很爛, 但是湊合著理解吧, 看下圖示:

16.png

__covariant 示例

這兩個(gè)關(guān)鍵字只是給編譯器看的巡揍,比如以__covariant為例, __covariant 用于向上強(qiáng)轉(zhuǎn)痛阻,即子類轉(zhuǎn)成父類:
創(chuàng)建一個(gè)Person, Person有一輛車car, 車的類型是泛型:

#import <Foundation/Foundation.h>

@interface Car : NSObject // 汽車

@property (nonatomic, copy) NSString *name;
@end

@interface BMW : Car // 寶馬
@end

@interface Ford : Car // 福特
@end

@implementation Car
@end

@implementation BMW
@end

@implementation Ford
@end

// Person
@interface Person<__covariant T> : NSObject

@property (nonatomic, strong) T car;
@end
// 寶馬
Person<BMW *> *p_bmw = Person.new;
BMW *bmw = BMW.new;
p_bmw.car = bmw;
p_bmw.car.name = @"BMW";

// 福特
Person<Ford *> *p_ford = Person.new;
Ford *ford = Ford.new;
p_ford.car = ford;
// p_ford.car = bmw; // 由于指定了泛型 T 是<Ford *>, 因此編譯器可以給出警告: Incompatible pointer types assigning to 'Ford * _Nonnull' from 'BMW *'
p_ford.car.name = @"FORD";

Person<Car *> *p_car = Person.new;

/**
 由于泛型信息中使用了: <__covariant T>
 編譯正常, 沒(méi)有警告
 */
p_car = p_ford; // 子轉(zhuǎn)父 Person<Car *> <---- Person<Ford *>
NSLog(@"%@", p_car.car.name);

NSLog(@"Above code is ok");

__contravariant 示例

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person<Car *> *p0 = Person.new;
        
        Person<Ford *> *p1 = Person.new;
        Ford *ford = Ford.new;
        p1.car = ford;
        p1.car.name = @"ford";
        
        p1 = p0; // 父類轉(zhuǎn)子類, Warning: Incompatible pointer types assigning to 'Person<Ford *> *' from 'Person<Car *> *'
        NSLog(@"%@", p1.car.name);
    }
    return 0;
}

要想沒(méi)有警告,需要添加 __contravariant:

@interface Person<__contravariant T> : NSObject

@property (nonatomic, strong) T car;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person<Car *> *p0 = Person.new;
        
        Person<Ford *> *p1 = Person.new;
        Ford *ford = Ford.new;
        p1.car = ford;
        p1.car.name = @"ford";
        
        p1 = p0; // OK, 父類轉(zhuǎn)子類沒(méi)有警告
        NSLog(@"%@", p1.car.name);
    }
    return 0;
}

泛型的一些實(shí)際用法示例

看一個(gè)Apple的API

@class NSLayoutXAxisAnchor,NSLayoutYAxisAnchor,NSLayoutDimension;
@interface UIView (UIViewLayoutConstraintCreation)

@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *leadingAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *trailingAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *leftAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *rightAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *topAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *bottomAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutDimension *widthAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutDimension *heightAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *centerXAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *centerYAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *firstBaselineAnchor API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *lastBaselineAnchor API_AVAILABLE(ios(9.0));
@end

這里是一些用于自動(dòng)布局的類, 這些類全部繼承于NSLayoutAnchor, 以基類NSLayoutAnchor的一個(gè)方法示例:

- (NSLayoutConstraint *)constraintEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor;

這里就使用了泛型進(jìn)行約束, 雖然后這個(gè)方法是基類NSLayoutAnchor的方法, 但是由于參數(shù)中指定了泛型信息 NSLayoutAnchor<AnchorType>, 當(dāng)在XCode中調(diào)用時(shí), 提示如下:

image.png

這就約束了參數(shù)必須是NSLayoutAnchor<NSLayoutXAxisAnchor *> *, 為什么可以約束參數(shù)是這種? 我們點(diǎn)進(jìn)去leftAnchor看一下:

@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *leftAnchor

正是由于 leftAnchor 在聲明時(shí)就指定了AnchorType的泛型信息:


image.png

它的好處就是: 在基類中寫了一個(gè)方法, 每個(gè)子類在聲明時(shí)都指定了這個(gè)方法中參數(shù)的泛型信息, 子類對(duì)參數(shù)進(jìn)行限制, 從而當(dāng)我們傳入的參數(shù)不匹配時(shí)可以給出合適的Warning:

UIView *view = UIView.new;
[self.view addSubview:view];
[view.leftAnchor constraintEqualToAnchor:self.view.centerYAnchor];
// Warning: Incompatible pointer types sending 'NSLayoutYAxisAnchor *' to parameter of type 'NSLayoutAnchor<NSLayoutXAxisAnchor *> * _Nonnull'

一個(gè)項(xiàng)目實(shí)例

再來(lái)看一個(gè)工作中的實(shí)際用法, 假設(shè)服務(wù)器返回的數(shù)據(jù)是code, message, data, 但是data的類型是不確定的, 我們就可以這樣處理:

@interface MyBaseResponse<__covariant T> : NSObject

@property (nonatomic, assign) NSInteger code;
@property (nonatomic, copy) NSString *message;
@property (nonatomic, strong) T data;

@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腮敌,一起剝皮案震驚了整個(gè)濱河市阱当,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌糜工,老刑警劉巖弊添,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異捌木,居然都是意外死亡油坝,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門刨裆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)澈圈,“玉大人,你說(shuō)我怎么就攤上這事帆啃∷才” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵努潘,是天一觀的道長(zhǎng)诽偷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)疯坤,這世上最難降的妖魔是什么报慕? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮贴膘,結(jié)果婚禮上卖子,老公的妹妹穿的比我還像新娘。我一直安慰自己刑峡,他們只是感情好洋闽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著突梦,像睡著了一般诫舅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宫患,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天刊懈,我揣著相機(jī)與錄音,去河邊找鬼。 笑死虚汛,一個(gè)胖子當(dāng)著我的面吹牛匾浪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播卷哩,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛋辈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了将谊?” 一聲冷哼從身側(cè)響起冷溶,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尊浓,沒(méi)想到半個(gè)月后逞频,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栋齿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年苗胀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褒颈。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡柒巫,死狀恐怖励堡,靈堂內(nèi)的尸體忽然破棺而出谷丸,到底是詐尸還是另有隱情,我是刑警寧澤应结,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布刨疼,位于F島的核電站,受9級(jí)特大地震影響鹅龄,放射性物質(zhì)發(fā)生泄漏揩慕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一扮休、第九天 我趴在偏房一處隱蔽的房頂上張望迎卤。 院中可真熱鬧,春花似錦玷坠、人聲如沸蜗搔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)樟凄。三九已至,卻和暖如春兄渺,著一層夾襖步出監(jiān)牢的瞬間缝龄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叔壤,地道東北人瞎饲。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像炼绘,于是被迫代替她去往敵國(guó)和親企软。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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