之所以会有这篇文章, 是因为最近Swift5出来后, 公司项目有意向往混编的方向走, 而纯Objc的老项目嘛...emmmm, 直接用的话转成Swift后真的一言难尽, 所以为了让公司其他人更好的改写用到的头文件, 就整理了一下难用的Nullability到底怎么用会比较方便, 顺便看了一下互相转换的宏哪个还用得上
开始改造
首先是Nullability, 先简单粗暴的用:
NS_ASSUME_NONNULL_BEGIN
NS_ASSUME_NONNULL_END
把整个头文件包起来(import那几行不需要, 一般不包这几行)
这样整个头文件所有类型都默认是非optional的
然后再把需要变成可选的单独加上Nullability关键字即可:
而考虑到各种问题, 比如官方一般用nullable
, 而有些地方只能用__nullable
, 所以最简单总结起来就是:
- 无脑类型后缀
__nullable
就好(block是在^后缀) -
_Nullable
就不要碰了, 混在一起容易乱 - 至于property里跟不跟官方用
nullable
? 推荐写成CodeSnippet自动生成然后不要碰了(其实也就strong和copy需要nullable):
@property (<#nullable, #>nonatomic, strong) <#Class#> *<#name#>;
@property (<#nullable, #>nonatomic, copy) NSString *<#name#>;
或者跟着官方的做法:
- 一般情况下无脑前缀
nullable
- 遇到block相关的就类型后缀
__nullable
加以区分,也比较好记(block是在^后缀) -
_Nullable
就不要碰了
不管选哪个, 重点其实是整个项目保持一致性才是最重要的
精致分割线
如果上面的总结不能帮到你, 具体解释就是:
-
__nullable
/_Nullable
是编译器参数,需要放到类型后面,也就是NSString *__nullable
这样
ps: OC里泛型是不能__nullable
的, 我一时没想通傻试了好久, emmm...反应过来的时候差点笑死
-
nullable
是属性, 可以和strong
/readonly
一样放到property的括号里, 或者作为参数时和__weak
一样前缀到变量类型前面:
para:(nullable NSString *)name
根据上面的规则就能衍变出:
property有两种写法
为了方便说明, copy/readonly这些称为property的属性
@property (copy) NSString *__nullable name;
@property (nullable, copy) NSString * name; // 本质还是前缀, 但property的属性需要写到括号里, 虽然这是官方写法, 但为了不要搞混最好不要记这个, 属性用CodeSnippet生成就好
ps1: property还有一种nullable
属性null_resettable
字面意思就是setter可以传空, getter不能返回为空,编译器改写成Swift时会用!来表示, 如UIViewController.view就是null_resettable的:
@property(null_resettable, nonatomic,strong) UIView *view;//这里复制过来就这样的,苹果少打了一个空格
ps2: weak不能用nonnull
方法的返回值和参数也各自有两种写法:
- (NSString *__nullable)nameForItem:(NSString *__nullable)item;
- (nullable NSString *)nameForItem:(nullable NSString *)item;// 官方也是这种写法, 还是那句话, 不要记这个
最麻烦的是block
block作为property
它本身是不是optional需要在^后缀__nullable
, 或者跟上面的property一样写成属性到括号里
@property (copy) void (^__nullable aBlock)();
@property (nullable, copy) void (^ aBlock)();
block做参数也是两种写法:
它本身是不是optioanl可以在^后缀__nullable
, 前缀nullable
, 但返回值和参数只能后缀__nullable
- (void)needABlock:(id __nullable (^__nullable)(id __nullable para))aBlock;
- (void)needABlock:(nullable id __nullable (^)(id __nullable para))aBlock;
返回值前缀nullable
会冲突这个很容易理解了, 所以反过来想, 大概是为了和返回值保持一致, 所以参数也只能后缀__nullable
了吧...
ps: 如果nulable的block是最后一个参数, Swift会自动转换成带默认值nil
open func needABlock(_ aBlock: ((Any?) -> Void)? = nil)
而普通类型的nullable变量则不会
最后是block的typedef
基本规则跟做参数是一样的, 但是定义这个type是不是optional跟做参数不同, 只能在^后缀__nullable
(所以无脑类型后缀__nullable
就好了):
typedef id __nullable (^__nullable ABlock)(id __nullable para);
还有一种 null_unspecified
代表不确定是不是为空, 这个一般用不上, 总之大概跟nullable
的用法差不多, 同样有编译器参数__null_unspecified
/_Null_unspecified
当既没有用ASSUME_NONNULL把头文件包起来, 也没有逐个添加Nullability时, 编译器就会默认用这个作为变量的Nullability
如果真的不能确定到底会不会为空(以后可能会为空), 可以用这个, 编译器改写成Swift时会用!来表示(和null_resettable
一样, 区别是OC里的警告不同), 如:
@property (null_unspecified) id name;
会被改写成:
open var name: Any!
接着还有一个用于命名的关键字是
NS_SWIFT_NAME(<#swift专用名#>)
可以用于任意内容, 包括类名, 属性名, 枚举:
NS_SWIFT_NAME(VoiceFilter)
@interface ABVoiceFilter : NSObject
@end
typedef NS_ENUM(NSUInteger, AType) {
ATypeNone NS_SWIFT_NAME(NoneOne),
ATypeOther NS_SWIFT_NAME(OtherPeople) ,
};
- (void)handleConnectItem:(id)connectionItem withParser:(id)parser NS_SWIFT_NAME(handle(item:parser:));