user102008
user102008

Reputation: 31353

Alternative to performSelector in Swift?

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?

Upvotes: 51

Views: 59452

Answers (17)

Kamil.S
Kamil.S

Reputation: 5543

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 undefined behavior.

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


    

Upvotes: 14

Nathan Day
Nathan Day

Reputation: 6047

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"]?()

Upvotes: 0

Hardy_Germany
Hardy_Germany

Reputation: 1279

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

Upvotes: 0

Sulthan
Sulthan

Reputation: 130181

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

Upvotes: 18

Magoo
Magoo

Reputation: 2636

Swift 3

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

Upvotes: 8

LeftySwift
LeftySwift

Reputation: 21

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)

Upvotes: 2

Sentry.co
Sentry.co

Reputation: 5619

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

Upvotes: 0

Valentin Shergin
Valentin Shergin

Reputation: 7344

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

}

Upvotes: 3

FizzBuzz
FizzBuzz

Reputation: 764

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

Upvotes: 16

Valentin Shergin
Valentin Shergin

Reputation: 7344

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)

Upvotes: 2

Matej Ukmar
Matej Ukmar

Reputation: 2245

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


}

Upvotes: 7

Prakash Raj
Prakash Raj

Reputation: 1943

the actual syntax for dispatch queue is as follow.

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

Upvotes: 2

Modo Ltunzher
Modo Ltunzher

Reputation: 668

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

Upvotes: 0

user3721424
user3721424

Reputation: 163

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

Upvotes: 6

Bagusflyer
Bagusflyer

Reputation: 12925

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

Upvotes: -1

vladof81
vladof81

Reputation: 26509

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!

Upvotes: 15

JTerry
JTerry

Reputation: 139

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();

Upvotes: 4

Related Questions