在 Swift 中从字符串调用方法

Posted

技术标签:

【中文标题】在 Swift 中从字符串调用方法【英文标题】:Call a method from a String in Swift 【发布时间】:2014-06-16 13:46:15 【问题描述】:

从方法的名称(以字符串格式)调用方法有时很有用。 在 Swift 中,建议更改行为并使用闭包“动态地”执行某些操作,例如,您可以拥有一个函数字典,名称为键,实现为值。 但是,有时您想简单地知道“如何做”,这就是这个问题的原因。 那么,如何动态调用一个 Swift 方法,从它的名字作为字符串开始呢?

在 Objective C 中很简单:

[self performSelector:NSSelectorFromString(@"aSelector")];

但是performSelector 在 Swift 中被禁止 有没有其他选择?

【问题讨论】:

只是想知道您为什么删除了答案?问答没有错 Swift 主要是一种静态语言,具有强类型,没有真正的反射。这种动态行为是不安全的,因此在 Swift 中应该避免。 Swift 目前不以任何方式支持它。此外,它不适用于 ARC(即使在 Obj-C 中也会触发警告)。你可以在 Swift 中使用 Obj-C 解决方案,但没有真正的 Swift 解决方案,因为 Swift 不喜欢这种东西。 @holex ,我已经阅读了所有文档并详细分析了 Swift ;-) 这就是我写这个问答的原因(你看到了吗?下面有我的答案)到目前为止,任何其他网站都没有该解决方案。 我知道!!您是否了解发布问题只是为了以问答方式附上答案?此外,为了确认您在说什么,我的问题从以下声明开始:“在 Swift 中,建议改变行为”。但问答的意义不同:了解它是如何工作的为什么 @holex,*** 社区鼓励 Q+A 风格的问题,他专门提出这个问题,以便为可能有同样疑问的人自己提供答案。 【参考方案1】:

在 Swift 中,您应该使用闭包并改变您的方法。 但是,如果您想使用performSelector 动态调用仅给出字符串签名的方法,尽管它本身不支持,但我已经找到了方法。

可以为 performSelector 创建一个 C 替代方案:

甚至适用于原生 swift 类(非 Objective-c) 从字符串中获取选择器

但是实现它的完整版本并不是那么简单,并且需要在 C 中创建方法。

在 C 语言中,我们有 dlsym(),一个函数,它返回一个指向给定 char 符号的函数的指针。 好吧,阅读这篇有趣的帖子: http://www.eswick.com/2014/06/inside-swift/ 我学到了很多关于 swift 的有趣的东西。

Swift 实例方法是具有特定签名的普通函数,像这样

_TFC14FirstSwiftTest12ASampleClass13aTestFunctionfS0_FT_CSo8NSString

“self”值作为最后一个参数传递

简而言之,您可以直接从 c 端调用它而无需任何类型的桥接,重建正确的函数签名就足够了。 在上面的签名中,有项目的名称(FirstSwiftTest)和长度(14),类的名称(ASampleClass)和长度(12),函数的名称(aTestFunction)和长度(13 ),然后其他值作为返回类型 ecc ecc。其他详情请看上一个链接

上面的函数,就是这个的表示:

class ASampleClass

    func aTestFunction() -> NSString
    
        println("called correctly")
        return NSString(string: "test")
    

嗯,在 c 端,我能够创建这个函数

#include <stdio.h>
#include <dlfcn.h>

typedef struct objc_object *id;

id _performMethod(id stringMethod, id onObject)

    // ...
    // here the code (to be created) to translate stringMethod in _TFC14FirstSwiftTest12ASampleClass13aTestFunctionfS0_FT_CSo8NSString
    // ...

    id (*functionImplementation)(id);
    *(void **) (&functionImplementation) = dlsym(RTLD_DEFAULT, "_TFC14FirstSwiftTest12ASampleClass13aTestFunctionfS0_FT_CSo8NSString");

    char *error;

    if ((error = dlerror()) != NULL)  
        printf("Method not found \n");
     else 
        return functionImplementation(onObject); // <--- call the function
    
    return NULL

然后在 swift 端调用它

    let sampleClassInstance = ASampleClass()

    println(_performMethod("aTestFunction", sampleClassInstance))

函数导致这些语句打印在日志上:

正确调用测试

所以在 C 中创建一个 _performMethod() 替代方案应该不难:

自动创建函数签名(因为它似乎有一个逻辑:-) 管理不同的返回值类型和参数

编辑

在 Swift 2 中(也许在 Beta3 中,我没有尝试)似乎 performSelector() 是允许的(并且您只能在 NSObject 子类上调用它)。检查二进制文件,似乎现在 Swift 创建了可以由 performSelector 专门调用的静态函数。

我创建了这个类

class TestClass: NSObject 
    func test() -> Void 
        print("Hello");
    


let test = TestClass()
let aSel : Selector = NSSelectorFromString("test")
test.performSelector(aSel)

现在我在二进制文件中找到了

000000010026d830 t __TToFC7Perform9TestClass4testfT_T_

目前我还不太了解这背后的原因,但我会进一步调查

【讨论】:

可能想澄清该类必须继承自 NSObject 才能获得可用的 performSelector 方法。【参考方案2】:

您可以通过这种方式从字符串中调用方法:

let foo = <some NSObject subclass instance>
let selectorName = "name"
foo.perform(Selector(selectorName))

仅当您的 foo 类是 NSObject 的子类时才可用

【讨论】:

是的,现在可以了。在回答时, performSelector 被“禁止”。似乎现在 Swift 为每个可能的 performSelector 创建了一个符号(一个静态函数)。我更新我的答案 perform 不是关键字。这是正确的还是我误解了什么? performNSObject (Swift 3) developer.apple.com/reference/objectivec/nsobject/… 的方法【参考方案3】:

swift3 版本

class MyClass:NSObject

    required public  override init()  print("Hi!") 
    
    public func test()
        print("This is Test")
    
    public class func static_test()
        print("This is Static Test")
    



if let c: NSObject.Type = NSClassFromString("TestPerformSelector.MyClass") as? NSObject.Type
   let c_tmp = c.init()
   c_tmp.perform(Selector("test"))
   c.perform(Selector("static_test"))

【讨论】:

是项目名,命名空间

以上是关于在 Swift 中从字符串调用方法的主要内容,如果未能解决你的问题,请参考以下文章

如何在swift中从.js文件中调用javascript函数

如果我只有字符串,如何在 Swift 中调用类方法?

如果我只有字符串,如何在 Swift 中调用类方法?

是否可以在 Spring 中从非事务方法调用事务方法?

在 JavaScript 中从父级调用子方法

在 .NET 中从 API 调用 Java 方法