206
votes

I've googled but not been able to find out what the swift equivalent to respondsToSelector: is.

This is the only thing I could find (Swift alternative to respondsToSelector:) but isn't too relevant in my case as its checking the existence of the delegate, I don't have a delegate I just want to check if a new API exists or not when running on the device and if not fall back to a previous version of the api.

17
All of these are meant to be replaced with Optionals, and exercised with Optional Chaining - Jack
Given that Apple explicitly recommends using NSClassFromString and respondsToSelector among other mechanics for checking for newly implemented functionality, I've got to believe that the mechanisms either are in place already, or will be there before release. Try watching the Advanced Interop... video from WWDC. - David Berry
@Jack Wu But what if the new method is something new introduced on something fundamental like UIApplication or UIViewController. These objects are not optional and the new method is not optional. How can you check if you must call for example UIApplcation:newVersionOfMethodX or you must call UIApplication:deprecatedVersionOfMethodX? (Given that you can build and run an app in Swift on iOS 7 this is going to be a very common scenario) - Gruntcakes
Is the API you are/were concerned with an Apple API? - GoZoner
@PotassiumPermanganate - of course, if the answer is 'yes, I was using Apple APIs' then perhaps he can use if #available(...) in Swift 2.x to avoid using respondsToSelector in the first place. But you knew that. (apple.co/1SNGtMQ) - GoZoner

17 Answers

177
votes

As mentioned, in Swift most of the time you can achieve what you need with the ? optional unwrapper operator. This allows you to call a method on an object if and only if the object exists (not nil) and the method is implemented.

In the case where you still need respondsToSelector:, it is still there as part of the NSObject protocol.

If you are calling respondsToSelector: on an Obj-C type in Swift, then it works the same as you would expect. If you are using it on your own Swift class, you will need to ensure your class derives from NSObject.

Here's an example of a Swift class that you can check if it responds to a selector:

class Worker : NSObject
{
    func work() { }
    func eat(food: AnyObject) { }
    func sleep(hours: Int, minutes: Int) { }
}

let worker = Worker()

let canWork = worker.respondsToSelector(Selector("work"))   // true
let canEat = worker.respondsToSelector(Selector("eat:"))    // true
let canSleep = worker.respondsToSelector(Selector("sleep:minutes:"))    // true
let canQuit = worker.respondsToSelector(Selector("quit"))   // false

It is important that you do not leave out the parameter names. In this example, Selector("sleep::") is not the same as Selector("sleep:minutes:").

59
votes

There is no real Swift replacement.

You can check in the following way:

someObject.someMethod?()

This calls the method someMethod only if it's defined on object someObject but you can use it only for @objc protocols which have declared the method as optional.

Swift is inherently a safe language so everytime you call a method Swift has to know the method is there. No runtime checking is possible. You can't just call random methods on random objects.

Even in Obj-C you should avoid such things when possible because it doesn't play well with ARC (ARC then triggers warnings for performSelector:).

However, when checking for available APIs, you can still use respondsToSelector:, even if Swift, if you are dealing with NSObject instances:

@interface TestA : NSObject

- (void)someMethod;

@end

@implementation TestA

//this triggers a warning

@end   


var a = TestA()

if a.respondsToSelector("someMethod") {
   a.someMethod()
}
42
votes

Update Mar 20, 2017 for Swift 3 syntax:

If you don't care whether the optional method exists, just call delegate?.optionalMethod?()

Otherwise, using guard is probably the best approach:

weak var delegate: SomeDelegateWithOptionals?

func someMethod() {
    guard let method = delegate?.optionalMethod else {
        // optional not implemented
        alternativeMethod()
        return
    }
    method()
}

Original answer:

You can use the "if let" approach to test an optional protocol like this:

weak var delegate: SomeDelegateWithOptionals?

func someMethod() {
  if let delegate = delegate {
    if let theMethod = delegate.theOptionalProtocolMethod? {
      theMethod()
      return
    }
  }
  // Reaching here means the delegate doesn't exist or doesn't respond to the optional method
  alternativeMethod()
}
12
votes

If the method you are testing for is defined as an optional method in a @objc protocol (which sounds like your case), then use the optional chaining pattern as:

if let result = object.method?(args) {
  /* method exists, result assigned, use result */
}
else { ... }

When the method is declare as returning Void, simply use:

if object.method?(args) { ... }

See:

“Calling Methods Through Optional Chaining”
Excerpt From: Apple Inc. “The Swift Programming Language.”
iBooks. https://itun.es/us/jEUH0.l

11
votes

It seems you need to define your protocol as as subprotocol of NSObjectProtocol ... then you'll get respondsToSelector method

@objc protocol YourDelegate : NSObjectProtocol
{
    func yourDelegateMethod(passObject: SomeObject)
}

note that only specifying @objc was not enough. You should be also careful that the actual delegate is a subclass of NSObject - which in Swift might not be.

9
votes

Functions are first-class types in Swift, so you can check whether an optional function defined in a protocol has been implemented by comparing it to nil:

