Swift 3.1
For standard Swift projects closures are elegant solution already covered in Sulthan's answer.
Invoking methods dynamically using selector string names makes sense if one depends on legacy Objective-C code / libraries or would like to invoke private API.
Only NSObject
subclasses can receive messages, attempt to send one to a pure Swift class will result in a crash.
#selector(mySelectorName)
can resolve typed selector names in its class source file only.
By sacrificing type checking a selector can be retrieved using NSSelectorFromString(...)
(it's not safer in any way compared to Selector("selectorName:arg:")
it just happens not to generate a warning).
Calling NSObject
subclass instance method
let instance : NSObject = fooReturningObjectInstance() as! NSObject
instance.perform(#selector(NSSelectorFromString("selector"))
instance.perform(#selector(NSSelectorFromString("selectorArg:"), with: arg)
instance.perform(#selector(NSSelectorFromString("selectorArg:Arg2:"), with: arg, with: arg2)
also with main thread variant:
instance.performSelector(onMainThread: NSSelectorFromString("selectorArg:"), with: arg, waitUntilDone: false)
As noted by iOS_MIB in https://mcmap.net/q/49988/-how-to-perform-a-selector-on-the-main-thread-with-the-appdelegate-in-a-global-function-in-swift
this is not equivalent to
DispatchQueue.main.async {
//perform selector
}
and background thread variant:
instance.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)
There are some limitations though:
- It can only take 0-2 arguments
- value type arguments like integers and selectors don't work
- can't handle returned value types
- returns objects as
Unmanaged<AnyObject>
So this low effort approach is convenient when return result and value type arguments are not needed.
Fetching NSObject
runtime method IMP
allows making a typed call with proper arguments and return type.
@convention(c)(types)->type
allows casting the IMP
result to compatible Swift closure function.
In @convention(c)
not all types are allowed
- For classes use Any or AnyClass
- For objects use Any or exact class type if its symbol is available
- For value types use the relevant type
- For void* use OpaquePointer
This is by definition unsafe and if done incorrectly will result in crashes and side effects.
Every Objective-C method on C level contains two hidden arguments to conform to objc_msgSend(id self, SEL op, ...)
which need to be included in the function type as @convention(c)(Any?,Selector, ... )
let instance : NSObject = fooReturningObjectInstance() as! NSObject
let selector : Selector = NSSelectorFromString("selectorArg:")
let methodIMP : IMP! = instance.method(for: selector)
unsafeBitCast(methodIMP,to:(@convention(c)(Any?,Selector,Any?)->Void).self)(instance,selector,arg)
These are static equivalents of perform(...)
NSObject.perform(NSSelectorFromString("selector"))
NSObject.perform(NSSelectorFromString("selectorArg:"), with: arg)
NSObject.perform(NSSelectorFromString("selectorArg:Arg2:"), with: arg, with: arg2)
NSObject.performSelector(onMainThread: NSSelectorFromString("selectorArg:"), with: arg, waitUntilDone: false)
NSObject.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)
Limitations:
- All type issues mentioned previously
- The receiver class needs to have a defined symbol
Fetching runtime static method IMP
and handling types, @convention(c)
applies
let receiverClass = NSClassFromString("MyClass")
let selector : Selector = NSSelectorFromString("selectorArg:")
let methodIMP : IMP! = method_getImplementation(class_getClassMethod(receiverClass, selector))
let result : NSObject = unsafeBitCast(methodIMP,to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(receiverClass,selector,arg) as! NSObject
There's no practical reason to do it, but objc_msgSend
can be used dynamically.
let instance : NSObject = fooReturningObjectInstance() as! NSObject
let handle : UnsafeMutableRawPointer! = dlopen("/usr/lib/libobjc.A.dylib", RTLD_NOW)
let selector : Selector = NSSelectorFromString("selectorArg:")
unsafeBitCast(dlsym(handle, "objc_msgSend"), to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(instance,selector,arg)
dlclose(handle)
Same for NSInvocation
(this is only fun exercise, don't do it)
class Test : NSObject
{
var name : String? {
didSet {
NSLog("didSetCalled")
}
}
func invocationTest() {
let invocation : NSObject = unsafeBitCast(method_getImplementation(class_getClassMethod(NSClassFromString("NSInvocation"), NSSelectorFromString("invocationWithMethodSignature:"))),to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(NSClassFromString("NSInvocation"),NSSelectorFromString("invocationWithMethodSignature:"),unsafeBitCast(method(for: NSSelectorFromString("methodSignatureForSelector:"))!,to:(@convention(c)(Any?,Selector,Selector)->Any).self)(self,NSSelectorFromString("methodSignatureForSelector:"),#selector(setter:name))) as! NSObject
unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setSelector:")),to:(@convention(c)(Any,Selector,Selector)->Void).self)(invocation,NSSelectorFromString("setSelector:"),#selector(setter:name))
var localName = name
withUnsafePointer(to: &localName) { unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setArgument:atIndex:")),to:(@convention(c)(Any,Selector,OpaquePointer,NSInteger)->Void).self)(invocation,NSSelectorFromString("setArgument:atIndex:"), OpaquePointer($0),2) }
invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self)
}
}
guard
statements. :) – Slating