The performSelector
family of methods are not available in Swift. So how can you call a method on an @objc
object, where the method to be called is chosen at runtime, and not known at compile time? NSInvocation
is apparently also not available in Swift.
I know that in Swift, you can send any method (for which there is an @objc
method declaration visible) to the type AnyObject
, similar to id
in Objective-C. However, that still requires you to hard-code the method name at compile-time. Is there a way to dynamically choose it at runtime?
Using closures
class A {
var selectorClosure: (() -> Void)?
func invoke() {
self.selectorClosure?()
}
}
var a = A()
a.selectorClosure = { println("Selector called") }
a.invoke()
Note that this is nothing new, even in Obj-C the new APIs prefer using blocks over performSelector
(compare UIAlertView
which uses respondsToSelector:
and performSelector:
to call delegate methods, with the new UIAlertController
).
Using performSelector:
is always unsafe and doesn't play well with ARC (hence the ARC warnings for performSelector:
).
As of Xcode 7, the full family of performSelector methods are available in Swift, including performSelectorOnMainThread()
and performSelectorInBackground()
. Enjoy!
Approach A
Use NSThread.detachNewThreadSelector
, good thing about this approach is that we can attach object to the message. Example code in 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)")
}
Console log:
greetings world
attached object: sunshine
Approach B
This alternative was discovered earlier, I have also tested on device and simulator. The idea is to use following method of UIControl:
func sendAction(_ action: Selector, to target: AnyObject!, forEvent event: UIEvent!)
Example code in 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")
}
Console log:
greetings world
Approach 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])
As per @JTerry answer "You don't need selectors in Swift", you can assign actual methods to variables. My solution was the following (I needed one parameter in method):
class SettingsMenuItem: NSObject {
...
var tapFunction: ((sender: AnyObject?) -> ())?
}
And then in view controller I declared, assigned and run the function in this way:
class SettingsViewController: UITableViewController {
func editProfile(sender: AnyObject?) {
...
}
...
menuItem.tapFunction = editProfile
...
if let tapFunction = menuItem.tapFunction {
tapFunction(sender: self)
}
}
You can use this in Swift
var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("someSelector"), userInfo: nil, repeats: false)
func someSelector() {
// Something after a delay
}
by this you can do what is performed by performSelector in Objective-C
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://stackoverflow.com/a/48644264/5329717 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)
}
}
I was struggling with this too. I finally realized that I didn't need to use targets or selectors. For me the solution was to assign the func to a variable and call that variable. It even works if you call it from other classes. Here's a quick example:
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();
Huh, we can use swizzling for unveil desired methods!
Just add this extension
and prefix all calls with 🚀
symbol.
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)
}
}
the actual syntax for dispatch queue is as follow.
dispatch_after(1, dispatch_get_main_queue()) { () -> Void in
self.loadData() // call your method.
}
Sometimes (especially if you are using target/action
pattern) you may have to use method -[UIApplication sendAction:to:from:forEvent:]
(for iOS), so in Swift it can be somethings like this:
UIApplication.sharedApplication()
.sendAction(someSelector, to: someObject, from: antotherObject, forEvent: someEvent)
I don't know exactly since when, but Apple brought back performSelector in Xcode 7.1.1 (At least that's the version I'm using).
In my app I'm currently building, I'm calling various functions with similar functionNames in a UIView generated from CoreAnimator (great app, BTW), so performSelector comes in very handy. Here's how I use it:
//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)
I'm using the following solution:
// 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()
})
I have a situation, where selector is constructed with string literal that comes from plist file. So the fastest way to perform some selector in swift was solved with next code
var timer = NSTimer(timeInterval: 1000, target: self, selector: Selector(someString), userInfo: nil, repeats: false)
timer.fire()
timer.invalidate()
A real world example in swift of "Matej Ukmar's" comment to "J Terry's" answer:
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
just another input for that topic.
From time to time I had to call functions/methods "indirectly". Example: calling individual functions for specific cells. I often use arrays of structs to define tabelView behavior.
I used PerformSelector etc. before, but that always looks "strange" in a swift program, so I did some research and since then, I used the indirect function calls.
This is a quick example of my playground to test the syntax and the behavior ... (xCode 9.4.1)
// Test for indirect function calls
// ------------------------------------------------------------------------
// functions we want to call inderectly
func function1() {
print("function1 active")
}
func function2() {
print("function2 active")
}
func function3() {
print("function3 active")
}
func function4(_ parameter: Int) {
print("function4 use the parameter: \(parameter)")
}
// ------------------------------------------------------------------------
// data structures
// a struct to build array items
struct functionCallTestStruct {
// struct properties
let what: String // a string as an example for other variables
let functionToCall : () // the function as an array element
var functionWithParameter : (Int) -> () // the function as an array element
let parameterForFunction : Int
// Initializer
init(_ what: String,
_ functionToCall: (),
_ functionWithParameter: @escaping (Int) -> (),
_ parameterForFunction: Int) {
self.what = what
self.functionToCall = functionToCall
self.functionWithParameter = functionWithParameter
self.parameterForFunction = parameterForFunction
}
}
// the array which holds the functions we want to call
let functionTestArray : [functionCallTestStruct] = [
functionCallTestStruct("We will call the first function", function1(), function4(_:), 10),
functionCallTestStruct("We will call the second function", function2(), function4(_:), 11),
functionCallTestStruct("We will call the third function", function3(), function4(_:), 12),
]
// ------------------------------------------------------------------------
// Test program
// a loop over the array
for i in 0 ..< functionTestArray.count {
// print explanation (be aware: print is quite lame, .. see the output ;-))
print(functionTestArray[i].what)
// and with this we indirectly call the functions
functionTestArray[i].functionToCall
let myParameter = functionTestArray[i].parameterForFunction
functionTestArray[i].functionWithParameter(myParameter)
}
gives the output:
function1 active
function2 active
function3 active
We will call the first function
function4 use the parameter: 10
We will call the second function
function4 use the parameter: 11
We will call the third function
function4 use the parameter: 12
funny fact: the print of the String (what) is slower that the function call with a print ... Which is also a warning: Don't trust on sequence with this tactic
来源:https://stackoverflow.com/questions/24158427/alternative-to-performselector-in-swift