if (someObject.someMethod != nil) {
    someObject.someMethod!(someArgument)
} else {
    // do something else
}
9
votes

For swift3

If you just want to call the method, run the code below.

self.delegate?.method?()

7
votes

In Swift 2,Apple introduced a new feature called API availability checking, which might be a replacement for respondsToSelector: method.The following code snippet comparison is copied from the WWDC2015 Session 106 What's New in Swift which I thought might help you,please check it out if you need to know more.

The Old Approach:

@IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
    if dropButton.respondsToSelector("setSpringLoaded:") {
        dropButton.springLoaded = true
    }
}

The Better Approach:

@IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
    if #available(OSX 10.10.3, *) {
        dropButton.springLoaded = true
    }
}
6
votes

For swift 3.0

import UIKit

@objc protocol ADelegate : NSObjectProtocol {

    @objc optional func hi1()
    @objc optional func hi2(message1:String, message2:String)
}

class SomeObject : NSObject {

    weak var delegate:ADelegate?

    func run() {

        // single method
        if let methodHi1 = delegate?.hi1 {
            methodHi1()
        } else {
            print("fail h1")
        }

        // multiple parameters
        if let methodHi2 = delegate?.hi2 {
            methodHi2("superman", "batman")
        } else {
            print("fail h2")
        }
    }
}

class ViewController: UIViewController, ADelegate {

    let someObject = SomeObject()

    override func viewDidLoad() {
        super.viewDidLoad()

        someObject.delegate = self
        someObject.run()
    }

    // MARK: ADelegate
    func hi1() {

        print("Hi")
    }

    func hi2(message1: String, message2: String) {

        print("Hi \(message1) \(message2)")
    }
}
4
votes

Currently (Swift 2.1) you can check it using 3 ways:

  1. Using respondsToSelector answered by @Erik_at_Digit
  2. Using '?' answered by @Sulthan

  3. And using as? operator:

    if let delegateMe = self.delegate as? YourCustomViewController
    {
       delegateMe.onSuccess()
    }
    

Basically it depends on what you are trying to achieve:

  • If for example your app logic need to perform some action and the delegate isn't set or the pointed delegate didn't implement the onSuccess() method (protocol method) so option 1 and 3 are the best choice, though I'd use option 3 which is Swift way.
  • If you don't want to do anything when delegate is nil or method isn't implemented then use option 2.
3
votes

As I started to update my old project to Swift 3.2, I just needed to change the method from

respondsToSelector(selector)

to:

responds(to: selector)
2
votes

I just implement this myself in a project, see code below. As mentions by @Christopher Pickslay it is important to remember that functions are first class citizens and can therefore be treated like optional variables.

@objc protocol ContactDetailsDelegate: class {

    optional func deleteContact(contact: Contact) -> NSError?
}

...

weak var delegate:ContactDetailsDelegate!

if let deleteContact = delegate.deleteContact {
    deleteContact(contact)
}
2
votes

another possible syntax by swift..

 if let delegate = self.delegate, method = delegate.somemethod{
        method()
    }
0
votes

I use guard let else, so that can do some default stuff if the delegate func is not implemented.

@objc protocol ViewController2Delegate: NSObjectProtocol {

    optional func viewController2(controller: ViewController2, didSomethingWithStringAndReturnVoid string: String)

    optional func viewController2(controller: ViewController2, didSomethingWithStringAndReturnString string: String) -> String
}

class ViewController2: UIViewController {

    weak var delegate: ViewController2Delegate?        

    @IBAction func onVoidButtonClicked(sender: AnyObject){

        if (delegate != nil && delegate!.respondsToSelector(Selector("viewController2:didSomethingWithStringAndReturnVoid:"))) {
            NSLog("ReturnVoid is implemented")

            delegate!.viewController2!(self, didSomethingWithStringAndReturnVoid: "dummy")
        }
        else{
            NSLog("ReturnVoid is not implemented")
            // Do something by default
        }
    }

    @IBAction func onStringButtonClicked(sender: AnyObject){

        guard let result = delegate?.viewController2?(self, didSomethingWithStringAndReturnString: "dummy") else {
            NSLog("ReturnString is not implemented")
            // Do something by default
            return
        }

        NSLog("ReturnString is implemented with result: \(result)")
    }
}
0
votes

I guess you want to make a default implementation for delegate. You can do this:

let defaultHandler = {}
(delegate?.method ?? defaultHandler)()
-1
votes

Swift 3:

protocol

@objc protocol SomeDelegate {
    @objc optional func method()
}

Object

class SomeObject : NSObject {

weak var delegate:SomeObject?

func delegateMethod() {

     if let delegateMethod = delegate?.method{
         delegateMethod()
     }else {
        //Failed
     }

   }

}
-4
votes

The equivalent is the ? operator:

var value: NSNumber? = myQuestionableObject?.importantMethod()

importantMethod will only be called if myQuestionableObject exists and implements it.