为啥 Objective-C API 会返回隐式展开的可选项?

Posted

技术标签:

【中文标题】为啥 Objective-C API 会返回隐式展开的可选项?【英文标题】:Why do Objective-C APIs return implicitly unwrapped optionals?为什么 Objective-C API 会返回隐式展开的可选项? 【发布时间】:2014-07-14 00:48:53 【问题描述】:

我对此感到很困惑。如果我们以UITableView中的cellForRowAtIndexPath:方法为例,它的方法签名是:

func cellForRowAtIndexPath(_ indexPath: NSIndexPath!) -> UITableViewCell!

而它的返回值为:

表示表格单元格的对象,如果单元格不可见或 indexPath 超出范围,则为 nil。

这听起来是使用标准可选项的完美理由。事实上,由于在 Objective-C 中所有基于指针的类型都可以是 nil...所有 Objective-C 指针类型都应该作为标准选项导入似乎是有道理的。

我从 WWDC 的谈话中知道,他们说对于隐式展开的选项:

可以显式测试 nil 可以直接访问底层值的属性/方法 可以隐式转换为其基础值

来自苹果的Using Swift with Cocoa and Objective-C:

当您访问这种可选类型中的值而没有先安全地解包它时,隐式解包的可选项会检查该值是否丢失。如果缺少该值,则会发生运行时错误。

因此,他们决定将其导入为声明 this 永远不应该为 nil 的东西,而不是将可能的 nil 值作为可选值导入 Swift ......但可能吗?听起来他们这样做完全否定了 Swift 中用于 Objective-C API 的可选类型的安全性。我似乎缺少什么?

他们认为运行时错误更好,而不是给出编译时错误或警告?这非常令人困惑。

考虑到似乎没有任何东西可以回答我所看到的这个问题...我认为这对其他人来说是显而易见的,我只是没有看到但是...为什么会这样?

真的只是为了让人们在 Swift 中使用 Objective-C API 时免于使用 if let 或可选链接,还是其他方式?

【问题讨论】:

【参考方案1】:

当你在 Swift 中创建一个隐式解包的可选项时,这并不意味着它总是非 nil:这意味着你告诉编译器当你访问它们的属性时,你期望对象是非nil。您引用的对象可以显式检查nil;将其设置为 nil 也不会导致异常,除非您在此之后尝试访问其任何属性。

当 Apple 对参数使用隐式解包选项时

func tableView(_ tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!

功能,它们让您节省了一些额外的if - let。在这种情况下,他们知道他们永远不会向您传递nil;在其他情况下,他们不知道,他们希望您nil-检查对象。

它们也允许您返回nil。由他们来检查nil 的结果,当然,除非您决定自己调用该函数。虽然我想不出从您自己的代码中调用cellForRowAtIndexPath 的正当理由,但如果您确实进行了调用,您有责任检查nil 的返回值。

如果您考虑将参数设置为UITableView?NSIndexPath? 的替代方法,则所有实现都必须在tableViewindexPath 之后使用感叹号,或者使用if - let成语。与此选择相比,隐式展开类型看起来是更好的选择。

【讨论】:

但是函数的返回值不能是 UITableViewCell 吗? ?为什么不让它成为一个普通的可选而不是一个隐式展开的呢? @NickH 是的,这也可以,但是对于使用他们的 API 的程序员来说,它会变得不那么方便。当UITableView 回调数据源时,您可以放心地假设这两个参数都不是nil。它们是可选的唯一原因是 Objective-C 的互操作性:在原生 Swift API 中,这两个参数可能都是非可选的。 “他们实际上向您承诺,他们传递给您的 tableView 和 indexPath 都不会是 nil” 不。没有这样的“承诺”。 Objective-C API 中的所有 对象指针类型自动 成为隐式解包的可选项;不涉及情报。在某些情况下,其中许多 API 可以(实际上应该)返回 nil @newacct 因此我感到困惑,这似乎是一个有人想要使用普通可选的地方。在使用 Objective-C API 时,隐式可选只是让编写 Swift 更容易,更像是 Objective-C,但它似乎否定了普通可选的安全性。 @NickH:这是绝对安全和便利之间的权衡。因为它可能是nil,所以它必须是可选的。当您确定它不是nil(如果您错了,它会引发错误)时,隐式展开使您更方便,就像您在任何地方显式展开它一样。如果您不确定它不是nil,您仍然可以使用可选绑定和可选链接。所以基本上你程序员可以选择合适的方式来使用它。这样,Apple 就不必在整个 API 中经过并明确注释可空/不可空。【参考方案2】:

以下是lists.swift.org 上swift-users 中的Greg Parker's answer:

作为隐式解包的可选导入是一种可用性 妥协。大多数 Objective-C 指针实际上从来都不是 nil。如果指针为nil,并且作者没有检查,那么流程 故意停下来。这并不比你得到的行为更糟​​糕 编写 Objective-C 代码。 IUO 进口旨在作为权宜之计。我n 从长远来看,每个 Objective-C 接口都应该被显式注释,以便 Swift 可以更精确地导入它们。您可以在自己的代码中使用 NS_ASSUME_NONNULL_BEGIN/END 在你的头文件中。每个未注释的 这些标记内的对象指针是非空的。

【讨论】:

以上是关于为啥 Objective-C API 会返回隐式展开的可选项?的主要内容,如果未能解决你的问题,请参考以下文章

为啥隐式符号到字符串的转换会导致 JavaScript 中的 TypeError?

为啥 twitter 的 API 会返回垃圾(有时)?

为啥 Chrome 的 PushManager 会返回旧版 API?

为啥 GCC 会警告这种隐式转换?

为啥析构函数会禁用隐式移动方法的生成?

ARC 不允许将 Objective-C 指针隐式转换为“void *”