在 Swift 中,很少见,但最终可能会得到具有 nil 值的非可选类型的值。正如在回答这个问题时所解释的,这可能是由桥接到 Swift 的糟糕的 Objective-C 代码引起的:
- (NSObject * _Nonnull)someObject {
return nil;
}
或者通过糟糕的 Swift 代码:
class C {}
let x: C? = nil
let y: C = unsafeBitCast(x, to: C.self)
在实践中,我在 MessageUI
中使用 MFMailComposeViewController
API 遇到了这个问题。下面创建一个非可选MFMailComposeViewController
,但如果用户尚未在 Mail 中设置电子邮件帐户,则以下代码会崩溃并显示EXC_BAD_ACCESS
:
let mailComposeViewController = MFMailComposeViewController()
print("(mailComposeViewController)")
调试器如下所示显示mailComposeViewController
的值:
mailComposeViewController = (MFMailComposeViewController) 0x0000000000000000
我在这里有几个问题:
- 我注意到
unsafeBitCast(_:to:)
的文档说它"破坏了 Swift 类型系统的保证",但是 Swift 文档中是否有一个地方可以解释这些保证可以被打破,以及如何/何时被打破? - 检查这种情况的惯用方法是什么?编译器不会让我检查是否
mailComposeViewController == nil
,因为它不是可选的。
即使是 Apple 的 API 有时也会为在 API 中未标记为 Optional 的类型返回 nil
。解决方案是分配给可选。
例如,有一段时间traitCollectionDidChange
返回了一个 UITraitCollection,即使它实际上可能是nil
的。您无法检查它nil
,因为 Swift 不会让您检查非 Optional for nil
。
解决方法是立即将返回的值分配给UITraitCollection?
,并检查该值是否nil
。这种事情也应该适用于你的用例(尽管你的邮件示例不是一个用例,因为你从一开始就做错了)。
对于实际的 Swift 指针类型,可能有更好的方法可以做到这一点,但一种选择可能是只查看地址:
func isNull(_ obj: AnyObject) -> Bool {
let address = unsafeBitCast(obj, to: Int.self)
return address == 0x0
}
(Int
被记录为机器字大小。
我有同样的问题,但我使用了Apple文档中的建议。
从文档中:
在显示邮件撰写视图控制器之前,请始终调用 canSendMail() 方法,用于查看当前设备是否配置为 发送电子邮件。如果用户的设备未设置为交付 电子邮件,您可以通知用户或简单地禁用电子邮件调度 应用程序中的功能。您不应尝试使用此 接口,如果 canSendMail() 方法返回 false。
if !MFMailComposeViewController.canSendMail() {
print("Mail services are not available")
return
}
考虑使用unsafeBitCast(_:to:)
。
主要问题是,在这种方法中,我们将指针投射到 Swift 中的值,而无需验证该指针是否可以符合我们期望的类型,从我的角度来看,这是非常危险的,因为它会产生崩溃。
另一种方法是声明一个类似于下面的函数:
@inline(never) func isNil<T>(value: T?) -> Bool {
value == nil
}
如果没有@inline(never)
,您将在发布版本中面临风险,因为编译器可以选择内联函数体,然后看到value == nil
理论上永远不会为真,因此也删除了检查,使生产代码的行为就像您永远不会编写用于检查 nils 的代码一样。