如何在 Swift 中的 AnyClass 上使用 performSelector() 调用类方法?

Posted

技术标签:

【中文标题】如何在 Swift 中的 AnyClass 上使用 performSelector() 调用类方法?【英文标题】:How to invoke a class method using performSelector() on AnyClass in Swift? 【发布时间】:2016-03-24 19:07:18 【问题描述】:

在 ObjC 中,您可以简单地使用来自 NSObject 的类方法调用一个类方法。

[Machine performSelector:@selector(calculate:) withObject:num];

但是如何在 Swift 2.2 中做到这一点?

@objc(Machine) // put it here, so you can simply copy/paste into Playground
class Machine: NSObject 
    static func calculate(param: NSNumber) -> String 
        if param.integerValue > 5 
            return "42"
        
        return "42" // there is only 1 answer to all the questions :D
    


if let aClass = NSClassFromString("Machine") 
    let sel = #selector(Machine.calculate(_:))
    let num = NSNumber(integer: 1337)
    let answer = aClass.performSelector(sel, withObject: num) // compiler error
    // let answer = aClass.calculate(num)                     // <-- this works
    print(answer)

使用此代码,我收到以下编译器错误:

错误:无法使用类型为“(Selector, withObject: NSNumber)”的参数列表调用“performSelector”

我在这里错过了什么?

【问题讨论】:

这绝对不是没有意义的,如果你想使用外部源的类怎么办?例如。从 plist 中的字符串中读取类名,是否要根据每个可能的对象编写 if 条件? 相关:Access Private UIKit Function Without Using Bridging Header 【参考方案1】:

AnyClass 不符合 NSObjectProtocol 开箱即用。我必须将aClass 转换为NSObjectProtocol 才能使用performSelectorperformSelector:withObject: 作为NSObjectProtocol 上的方法桥接到Swift):

斯威夫特 3:

if let aClass = NSClassFromString("Machine") 
    let sel = #selector(Machine.calculate(param:))
    let num = NSNumber(value: 1337)

    if let myClass = aClass as? NSObjectProtocol 
        if myClass.responds(to: sel) 
            let answer = myClass.perform(sel, with: num).takeRetainedValue() // this returns AnyObject, you may want to downcast to your desired type
            print(answer) // "42\n"
        
    

Swift 2.x:

(aClass as! NSObjectProtocol).performSelector(sel, withObject: num) // Unmanaged<AnyObject>(_value: 42) 

更安全一点:

if let aClass = NSClassFromString("Machine") 
    let sel = #selector(Machine.calculate(_:))
    let num = NSNumber(integer: 1337)

    if let myClass = aClass as? NSObjectProtocol 
        if myClass.respondsToSelector(sel) 
            let answer = myClass.performSelector(sel, withObject: num).takeUnretainedValue()
            print(answer) // "42\n"
        
    

performSelector 返回一个Unmanaged 对象,这就是为什么需要takeUnretainedValue()(或者如果您想转移内存所有权,也可以选择takeRetainedValue())。

【讨论】:

这很有趣,因为将其转换为? AnyObject 为我工作 @Knight0fDragon Casting to AnyObject 会起作用,因为AnyClass 实际上是元类型AnyObject.Type 的类型别名。你可能会收到一个警告,因为它们是同一类型,所以抛弃成功。不过,将类强制转换为对象可能不是一个好主意。 @Knight0fDragon 你只能在 Objective-C 类型上使用 performSelector,而不是纯 Swift 类型。尝试一下。这就是我检查NSObjectProtocol 一致性的原因。 "你只能在 Objective-C 类型上使用 performSelector,而不是纯 Swift 类型。"但实际上,在运行时,纯 Swift 类型 do 支持 .respondsToSelector().performSelector() 等。因此转换为 AnyObject(以克服在编译时缺乏对此类支持的了解)并使用他们确实工作,不需要类型符合NSObjectProtocol 奇怪的是,Swift 4 会发出警告,试图将 AnyClass 转换为 NSObjectProtocol,这让我很恼火。在运行时它仍然有效,但试图保持警告项目的干净。有人有想法吗?【参考方案2】:

如果您想在机器上执行计算,只需:

Machine.calculate(NSNumber(integer: 1337))

如果你需要调用执行选择器,那么做:

if let aClass = NSClassFromString("Machine") as? AnyObject

    let sel = #selector(Machine.calculate(_:))
    let num = NSNumber(integer: 1337)
    let answer = aClass.performSelector(sel, withObject: num)
    print(answer)

【讨论】:

以上是关于如何在 Swift 中的 AnyClass 上使用 performSelector() 调用类方法?的主要内容,如果未能解决你的问题,请参考以下文章

Swift 元类型selfSelfAnyObjectAny和AnyClass

Swift 元类型selfSelfAnyObjectAny和AnyClass

Swift基础-AnyObject&Any&AnyClass

Swift 2.1 中的 NSClassFromString

AnyClass 是 NSObjectProtocol... 有时?

Swift 3.0中结构的枚举