美文网首页大刘的 iOS 自学笔记
OC的泛型和__covariant __contravarian

OC的泛型和__covariant __contravarian

作者: 大刘 | 来源:发表于2022-06-26 14:21 被阅读0次

    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
    

    相关文章

      网友评论

        本文标题:OC的泛型和__covariant __contravarian

        本文链接:https://www.haomeiwen.com/subject/tbqgvrtx.html