OC的泛型和__covariant __contravariant

Created by 大劉 liuxing8807@126.com

什么是泛型

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

比如:

@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由于沒有指定“參數(shù)類型”, 因為編譯器認為是id, 而id是沒有一個名字叫做name的屬性, 因此編譯器報錯.

泛型的用途肯定不僅僅是為了編譯提示, 但是OC中的泛型并沒有Java和Swift中的泛型簡單易用, OC中的泛型更像是一種偽泛型, 為了說明, 我們先來看一下Swift中的泛型:

// 定義一個交換兩個變量的函數(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中默認是不可以這樣的:

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

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

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

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

16.png

__covariant 示例

這兩個關(guān)鍵字只是給編譯器看的,比如以__covariant為例, __covariant 用于向上強轉(zhuǎn),即子類轉(zhuǎn)成父類:
創(chuàng)建一個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>
 編譯正常, 沒有警告
 */
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;
}

要想沒有警告,需要添加 __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)子類沒有警告
        NSLog(@"%@", p1.car.name);
    }
    return 0;
}

泛型的一些實際用法示例

看一個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

這里是一些用于自動布局的類, 這些類全部繼承于NSLayoutAnchor, 以基類NSLayoutAnchor的一個方法示例:

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

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

image.png

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

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

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


image.png

它的好處就是: 在基類中寫了一個方法, 每個子類在聲明時都指定了這個方法中參數(shù)的泛型信息, 子類對參數(shù)進行限制, 從而當(dāng)我們傳入的參數(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'

一個項目實例

再來看一個工作中的實際用法, 假設(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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容