在Swift中执行select器的替代方法?

performSelector系列方法在Swift中不可用 。 那么如何在@objc对象上调用一个方法,在调用的时候被调用的方法是在运行时select的,而在编译时不知道? 显然NSInvocation在Swift中也是不可用的。

我知道在Swift中,可以将任何方法(对于其中有一个@objc方法声明可见)发送到AnyObjecttypes,类似于Objective-C中的id 。 但是,这仍然要求您在编译时对方法名称进行硬编码。 有没有办法在运行时dynamicselect它?

使用闭包

 class A { var selectorClosure: (() -> Void)? func invoke() { self.selectorClosure?() } } var a = A() a.selectorClosure = { println("Selector called") } a.invoke() 

请注意,这并不是什么新东西,即使在Obj-C中,新的API更喜欢在performSelector使用块(比较UIAlertView使用respondsToSelector:performSelector:使用新的UIAlertController调用委托方法)。

使用performSelector:始终是不安全的,并且不能很好地与ARC(因此执行select器的ARC警告)一起玩。

从Xcode 7开始,完整的performSelector方法系列在Swift中可用,包括performSelectorOnMainThread()performSelectorInBackground() 。 请享用!

方法A

使用NSThread.detachNewThreadSelector ,关于这种方法的好处是我们可以附加对象到消息。 ViewController中的示例代码:

 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let delay = 2.0 * Double(NSEC_PER_SEC) var time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay)) dispatch_after(time, dispatch_get_main_queue(), { NSThread.detachNewThreadSelector(Selector("greetings:"), toTarget:self, withObject: "sunshine") }) } func greetings(object: AnyObject?) { println("greetings world") println("attached object: \(object)") } 

控制台日志:

问候世界

附件:阳光

方法B

这个替代scheme早先被发现,我也在设备和模拟器上testing过。 这个想法是使用以下方法的UIControl

 func sendAction(_ action: Selector, to target: AnyObject!, forEvent event: UIEvent!) 

ViewController中的示例代码:

 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. var control: UIControl = UIControl() control.sendAction(Selector("greetings"), to: self, forEvent: nil) // Use dispatch_after to invoke this line as block if delay is intended } func greetings() { println("greetings world") } 

控制台日志:

问候世界

方法C

的NSTimer

 class func scheduledTimerWithTimeInterval(_ seconds: NSTimeInterval, target target: AnyObject!, selector aSelector: Selector, userInfo userInfo: AnyObject!, repeats repeats: Bool) -> NSTimer! 

Swift 3

perform(#selector(someSelector), with: nil, afterDelay: 1.0, inModes: [.commonModes])

根据@JTerry的回答“你不需要在Swift中select器”,你可以将实际的方法分配给variables。 我的解决scheme是以下(我需要在方法中的一个参数):

 class SettingsMenuItem: NSObject { ... var tapFunction: ((sender: AnyObject?) -> ())? } 

然后在视图控制器中声明,这样分配和运行函数:

 class SettingsViewController: UITableViewController { func editProfile(sender: AnyObject?) { ... } ... menuItem.tapFunction = editProfile ... if let tapFunction = menuItem.tapFunction { tapFunction(sender: self) } } 

你可以在Swift中使用它

 var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("someSelector"), userInfo: nil, repeats: false) func someSelector() { // Something after a delay } 

通过这个你可以执行Objective-C中的performSelector

我也在为此苦苦挣扎。 我终于意识到,我不需要使用目标或select器。 对我来说,解决scheme是将func分配给一个variables并调用该variables。 如果你从其他类中调用它,它甚至可以工作。 这里有一个简单的例子:

 func Apple() ->Int { let b = 45; return b; } func Orange()->Int { let i = 5; return i; } func Peach() { var a = Apple; // assign the var a the Apple function var b = Orange; // assisgn the var b to the Orange function let c = a(); // assign the return value of calling the 'a' or Apple function to c let d = b(); // assign the return value of calling the 'b' or Orange function d Pear(a, b) } func Pear(x:()->Int, y:()->Int)->Int { let w = (x()+y()); // call the x function, then the y function and add the return values of each function. return w; // return the sum } Peach(); 

呵呵,我们可以用swizzling来揭开想要的方法!

只需添加此extension并用🚀符号前缀所有呼叫。

 import Foundation private var dispatchOnceToken: dispatch_once_t = 0 private var selectors: [Selector] = [ "performSelector:", "performSelector:withObject:", "performSelector:withObject:withObject:", "performSelector:withObject:afterDelay:inModes:", "performSelector:withObject:afterDelay:", ] private func swizzle() { dispatch_once(&dispatchOnceToken) { for selector: Selector in selectors { let 🚀selector = Selector("🚀\(selector)") let method = class_getInstanceMethod(NSObject.self, selector) class_replaceMethod( NSObject.self, 🚀selector, method_getImplementation(method), method_getTypeEncoding(method) ) } } } extension NSObject { func 🚀performSelector(selector: Selector) -> AnyObject? { swizzle() return self.🚀performSelector(selector) } func 🚀performSelector(selector: Selector, withObject object: AnyObject?) -> AnyObject? { swizzle() return self.🚀performSelector(selector, withObject: object) } func 🚀performSelector(selector: Selector, withObject object1: AnyObject?, withObject object2: AnyObject?) -> AnyObject? { swizzle() return self.🚀performSelector(selector, withObject: object1, withObject: object2) } func 🚀performSelector(selector: Selector, withObject object: AnyObject?, afterDelay delay: NSTimeInterval, inModes modes: [AnyObject?]?) { swizzle() self.🚀performSelector(selector, withObject: object, afterDelay: delay, inModes: modes) } func 🚀performSelector(selector: Selector, withObject object: AnyObject?, afterDelay delay: NSTimeInterval) { swizzle() self.🚀performSelector(selector, withObject: object, afterDelay: delay) } } 

Swift 3.1
对于标准的Swift项目,closures是Sulthan的答案已经涵盖的优雅的解决scheme。 如果依赖于旧的Objective-C代码/库,则使用select器string名称dynamic调用方法是有意义的。

只有NSObject子类可以接收消息,试图发送一个纯Swift类会导致崩溃。

#selector(mySelectorName)只能在类的源文件中parsingtypes化的select器名称。
通过牺牲types检查select器可以检索使用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) instance.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg) 

