Nullability、__covariant、__contravariant,__kindof

来源:互联网 发布:淘宝实拍保护图片要求 编辑:程序博客网 时间:2024/06/03 21:37

1 Nullability

  • nonnull :
    • 不可为nil
  • nullable
    • 可以为nil
  • null_resettable
    • 表示 setter 可以为nil,但是 getter 不可以为nil
  • __nullable
    • 用来修饰一个变量,前面还要加双下划线
@property (nonatomic, strong, nonnull) Sark *sark;@property (nonatomic, copy,  nullable) NSArray *friends;+ (nullable NSString *)friendWithName:(nonnull NSString *)name; @property (null_resettable, nonatomic, strong) UIView *view; - (void)startWithCompletionBlock:(nullable void (^)(NSError * __nullable error))block;
  • Audited Regions
    • Foundation 提供了一对儿宏,包在里面的对象默认加 nonnull 修饰符,只需要把 nullable 的指出来就行
NS_ASSUME_NONNULL_BEGIN@interface Sark : NSObject@property (nonatomic, copy, nullable) NSString *workingCompany;@property (nonatomic, copy) NSArray *friends;- (nullable NSString *)gayFriend;@endNS_ASSUME_NONNULL_END

总结:

Nullability 在编译器层面提供了空值的类型检查,在类型不符时给出 warning,方便开发者第一时间发现潜在问题。不过我想更大的意义在于能够更加清楚的描述接口,是主调者和被调者间的一个协议,比多少句文档描述都来得清晰,打个比方:
+ (nullable instancetype)URLWithString:(NSString *)URLString;

NSURL 的这个 API 前面加了 nullable 后,更加显式的指出了这个接口可能因为 URLString 的格式错误而创建失败,使用时自然而然的就考虑到了判空处理。

2 Lightweight Generics

  • 带泛型的容器
    • 可以指定容器类中对象的类型了
NSArray<NSString *> *strings = @[@"sun", @"yuan"];NSDictionary<NSString *, NSNumber *> *mapping = @{@"a": @1, @"b": @2};
  • 自定义泛型类
    • 这个类型在 @interface 和 @end 区间的作用域有效,可以把它作为入参、出参、甚至内部 NSArray 属性的泛型类型,我们还可以给 ObjectType 增加类型限制。若什么都不加,表示接受任意类型 ( id );当类型不满足时编译器将产生 error。对于
//这个 ObjectType(其他字符也可以) 是传入类型的 placeholder,它只能在 @interface 上定义(类声明、类扩展、Category)@interface Stack<ObjectType> : NSObject//入参- (void)pushObject:(ObjectType)object;//出参- (ObjectType)popObject;//内部 NSArray 属性的泛型类型@property (nonatomic, readonly) NSArray<ObjectType> *allObjects;@end
  • 自定义泛型类带类型限制
    • 我们还可以给 ObjectType 增加类型限制。若什么都不加,表示接受任意类型 ( id );当类型不满足时编译器将产生 error。
// 只接受 NSNumber * 的泛型@interface Stack<ObjectType: NSNumber *> : NSObject// 只接受满足 NSCopying 协议的泛型@interface Stack<ObjectType: id<NSCopying>> : NSObject//多参数的泛型,用逗号隔开,其他都一样,可以参考 NSDictionary 的头文件

总结:

@interface Stack<ObjectType: id<NSCopying>,keyObject:id<NSCopying>,myObject:NSString *> : NSObject-(void)pushObject:(ObjectType)object;-(myObject)popObject;@property (nonatomic,readonly)NSArray<ObjectType>*allObjects;@end

3 协变性和逆变性

当类支持泛型后,它们的 Type 发生了变化,比如下面三个对象看上去都是 Stack,但实际上属于三个 Type:

Stack *stack; // Stack *Stack<NSString *> *stringStack; // Stack<NSString *>Stack<NSMutableString *> *mutableStringStack; // Stack<NSMutableString *>

当其中两种类型做类型转化时,编译器需要知道哪些转化是允许的,哪些是禁止的,比如,默认情况下:
这里写图片描述

我们可以看到:

  • 不指定泛型类型的 Stack 可以和任意泛型类型转化,
  • 但指定了泛型类型后,两个不同类型间是不可以强转的

假如你希望主动控制转化关系,就需要使用泛型的协变性和逆变性修饰符了:

  • __covariant
    • 协变性,子类型可以强转到父类型(里氏替换原则)
  • __contravariant
    • 逆变性,父类型可以强转到子类型(WTF?)

协变性:

@interface Stack<__covariant ObjectType> : NSObject

效果:
NSString = NSMutableString
这里写图片描述

逆变:

@interface Stack<__contravariant ObjectType> : NSObject

效果:

NSMutableString = NSString
这里写图片描述

4 __kindof

  • 明确表明了返回值,又让使用者不必写强转

没有__kindof之前:

- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;    MyCell *tCell = (MyCell*)[tableview dequeueReusableCellWithIdentifier:@"cellID"];

有__kindof之后:

- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier; MyCell*tCell = [tableview dequeueReusableCellWithIdentifier:@"cellID"];

Where to go

有了上面介绍的这些新特性以及如 instancetype 这样的历史更新,Objective-C 这门古老语言的类型检测和类型推断终于有所长进,现在不论是接口还是代码中的 id 类型都越来越少,更多潜在的类型错误可以被编译器的静态检查发现。

同时,个人感觉新版的 Xcode 对继承链构造器的检测也加强了,NS_DESIGNATED_INITIALIZER 这个宏并不是新面孔,可以使用它标志出像 Swift 一样的指定构造器和便捷构造器

最后,附上一段用上了所有新特性的代码,Swift 是发展趋势,如果你暂时依然要写 Objective-C 代码,把所有新特性都用上,或许能让你到新语言的迁移更无痛一点。
这里写图片描述

原文:2015 Objective-C 新特性

0 0
原创粉丝点击