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這種集合類型支持泛型:
但是在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è)單詞翻譯的很爛, 但是湊合著理解吧, 看下圖示:
__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í), 提示如下:
這就約束了參數(shù)必須是NSLayoutAnchor<NSLayoutXAxisAnchor *> *
, 為什么可以約束參數(shù)是這種? 我們點(diǎn)進(jìn)去leftAnchor看一下:
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *leftAnchor
正是由于 leftAnchor 在聲明時(shí)就指定了AnchorType的泛型信息:
它的好處就是: 在基類中寫了一個(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