提问人:Pyrettt Pyrettt 提问时间:6/20/2023 最后编辑:Pyrettt Pyrettt 更新时间:7/3/2023 访问量:70
为什么目标 API 使用 Any 而不是 AnyObject?
Why do target APIs use Any instead of AnyObject?
问:
为什么 Objective-C API 喜欢或导入 Swift 时带有 的观察者类型 ,而不是 ?NotificationCenter.addObserver
UIButton.addTarget
Any
AnyObject
这有什么原因吗?是否有可能正确地将选择器指定给除类方法之外的东西?@objc
答:
原因之一是,您可以为对象使用协议类型,并且引用类型和值类型都可能符合该协议。
一个例子
protocol Observer {
func observe()
}
class LookOut: Observer {
@objc func observe() {}
}
这将起作用,因为参数的类型是Any
let observer: Observer = LookOut()
NotificationCenter.default.addObserver(observer,
selector: #selector(LookOut.observe),
name: .NSCalendarDayChanged,
object: nil)
但是使用这种方法它会失败,因为协议不符合AnyObject
func fakeObserver(_ object: AnyObject, selector: Selector) {}
所以
fakeObserver(observer, selector: #selector(LookOut.observe))
生成错误
参数类型“any Observer”应为类或类约束类型的实例
当然,像下面这样的值类型在任何一种情况下都不起作用,因为该函数不能被注释@objc
struct LookIn: Observer {
func observe() {}
}
其原因在 Objective-C id 中解释为 Swift Any。在 Swift 2 中,正如你所建议的那样。 参数被桥接为 AnyObject。在 Swift 3 中,它们被更改为 Any:id
这一变化使 Objective-C API 在 Swift 中更加灵活,因为 Swift 定义的值类型可以传递给 Objective-C API 并提取为 Swift 类型,从而消除了对手动“框”类型的需要。这些好处也延伸到集合:Objective-C 集合类型 NSArray、NSDictionary 和 NSSet,以前只接受 AnyObject 的元素,现在可以保存 Any 类型的元素。
SE-0116 Import Objective-C id as Swift Any type(SE-0116 导入 Objective-C ID
为 Swift Any
类型)中详细介绍了此更改。
这使得 Swift/ObjC 互操作比 Swift 2 中的互操作简单得多,但它确实创建了一些特定的情况,例如操作目标,在这些情况下,创建在运行时会失败的代码可能更容易一些。然而,在实践中,对参数的限制是更重要的保护。目标和选择器不匹配一直是 ObjC 中的一个风险,在这里允许 Any 实际上并没有让情况变得更糟。您仍然需要正确使用,如果您引用非@objc方法,它会抛出错误。selector
#selector
Joakim 也提出了一些很好的论点,尽管在这种情况下,我通常希望 Observer 需要 NSObjectProtocol。我怀疑这更多的是不值得为这些情况添加NS_REFINED_FOR_SWIFT宏。如果您发现由于缺乏 AnyObject 强制执行而发生实际错误,那么值得在 Swift 论坛上提出,作为可能的改进。
但是对于你的次要问题“是否有可能正确地将选择器指定给除类方法之外@objc东西”,答案是肯定的,如果你所说的“正确”是指“它会起作用”。
例如,你可以通过引用附加到 NSString 的方法将选择器发送到 String,即使它是一个结构:
let string: String = "This is a Swift string"
// This is an extension on **NSString**, not String.
extension NSString {
@objc func strangeButTrue() { print("Yes, this will print") }
}
Timer.scheduledTimer(timeInterval: 1,
target: string,
selector: #selector(NSString.strangeButTrue),
userInfo: nil,
repeats: false)
另请注意,这是编译时的便利。这也可以这样表示:#selector(NSString.strangeButTrue)
let nameOfSelectorDecidedAtRunTime = "strangeButTrue"
Timer.scheduledTimer(timeInterval: 1,
target: string,
selector: Selector(nameOfSelectorDecidedAtRunTime),
userInfo: nil,
repeats: false)
在运行时从字符串构造选择器在 ObjC 中很常见。我参与过的许多大型项目都至少有一个地方已经完成。能够以这种方式处理桥接对象确实使移植现有的 ObjC 代码变得更加容易,即使这不是你在 Swift 中真正应该做的事情。
评论