Swift 3.1
对于标准 Swift 项目,闭包是 Sulthan's answer 中已经涵盖的优雅解决方案。
如果依赖于遗留的 Objective-C 代码/库或想要调用私有 API,则使用选择器字符串名称动态调用方法是有意义的。
只有NSObject 子类可以接收消息,尝试向纯 Swift 类发送消息会导致崩溃。
#selector(mySelectorName) 只能在其类源文件中解析类型化的选择器名称。
通过牺牲类型检查,可以使用NSSelectorFromString(...)检索选择器
(与Selector("selectorName:arg:")相比,它在任何方面都不安全。只是碰巧没有产生警告)。
调用NSObject子类实例方法
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)
还有主线程变体:
instance.performSelector(onMainThread: NSSelectorFromString("selectorArg:"), with: arg, waitUntilDone: false)
正如 iOS_MIB in https://stackoverflow.com/a/48644264/5329717
这不等同于
DispatchQueue.main.async {
//perform selector
}
和后台线程变体:
instance.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)
但有一些限制:
- 只能接受 0-2 个参数
- 整数和选择器等值类型参数不起作用
- 无法处理返回值类型
- 将对象返回为
Unmanaged<AnyObject>
因此,当不需要返回结果和值类型参数时,这种省力的方法很方便。
获取NSObject 运行时方法IMP 允许使用正确的参数和返回类型进行类型化调用。
@convention(c)(types)->type 允许将 IMP 结果转换为兼容的 Swift 闭包函数。
在@convention(c) 中并非所有类型都允许
- 对于类使用 Any 或 AnyClass
- 对于对象,如果其符号可用,则使用 Any 或精确类类型
- 对于值类型,请使用相关类型
- 对于 void* 使用 OpaquePointer
这是根据定义 不安全 如果操作不当会导致崩溃和副作用。
C 级别的每个 Objective-C 方法都包含两个隐藏参数以符合 objc_msgSend(id self, SEL op, ...),它们需要包含在函数类型中为 @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)
这些是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)
限制:
- 前面提到的所有类型问题
- 接收器类需要有一个已定义的符号
获取运行时静态方法IMP和处理类型,@convention(c)适用
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
没有实际理由这样做,但objc_msgSend 可以动态使用。
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)
NSInvocation 也一样(这只是一个有趣的练习,不要这样做)
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)
}
}