虽然有一些限制:

  • 它只能取0-2的参数
  • 像整数和select器这样的值types参数不起作用
  • 无法处理返回的值types
  • 将对象作为Unmanaged<AnyObject>返回

所以当不需要返回结果和值types参数时,这种低效率的方法是很方便的。

获取NSObject运行时方法IMP允许使用正确的参数和返回types进行types化的调用。 @convention(c)(types)->type允许将IMP结果转换为兼容的Swift闭包函数。

@convention(c)不允许所有types

  • 对于类使用Any或AnyClass
  • 对象使用任何或确切的类types,如果它的符号是可用的
  • 对于值types使用相关的types
  • 对于void *使用OpaquePointer

这从定义上来说是 不安全的 ,如果做得不正确会导致崩溃和副作用。

每个在C级上的Objective-C方法都包含两个隐藏的参数,以符合objc_msgSend(id self, SEL op, ...) ,这些参数需要作为@convention(c)(Any?,Selector, ... )包含在函数types中@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) 

限制:

  • 所有types的问题都提到过
  • 接收器类需要有一个定义的符号

获取运行时静态方法IMP和处理types, @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 

这样做没有实际的理由,但可以dynamic使用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) } } 

调度队列的实际语法如下。

 dispatch_after(1, dispatch_get_main_queue()) { () -> Void in self.loadData() // call your method. } 

有时(特别是如果你使用target/action模式),你可能不得不使用方法-[UIApplication sendAction:to:from:forEvent:] (for iOS),所以在Swift中可能有这样的东西:

 UIApplication.sharedApplication() .sendAction(someSelector, to: someObject, from: antotherObject, forEvent: someEvent) 

我不知道自从什么时候,但苹果带回了Xcode 7.1.1 performSelector(至less这是我使用的版本)。

在我的应用程序,我目前正在build设,我调用CoreAnimator生成的UIView(伟大的应用程序,顺便说一句)类似的functionNames函数,所以performSelector来非常方便。 以下是我如何使用它:

 //defines the function name dynamically. the variables "stepN" and "dir" are defined elsewhere. let AnimMethod = "addStep\(stepN)\(dir)Animation" //prepares the selector with the function name above let selector: Selector = NSSelectorFromString(AnimMethod) //calls the said function in UIView named "meter" meter.performSelector(selector) 

我正在使用以下解决scheme:

 // method will be called after delay func method1() { ...... } // to replace performSelector // delay 100 ms let time : dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_MSEC/(USEC_PER_SEC*10))) dispatch_after(time, dispatch_get_main_queue(), { self.method1() }) 

我有一个情况,其中select器是用来自plist文件的string文字构造的。 所以在swift中执行某个select器的最快方法是用下一个代码解决的

 var timer = NSTimer(timeInterval: 1000, target: self, selector: Selector(someString), userInfo: nil, repeats: false) timer.fire() timer.invalidate() 

一个真实世界的例子,迅速“Matej Ukmar的”评论“J特里的”答案:

 class Button { var title:String = "The big button" var selector: ((sender: AnyObject?, type:String) -> ())?/*this holds any method assigned to it that has its type signature*/ func click(){ selector!(sender: self,type: "click")/*call the selector*/ } func hover(){ selector!(sender: self,type: "hover")/*call the selector*/ } } class View { var button = Button() init(){ button.selector = handleSelector/*assign a method that will receive a call from the selector*/ } func handleSelector(sender: AnyObject?,type:String) { switch type{ case "click": Swift.print("View.handleSelector() sender: " + String(sender!.dynamicType) + ", title: " + String((sender as! Button).title) + ", type: " + type) case "hover": Swift.print("View.handleSelector() sender: " + String(sender!.dynamicType) + ", title: " + String((sender as! Button).title) + ", type: " + type) default:break; } } } let view:View = View() view.button.click()/*Simulating button click*/ view.button.hover()/*Simulating button hover*/ //Output: View.handleSelector() sender: Button, title: The big button, type: click //Output: View.handleSelector() sender: Button, title: The big button, type: hover