Nullability and Objective-C
https://developer.apple.com/swift/blog/?id=25
http://www.cocoachina.com/ios/20150601/11989.html
http://blog.csdn.net/zhangao0086/article/details/44409913
http://blog.sunnyxx.com/tags/iOS9/
http://blog.csdn.net/colorapp/article/details/49391209
Nullability
//////////////////////////////////////////////////////////////////////////////////////////////
总的来说,可空特性标示符有三种,可以用双下划线(用在任何指针类型),或者没有下划线的(用在Objective-C属性,方法结果类型或者方法参数类型)。
Type qualifier spelling | Objective-C property/method spelling | Swift view | Meaning |
__nonnull | nonnull | Non-optional, 如: UINib | 该值永远不会为nil(有一种例外是可能参数传递时传入的消息为空) |
__nullable | nullable | Optional, 如:UITableViewCell? | 该值可能为nil |
__null_unspecified | null_unspecified | 隐式解封可选类型如, NSDate! | 不确定该值是否为空(很少见) |
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
NS_ASSUME_NONNULL_BEGIN @interface AAPLList : NSObject <NSCoding, NSCopying> //--- - (nullable AAPLListItem *)itemWithName:(NSString *)name; - (NSInteger)indexOfItem:(AAPLListItem *)item; @property (copy, nullable) NSString *name; @property (copy, readonly) NSArray *allItems; //--- @end NS_ASSUME_NONNULL_END // -------------- self.list.name = nil; // okay AAPLListItem *matchingItem = [self.list itemWithName:nil]; // warning在C指针中使用 Nullability 的话,与OC中不同的地方在于,使用的nullability qualifier需要在前面添加双下划线,并且要将nullability qualifier写在指针后面。例如下面:
为了安全起见,这个规则也有一些例外情况:
typedef
定义的类型不会继承nullability
特性—它们会轻松地根据上下文选择nullable或non-nullable,所以,就算是在审查区域内,typedef
定义的类型也不会被当作nonnull
。- 像
id *
这样更复杂的指针类型必须被显式地注解,比如,你要指定一个nonnull的指针为一个nullable的对象引用,那么需要使用__nullable id * __nonnull
。 - 像
NSError **
这些特殊的、通过方法参数返回错误对象的类型,将总是被当作是一个nullable的指针指向一个nullable的指针:__nullable NSError ** __nullable
。
你可以通过Error Handling Programming Guide了解更多详细内容。
兼容性
你的Objective-C框架现有的代码写对了吗?是否能安全的改变它们的类型? Yes, it is.
- 现有的、被编译过的代码还能继续使用你的框架,也就是说ABI没有变化(编译器不会报错),这也意味着现有的代码不会在运行时捕获到
nil
的不正确传值。 - 用新的Swift编译器编译现有的源码,并在使用你的框架的时候,可能会因为一些不安全的行为在编译时得到额外的警告。
nonnull
不影响优化,尤其是你还可以在运行时检查标记为nonnull
的参数是否为nil
,这可能需要必要的向后兼容。
Lightweight Generics
NSArray<NSString *> *strings = @[@"sun", @"yuan"]; NSDictionary<NSString *, NSNumber *> *mapping = @{@"a": @1, @"b": @2};
自定义类中的使用语法:
__kindof
在OC中,我们的代码中会大量使用id这个特性,这个特性用起来会带来很多很方便的特性,但是它有个缺陷,我们经常需要进行强制类型转换。Xcode 7中有个新特性,__kindof,“Kindof” types express “some kind of X”,用__kind修饰的变量表示是某个类或者这个类的子类。
__kindof 这修饰符还是很实用的,解决了一个长期以来的小痛点,拿原来的 UITableView 的这个方法来说:
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
使用时前面基本会使用 UITableViewCell 子类型的指针来接收返回值,所以这个 API 为了让开发者不必每次都蛋疼的写显式强转,把返回值定义成了 id 类型,而这个 API 实际上的意思是返回一个 UITableViewCell 或 UITableViewCell 子类的实例,于是新的 __kindof 关键字解决了这个问题:
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
既明确表明了返回值,又让使用者不必写强转。再举个带泛型的例子,将Kindof types和lightweight generics结合在一起,UIView 的 subviews 属性被修改成了:
@property (nonatomic, readonly, copy) NSArray<__kindof UIView *> *subviews;
这样,写下面的代码时就没有任何警告了:
UIButton *button = view.subviews.lastObject;
协变性和逆变性
当类支持泛型后,它们的 Type 发生了变化,比如下面三个对象看上去都是 Stack,但实际上属于三个 Type:
1 2 3 | Stack *stack; // Stack * Stack<NSString *> *stringStack; // Stack<NSString *> Stack<NSMutableString *> *mutableStringStack; // Stack<NSMutableString *> |
当其中两种类型做类型转化时,编译器需要知道哪些转化是允许的,哪些是禁止的,比如,默认情况下:
我们可以看到,不指定泛型类型的 Stack 可以和任意泛型类型转化,但指定了泛型类型后,两个不同类型间是不可以强转的,假如你希望主动控制转化关系,就需要使用泛型的协变性和逆变性修饰符了:
__covariant
- 协变性,子类型可以强转到父类型(里氏替换原则)__contravariant
- 逆变性,父类型可以强转到子类型(WTF?)
协变:
1 | @interface Stack<__covariant ObjectType> : NSObject |
效果:
逆变:
1 | @interface Stack<__contravariant ObjectType> : NSObject |
效果:
协变是非常好理解的,像 NSArray 的泛型就用了协变的修饰符,而逆变我还没有想到有什么实际的使用场景。
关于id类型
- 在返回 “self” 的方法中,使用instancetype来代替id
- 大多数 Collections 都可以变成 Typed Collections 来代替id
- __kindof X * 来表示 “some subclass of X”,而不再使用id,可以减少类型强制转换之类的代码
- id<SomeProtocol> 表示conforms to SomeProtocol的任意类型