Alternative to performSelector in Swift?
Asked Answered
D

17

51

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?

Decor answered 11/6, 2014 at 8:44 Comment(4)
Rethink your approach. In my code a closure solved it.Slating
Still you can do this, find my answer here #24126761Perilune
@Slating Fine, please rethink pyramid of doom for me then, thanks.Ways
@Ways I rethink it as a sequence of guard statements. :)Slating
H
18

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:).

Hogarth answered 11/6, 2014 at 9:17 Comment(1)
Looks good, but you can rewrite your selectorClosure property like this (note that nil assignment for an optional is implicit): var selectorClosure: (() -> Void)?Commode
P
16

As of Xcode 7, the full family of performSelector methods are available in Swift, including performSelectorOnMainThread() and performSelectorInBackground(). Enjoy!

Panhellenic answered 21/7, 2015 at 19:48 Comment(1)
@RoiMulia yes, so long as you compile with Xcode 7+Revisionism
V
15

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!
Vaginismus answered 7/7, 2014 at 4:17 Comment(2)
Thanks, it seems A (dispatch_after(DISPATCH_TIME_NOW, ...) is the only way to dynamically call ObjC method with argument from Swift.Allynallys
BTW, is it really necessary to dispatch on main queue? I.e. when you change UIView's property it might notify observer waiting for main thread event? Also, while you are dispatching on main thread it would be executed on different thread anyway.Allynallys
H
13

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)
    }
}
Helman answered 1/5, 2017 at 6:0 Comment(0)
S
8

Swift 3

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

Sollows answered 26/11, 2016 at 15:37 Comment(0)
S
7

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)
    }


}
Scraggy answered 11/12, 2014 at 15:39 Comment(2)
your answer is limited to selectors in general and if they are still needed in swift. But the question was about alternatives to performSelector which was used for async stuff, defer, wait for completion or easy and quick thread control (running on main or a background thread).Gyratory
Thanks for this example, it is the first instance I found outlining how you call an "unknown" method with a know signature in Swift (which is occasionally what I want to do if I model things with dynamic methods). Much more useful than NSInvocation and much less error prone too.Spang
R
6

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

Reinold answered 12/8, 2014 at 4:54 Comment(3)
This is not exactly the same because 1) it does it asynchronously, whereas performSelector: (without afterDelay) is synchronous; 2) it only works on a thread where we are running a run loop.Decor
Hi @Decor it works fine for me, now, how do I stop the timer if I set repeats to true?Disqualify
@Disqualify timer.invalidate()Definitely
K
4

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();
Kalinin answered 2/7, 2014 at 2:6 Comment(1)
This worked for me. I needed function with one parameter so I declared 'selector' property like this --- var tapFunction: ((sender: AnyObject?) -> ())? --- and then declare a function as --- func actionMethod(sender: AnyObject?)Scraggy
W
3

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)
    }

}
Whinchat answered 29/8, 2015 at 18:4 Comment(2)
hey.. did I just see a Rocket..!!! :) just kidding.. works like a charm.. thanks.Worker
Yeah... Thank you.Whinchat
E
2

the actual syntax for dispatch queue is as follow.

dispatch_after(1, dispatch_get_main_queue()) { () -> Void in
        self.loadData() // call your method.
    }
Euphemiah answered 19/10, 2014 at 14:12 Comment(0)
W
2

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)
Whinchat answered 25/6, 2015 at 21:57 Comment(0)
F
2

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)
Forestforestage answered 5/12, 2015 at 1:21 Comment(0)
L
0

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()
Legault answered 1/10, 2014 at 15:3 Comment(0)
I
0

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
Iseult answered 22/11, 2015 at 14:53 Comment(0)
H
0

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

Huguenot answered 23/6, 2018 at 17:30 Comment(0)
T
0

You didn't really explain what your where trying to actually achieve, so here's sone ideas that might lead your to a solution. Firstly, and I would encourage you to to do this unless you haven to, but you can dynamical generate a selection from a String in swift

Selector("myMehodA:")
Selector("myMehodB:")

But functions are first rate objects in swift so why not just use a dictionary

func bMethod() { print("B") }

let dict = ["aThing": { print("A") },
            "bThing": bMethod]

dict["aThing"]?()
dict["bThing"]?()
Tirzah answered 8/2, 2022 at 13:1 Comment(0)
B
-1

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()
})
Barometrograph answered 11/7, 2014 at 8:53 Comment(1)
the question wasn't about a delayDecor

© 2022 - 2024 — McMap. All rights reserved.