Call a method from a String in Swift

前端 未结 3 862
星月不相逢
星月不相逢 2020-11-27 16:49

Calling a method from its name (in a String format) can be sometimes useful.
In Swift it is recomended to change behavior and to use closures to do something \"dynamical

相关标签:
3条回答
  • 2020-11-27 17:25

    In Swift, you should use closures and change your approach. However, if you want to use performSelector to dynamically call a method given only it's String signature, altough it's not supported natively, I've found how to do it.

    It is possible to create a C alternative to performSelector that:

    • works even on native swift classes (non objective-c)
    • takes a selector from string

    However it's not so straightforward to implement a complete version of it, and it's necessary to create the method in C.

    in C we have dlsym(), a function that returns a pointer to a function given the char symbol. Well, reading this interesting post: http://www.eswick.com/2014/06/inside-swift/ I've learned a lot of interesting things about swift.

    Swift instance methods are plain functions with a specific signature, like this

    _TFC14FirstSwiftTest12ASampleClass13aTestFunctionfS0_FT_CSo8NSString
    

    where the "self" value is passed as the last parameter

    in short you can call it directly from the c side without any kind of bridging, it is sufficient to rebuild the correct function signature. In the signature above, there is the name of the project (FirstSwiftTest) and the lenght (14), the name of the class (ASampleClass) and the lenght (12), the name of the function (aTestFunction) and the lenght (13), then other values as the return type ecc ecc. For other details look at the previous link

    The function above, is the representation of this:

    class ASampleClass
    {
        func aTestFunction() -> NSString
        {
            println("called correctly")
            return NSString(string: "test")
        }
    

    }

    Well, on the c side, I was able to create this function

    #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
    }
    

    And then called it on the swift side

        let sampleClassInstance = ASampleClass()
    
        println(_performMethod("aTestFunction", sampleClassInstance))
    

    The function resulted in these statement printed on the log:

    called correctly
    test

    So it should be not so difficult to create a _performMethod() alternative in C that:

    • creates automatically the function signature (since it seems to have a logic :-)
    • manages different return value types and parameters

    EDIT

    In Swift 2 (and maybe in Beta3, I didn't try) It seems that performSelector() is permitted (and you can call it only on NSObject subclasses). Examining the binary, It seems that now Swift creates static functions that can be specifically called by performSelector.

    I created this class

    class TestClass: NSObject {
        func test() -> Void {
            print("Hello");
        }
    }
    
    let test = TestClass()
    let aSel : Selector = NSSelectorFromString("test")
    test.performSelector(aSel)
    

    and now in the binary I find

    000000010026d830 t __TToFC7Perform9TestClass4testfT_T_

    At this time, I don't understand well the reasons behind this, but I'll investigate further

    0 讨论(0)
  • 2020-11-27 17:34

    swift3 version

    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"))
    }
    
    0 讨论(0)
  • 2020-11-27 17:35

    You could call a method from a String this way:

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

    It is available only when your foo class is subclass of NSObject

    0 讨论(0)
提交回复
热议问题