可空性注释:Objective-C现代化(第1部分)

对于iOS开发人员而言,Objective-C现代化确实非常重要。 它提供了更好的类型安全性,并改善了与Swift代码库的交互。 有关更多信息,请查看我的第一篇文章:了解Objective-C现代化。

首先,我们将讨论可空性注释,它们为何重要以及它如何工作。

背景

与Java等运行时错误语言不同,Swift和Objective-C都具有编译时错误的优势。 即使这些错误有时会令人讨厌,但编译时错误通过帮助我们追踪那些绝对会导致应用程序在运行时崩溃的愚蠢错误,为我们提供了巨大的优势。

尽管Objective-C出现编译时错误,但在涉及类型安全之类的某些问题时,它往往比Swift更宽松。 一个这样的示例就是它如何处理缺少值的问题: null (对于ObjC指针引用,为nil )。

在Objective-C中,您可以使用nil指针做一些事情,因为Objective-C将nil视为0 。 如果将消息发送到nil指针,则会返回nil (或其他类似的“falsy”值)。

在Swift中, nil在功能上等效于其他语言,例如C ++中的null 。 如果您尝试在nil上调用函数,则会得到如下异常:

 致命错误:展开一个可选值时意外发现nil 

因此,确定您的价值是否可以为零非常重要。

Swift中现有的Objective-C API

Swift通过可选(可以为nil )和非可选(不能为nil )来实现这一点。 但是,直到几年前,Objective-C还无法表达这一概念。 因此,您的Objective-C代码:

  @property(非原子,副本,只读)NSString * email;-(BOOL)setUserEmail:(NSString *)email密码:(NSString *)password; 
-(BOOL)setUserImage:(UIImage *)图像;

将会翻译成Swift这样的东西:

  var email:字符串{get} 
func setUserEmail(_ email:字符串!,密码:字符串!)
函数setUserImage(_ image:UIImage!)

如果没有文档,此代码会造成混乱,因为所有这些函数都将nil作为有效的参数值,但尚不清楚nil是否应被接受。 另外,如果我们在Objective-C代码中传递nil,则不会出现Objective-C编译错误。 这可能不会导致崩溃,但是肯定是模棱两可的。

歧义很少是好的。 大规模的歧义是危险的。

随着Swift开始起飞,苹果公司开始使用自己的一些框架启用适当的Swift可选翻译.2015年3月,苹果发布了一篇有关Nullability和Objective-C的文章,向我们展示了我们如何也可以启用这种适当的翻译。

基本

核心是2个关键字: _Nullable_Nonnull 。 与C const关键字类似,您可以在代码库中的几乎任何位置使用这些关键字。 但是,它们真是丑陋的关键字,因此Apple还引入了方法声明关键字和属性关键字。

  @property(非原子,副本,只读,非空)NSString * email;--(BOOL)setUserEmail:(nonnull NSString *)电子邮件密码:(nonnull NSString *)password; 
-(BOOL)setUserImage :(可空UIImage *)image;

这段代码转换为Swift的方式就像是用纯Swift编写的一样:

  var email:字符串{get} 
func setUserEmail(_ email:字符串,密码:字符串)
函数setUserImage(_ image:UIImage?)

审核区域

尽管这一切都很好,但是连续添加所有这些关键字可能很繁琐,尤其是对于上面的示例,其中大多数情况都不为空。 因此,Apple还引入了NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END宏,它们的作用完全相同:

  NS_ASSUME_NONNULL_BEGIN @ property(非原子,副本,只读)NSString * email;-(BOOL)setUserEmail:(NSString *)电子邮件密码:(NSString *)password; 
-(BOOL)setUserImage :(可为空的UIImage *)image; NS_ASSUME_NONNULL_END

这段代码以完全相同的方式转换为Swift,而花费的精力很少:

  var email:字符串{get} 
func setUserEmail(_ email:字符串,密码:字符串)
func setUserImage(_ image:UIImage?)

在实现可空性注释的过程中,我遇到了一些麻烦。 这里有一些方便的提示:

块语法

如果您正在使用typedef Objective-C的f * cking块语法,则需要依赖_Nullable_Nonnull关键字:

  NS_ASSUME_NONNULL_BEGINtypedef void(^ ExampleBlock)(NSData * _Nullable响应,NSError * _Nullable错误); 
typedef id _Nullable(^ ExampleReturnBlock)(id object,BOOL * stop)NS_ASSUME_NONNULL_END

此代码等效于Swift:

  typealias ExampleBlock =(响应:数据?,错误:错误?)->无效 
typealias ExampleReturnBlock =(对象:任意,停止:Bool)->是否任意?

NSError **

始终假定NSError **是可为空的,但我们将在以后的文章中介绍此语法。

试错

如有疑问,我的建议是尽可能以最佳方式进行注释,然后尝试从Swift调用ObjC代码。 这可以帮助您确保代码看起来和感觉正确。

有关更多信息,请查看Nikita Lutsenko的Advanced ObjC Swift Interoperability。

我的下一篇文章将讨论Objective-C轻量级泛型的使用。 敬请关注。


如果您喜欢此内容,请确保在Medium和Twitter @codytwinton上关注我。 谢谢!