Dynamically Call a Function in Swift

Call a method from a String in Swift

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

Call a method with dynamic class name in swift

I liked this question, because it made me to think a lit'bit outside of the box.

I'll answer it, by dividing it into a few parts.

First

call class functions

Class function is basically a Type methods, which can be achieved using the static word inside the class context.

Taking that into account, you can get a simple solution, using protocol and passing the class reference (conforming to that protocol) like this:

protocol Aaa{
static func doSomething();
}
class Foo : Aaa{
static func doSomething() {
print("Foo doing something");
}
}
class FooBar : Aaa{
static func doSomething() {
print("FooBar doing something");
}
}

class ActualWork{

//Using class (static) method
func callDynamicClassMethod <T: Aaa> (x: T.Type) {
x.doSomething();
}
}

//This is how you can use it
func usage(){
let aw = ActualWork();

aw.callDynamicClassMethod(x: Foo.self);
aw.callDynamicClassMethod(x: Foo.self);
}

Second

In case you don't really need the method on the class context, you may consider using instance methods. In that case the solution would be even simpler, like this:

protocol Bbb{
func doSomething();
}
class Bar : Bbb{
func doSomething() {
print("Bar instance doing something");
}
}
class BarBar : Bbb{
func doSomething() {
print("BarBar instance doing something");
}
}
class ActualWork{
//Using instance (non-static) method
func callDynamicInstanceMethod <T: Bbb> (x: T){
x.doSomething();
}
}
//This is how you can use it
func usage(){
let aw = ActualWork();
aw.callDynamicInstanceMethod(x: Bar());
aw.callDynamicInstanceMethod(x: BarBar());
}

Third

If you need to use the class func syntax, as OP originally did:

class func doSomething()

You CANNOT simply use a protocol. Because protocol is not a class...
So compiler won't allow it.

Cannot declare class function inside protocol

But it's still possible, you can achieve that by using
Selector with NSObject.perform method

like this:

class ActualWork : NSObject{

func callDynamicClassMethod<T: NSObject>(x: T.Type, methodName: String){
x.perform(Selector(methodName));
}

}

class Ccc : NSObject{
@objc class func doSomething(){
print("Ccc class Doing something ");
}

}

class Ddd : NSObject{
@objc class func doSomething(){
print("Ccc class Doing something ");
}

@objc class func doOther(){
print("Ccc class Doing something ");
}
}

//This is how you can use it
func usage() {
let aw = ActualWork();

aw.callDynamicClassMethod(x: Ccc.self, methodName: "doSomething");
aw.callDynamicClassMethod(x: Ddd.self, methodName: "doSomething");
aw.callDynamicClassMethod(x: Ddd.self, methodName: "doOther");

}

How to call a method in Swift 5 at runtime given a string representation of the method name?

Based on the response of others and more digging, this cannot currently be done using Swift 5.x or later! If you too are trying t do this, you will need to find another solution.

Use of function types or closures to dynamically call a function

Suppose you have some functions defined somewhere, with identical signatures.

func somefunc(callback:()->()) -> () {
// work, work, work
callback()
}
func otherfunc(callback:()->()) -> () {
// work, work, work
callback()
}

These functions have the type (()->())->()

This is a closure that takes one parameter and returns Void. The type of the one parameter is a closure that takes nothing and returns Void.

Note the parameter label (i.e, callback) is not part of the type.

Then you could do something like this:

var myfunc: (()->())->()
switch index {
case 0:
myfunc = somefunc
case 1:
myfunc = otherfunc
default:
myfunc = { _ in }
}
// do some same stuff
myfunc() {
// do some same async stuff
}
// do some more same stuff

The default case just creates an anonymous closure that doesn't do anything. You can't simply break on the default case because myfunc would be uninitialized upon use.

Calling function by name / string in swift

Try to change it to @objc func getDocumentsFolder() -> String

Swift language uses Table Dispatch for all class methods. But for performing selector on method, this method should be invoked with Message Dispatch.

That's what @objc keyword is doing - it's marks function as dynamic and it will be invoked with Message Dispatch

You can read more about method dispatches here

UPD

Agree with @Hamish comment, @objc will not make this function method dispatch for Swift, but for perform(_:) method.

(Reflection) Calling A Method With Parameters By Function Name In Swift

First of all, as you noted Swift doesn't have full reflection capabilities and rely on the coexisting ObjC to provide these features.

So even if you can write pure Swift code, you will need Solution to be a subclass of NSObject (or implement NSObjectProtocol).

Playground sample:

class Solution: NSObject {

@objc func functionName(greeting: String, name: String) {
print(greeting, name)
}

}

let solutionInstance = Solution() as NSObject
let selector = #selector(Solution.functionName)
if solutionInstance.responds(to: selector) {
solutionInstance.perform(selector, with: "Hello", with: "solution")
}

There are other points of concern here:

  • Swift's perform is limited to 2 parameters
  • you need to have the exact signature of the method (#selector here)

If you can stick an array in the first parameters, and alway have the same signature then you're done.
But if you really need to go further you have no choice than to go with ObjC, which doesn't work in Playground.

You could create a Driver.m file of the like:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

id call (NSObject *callOn, NSString *callMethod, NSArray <NSObject *>*callParameters)
{
void *result = NULL;
unsigned int index, count;

Method *methods = class_copyMethodList(callOn.class, &count);
for (index = 0; index < count; ++index)
{
Method method = methods[index];

struct objc_method_description *description = method_getDescription(method);
NSString *name = [NSString stringWithUTF8String:sel_getName(description->name)];
if ([name isEqualToString:callMethod])
{
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:description->types];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

NSObject *parameters[callParameters.count];
for (int p = 0; p < callParameters.count; ++p) {
parameters[p] = [callParameters objectAtIndex:p];
[invocation setArgument:¶meters[p] atIndex:p + 2]; // 0 is self 1 is SEL
}
[invocation setTarget:callOn];
[invocation setSelector:description->name];
[invocation invoke];
[invocation getReturnValue:&result];
break;
}
}
free(methods);

return (__bridge id)result;
}

Add it to a bridging-header (for Swift to know about what is in ObjC):

// YourProjectName-Bridging-Header.h
id call (NSObject *callOn, NSString *callMethod, NSArray *callParameters);

And call it with a Solution.swift like this:

import Foundation

class Solution: NSObject {

override init() {
super.init()
// this should go in Driver.swift
let result = call(self, "functionNameWithGreeting:name:", ["Hello", "solution"])
print(result as Any)
}

@objc
func functionName(greeting: String, name: String) -> String {
print(greeting, name)
return "return"
}

}

output:

Hello solution
Optional(return)

Edit: compilation

To compile both ObjC and Swift on the command line you can first compile ObjC to an object file:

$ cc -O -c YouObjCFile.m

Then compile your Swift project with the bridging header and the object file:

$ swiftc -import-objc-header ../Your-Bridging-Header.h YouObjCFile.o AllYourSwiftFiles.swift -o program

working sample



Related Topics



Leave a reply



Submit