【问题标题】:Call a method from a String in Swift在 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,Stackoverflow 社区鼓励 Q+A 风格的问题,他专门提出这个问题,以便为可能有同样疑问的人自己提供答案。

标签: swift


【解决方案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"))
}

【讨论】:

  • 是项目名,命名空间
猜你喜欢
  • 2014-07-23
  • 1970-01-01
  • 1970-01-01
  • 2010-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多