这是一个关于如何在
init
类中优雅地规避 NSObject
为空的问题。
这是一个经典的 Objective-C 实现:
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
static id sharedInstance;
dispatch_once(&onceToken, ^
{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
但现在我想将其声明为
nonnull
,如果可能的话:
+ (nonnull instancetype)sharedInstance;
不幸的是,
init
返回一个nullable instancetype
值。我应该在调用 NSAssert
之后添加 init
还是什么?
我注意到有些人甚至将nonnull
的价值观记录为现实
nullable
。这有道理吗?
我是否应该大胆地在各处添加 NS_ASSUME_NONNULL_BEGIN
,而不真正确保值是
nonnull
?
sharedInstance
方法中生成并由该方法返回的值在运行时实际上不为零?
nonnull
指针?
nonnull
nullable
调用的结果并从返回类型为
nonnull
的方法返回它......但在某个地方通常有一个原始调用,其返回类型为
nonnull
只是因为写它的程序员说,“我保证永远不会返回 null,交叉我的心等等。”如果您熟悉 Swift,这与隐式解包选项的情况类似——当您“知道”某个值不能为 nil,但无法向编译器证明该知识,因为该知识是外部的,则可以使用这些选项到源代码(例如,来自情节提要或捆绑资源的内容)。
这就是这里的情况 - 你“知道”
init
永远不会返回 nil,要么是因为你编写/有相关初始化程序的源代码,要么因为它只是
NSObject
的
init
记录在
return self
不做任何事情。调用该初始化程序失败的唯一情况是因为前面的
alloc
调用失败(因此您在
nil
上调用方法,该方法始终返回
nil
)。如果
alloc
返回 nil,那么你已经陷入了困境 并且你的流程对于这个世界来说并不长——这不是设计 API 的失败案例。 (可空性注释通常用于描述 API 的预期用途,而不是更极端的边缘和角落情况。如果 API 调用仅由于
通用错误而失败,则将其注释为可为空是没有意义的;同样,如果API 仅在可通过非空参数注释排除的输入上失败,返回值可假定为非空。)
所以,长话短说:是的,只需将NS_ASSUME_NONNULL
放在标题周围,然后按原样交付您的
sharedInstance
实现即可。返回一个
nonnull
可空
nullable
的值,但您知道(或“知道”)它永远不会为零,并且希望从您的
nonnull
注释方法返回它。并且您处于这样的情况:尝试时会收到编译器警告。有一个语法 - 只需将值转换为预期的返回类型、注释和所有内容:
return (NSWhatever *_Nonnull)whatever;
在您的情况下,这不需要 - 因为您正在处理 id
和
instancetype
特殊类型,编译器对可空性转换更加宽容,并且可能不会一开始就发出警告。