Nullability、__covariant、__contravariant,__kindof

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;
@end
NS_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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值