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
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.
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.
Call function as string in Swift
You should add an @objc
inference to the function, otherwise your function will not be expressable in Objective-C, which is what NSSelectorFromString
uses.
@objc func function() {
[...]
}
Learn more about the limited @objc
inference in Swift 4 here.
(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
Calling NSString method on a String in Swift
After doing some research, it looks like containsString
is not a String
function, but can be accessed by bridging to an NSString
.
Under Apple's Documentation on Using Swift with Cocoa and Objective-C, it says that
Swift automatically bridges between the String type and the NSString
class. This means that anywhere you use an NSString object, you can
use a Swift String type instead and gain the benefits of both types
But it appears that only some of NSString's functions are accessible without explicitly bridging. To bridge to an NSString and use any of its functions, the following methods work:
//Example Swift String var
var newString:String = "this is a string"
//Bridging to NSString
//1
(newString as NSString).containsString("string")
//2
newString.bridgeToObjectiveC().containsString("string")
//3
NSString(string: newString).containsString("string")
All three of these work.
It's interesting to see that only some NSString
methods are available to Strings
and others need explicit bridging. This may be something that is built upon as Swift develops.
A way to dynamically invoke methods in Swift
You can use KVO pattern to solve this issue, however as @David Pasztor comment says.
When you are trying to dynamically call methods in a strongly typed, compiled language, you are most probably on the wrong track to solve your issue. This is especially true for Swift. Explain the problem you're trying to solve that you think requires having to dynamically all methods, rather than trying to translate JS code line by line to Swift.
However KVO can be used to solve such issues, i suggest reading about it it allows you to dynamically observe keys, and based on that you can do your actions.
Keep in mind KVO is compiled with Ojbc
compiler.
How do I call a function with the following completion handler?
There can be multiple forms to call the method.
1. Define the parameters when calling the closure
, u.e.
InAppHandler.purchaseSubscription(productId: "test") {(x, y, z) in
print(x, y, z)
processPurchase()
}
2. You can use the shorthand form ($0, $1
etc.) for the parameters in the closure
while calling it, i.e.
InAppHandler.purchaseSubscription(productId: "test") {
print($0, $1, $2)
processPurchase()
}
The above 2 are same. Just that in first one you're giving the parameter names and in the second one you're using the shorthand for those parameters.
3. In case you're not using any parameters that you're getting in the closure
, mark them with underscore(_
) like so,
InAppHandler.purchaseSubscription(productId: "test") {(_, _, _) in
processPurchase()
}
You can use any of the other forms depending on your requirement.
Related Topics
How to Apply the Type to a Nsfetchrequest Instance
Swift Dictionary Get Key For Value
Unexpected Non-Void Return Value in Void Function (Swift 2.0)
Changing the Status Bar Color For Specific Viewcontrollers Using Swift in Ios8
Uialertcontroller - Add Custom Views to Actionsheet
Whither Dispatch_Once in Swift 3
Getting the Decimal Part of a Double in Swift
Swift 2 ( Executefetchrequest ): Error Handling
Realitykit VS Scenekit VS Metal - High-Quality Rendering
Xcode Swift Am/Pm Time to 24 Hour Format
How to Make a Random Color With Swift
When Should I Access Properties With Self in Swift
Use Binding≪Int≫ With a Textfield Swiftui
Check If 'Any' Value Is Object
How to Cast Self to Unsafemutablepointer≪Void≫ Type in Swift