OC的泛型和__covariant __contravariant

Created by 大刘 liuxing8807@126.com

什么是泛型

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参

比如:

@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由于没有指定“参数类型”, 因为编译器认为是id, 而id是没有一个名字叫做name的属性, 因此编译器报错.

泛型的用途肯定不仅仅是为了编译提示, 但是OC中的泛型并没有Java和Swift中的泛型简单易用, OC中的泛型更像是一种伪泛型, 为了说明, 我们先来看一下Swift中的泛型:

// 定义一个交换两个变量的函数
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("交换前数据:  \(numb1) 和 \(numb2)")
swapTwoValues(&numb1, &numb2)
print("交换后数据: \(numb1) 和 \(numb2)")
 
var str1 = "A"
var str2 = "B"
 
print("交换前数据:  \(str1) 和 \(str2)")
swapTwoValues(&str1, &str2)
print("交换后数据: \(str1) 和 \(str2)")

这里的T就是“参数化类型”, 即泛型.
Swift的Array这种集合类型支持泛型:

image.png

但是在OC中默认是不可以这样的:

- (void)swapTwoValues<T>(T a, T b); // 编译器报错

这就需要用到协变和逆变

OC中 用于泛型的关键字 __covariant(协变) 和 __contravariant(逆变)

在OC中要想直接使用泛型声明成员变量, 需要额外使用关键字 __covariant(协变) 和 __contravariant(逆变, 有的叫裂变), 这两个单词翻译的很烂, 但是凑合着理解吧, 看下图示:

16.png

__covariant 示例

这两个关键字只是给编译器看的,比如以__covariant为例, __covariant 用于向上强转,即子类转成父类:
创建一个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; // 子转父 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; // 父类转子类, 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, 父类转子类没有警告
        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的方法, 但是由于参数中指定了泛型信息 NSLayoutAnchor<AnchorType>, 当在XCode中调用时, 提示如下:

image.png

这就约束了参数必须是NSLayoutAnchor<NSLayoutXAxisAnchor *> *, 为什么可以约束参数是这种? 我们点进去leftAnchor看一下:

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

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


image.png

它的好处就是: 在基类中写了一个方法, 每个子类在声明时都指定了这个方法中参数的泛型信息, 子类对参数进行限制, 从而当我们传入的参数不匹配时可以给出合适的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'

一个项目实例

再来看一个工作中的实际用法, 假设服务器返回的数据是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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容