我们可以确保 `+ (nonnull instancetype)sharedInstance;` 的可空性吗?

问题描述 投票:0回答:1

这是一个关于如何在

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

objective-c nullable init non-nullable objective-c-nullability
1个回答
9
投票
这里似乎有两个问题:

  1. 如何确保在我的单例

    sharedInstance

    方法中生成并由该方法返回的值在运行时实际上不为零?

  2. 我如何满足为空注释、编译器警告和 Swift 桥接系统的要求,返回一个

    nonnull

     指针?

确保/执行

nonnull

在某些时候,每个 API 合约都会分解为人类合约。编译器可以帮助确保,例如,您不能获取

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
 特殊类型,编译器对可空性转换更加宽容,并且可能不会一开始就发出警告。

© www.soinside.com 2019 - 2024. All rights reserved.