防止 Obj-C 代码将 `nil` 传递给具有非可选参数的 Swift 方法

Posted

技术标签:

【中文标题】防止 Obj-C 代码将 `nil` 传递给具有非可选参数的 Swift 方法【英文标题】:Prevent Obj-C code from passing `nil` to a Swift method with non-optional parameters 【发布时间】:2020-07-17 17:34:43 【问题描述】:

我正在处理 Obj-C 代码可以将 nil 传递给不采用可选参数的 Swift 方法的问题。

我正在为 Xcode 编译器寻找一种方法来防止这种情况发生。似乎所有可空性注释都是为了让 Swift 调用 Objc,而不是反之亦然。

一些示例代码:

// file1.swift
@objc
class Foo: NSObject 
  @objc let date: Date? // default to nil
  ...


// file2.swift
extension Date 
  @objc(doSomethingWithOtherDate:)
  func doSomething(with otherDate: Date) 
    print(date)
  

然后在Obj-c中,我们调用Swift方法传入一个可选值,nil:

// file3.m
[Date.new doSomethingWithOtherDate:[Foo.new date]];

有没有办法防止这种情况发生?我们如何告诉编译器doSomethingWithOtherDate 不接受nil

跟进

跟进此问题,在调用中传递文字 nil 会触发警告,这非常有帮助。但是,传递 nil 值不会触发任何事情。但是,如果 nil 引用是本地的,则静态分析器会发现问题。

但是,如果 nil 引用通过另一个对象传递,则静态分析器无法识别此问题。

我制作了这个 repo 来展示这个问题:https://github.com/eneko/ObjcCrashDemo

【问题讨论】:

【参考方案1】:

一般来说,不会。 Objective-C 对可空性没有足够的理解来检测每个案例。

如果您明确传递 nil,您应该会收到警告“Null 传递给需要非 null 参数的被调用者”。您可以通过将 -Werror=nonnull 添加到构建设置中的“其他警告标志”来将其变为错误。

但是,这是极其有限的。即使是像NSDate* date = nil; 这样简单的东西,通过它也不会产生警告/错误。即使在 Objective-C 中添加 nonnull 属性并传递它也不会产生警告/错误。

【讨论】:

感谢您的提示。我试过添加标志,但正如你提到的,在这种情况下它没有帮助。这是一个无赖。看来,如果没有办法阻止这种行为,最安全的方法是让所有带有 @objc 标记的 Swift 方法只有可选参数。远非理想:/【参考方案2】:

我找到了这篇文章:https://f***canas.com/blog/2020/1/9/swift-undefined-behavior.html(搜索“当 nil 不可能时检测 nil”部分)。本文讨论了该问题的一个相当复杂的解决方案,但我建议更简单的解决方案是将您的函数参数类型声明为Date!(值或零),然后在继续之前对传递的值执行guard let在函数体中:

// file2.swift
extension Date 
  @objc(doSomethingWithOtherDate:)
  func doSomething(with otherDate: Date!) 
    guard let od = otherDate else 
      // do something else
      return
    
    print(date)
  

【讨论】:

这种方法的危险在于,当您从 Swift 调用此代码时,您将失去 Swift 类型检查器的优势,当您意外传递 nil 时,可能会将编译时错误变成运行时错误 是的。为此,最好将参数设置为可选:Date?,这对于 Obj-C 和 Swift 都是安全的。但是,不能解决发布的问题,这是关于非可选参数的问题。 问题在于 Swift -> ObjC 之间的内在兼容性。换一种方式(在 Swift 中调用 ObjC 函数),可以将 ObjC 函数参数注释为非空,这在 Swift 中正确声明参数类型为非可选。根据文章,如果没有某种黑客解决方法,就无法解决这种情况。因此,您的选择是使用本文中的解决方法,或者通过将 swift 参数类型声明为 !或?。

以上是关于防止 Obj-C 代码将 `nil` 传递给具有非可选参数的 Swift 方法的主要内容,如果未能解决你的问题,请参考以下文章

Obj-C 框架返回 nil,并让我的 Swift 代码崩溃,说“致命错误:在展开可选值时意外发现 nil”

Obj-C 框架返回 nil,并让我的 Swift 代码崩溃,说“致命错误:在展开可选值时意外发现 nil”

将 nil 传递给 archivedDataWithRootObject 返回奇怪的 NSData

将字符串从 Obj-C 传递到 React Native

具有非原始类型的 C++ 值传递?

如何在 swift 中将 JSON `null` 值传递给`nil` 值?