217

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.

mfaani
  • 33,269
  • 19
  • 164
  • 293
Gruntcakes
  • 37,738
  • 44
  • 184
  • 378
  • All of these are meant to be replaced with Optionals, and exercised with Optional Chaining – Jack Jun 11 '14 at 16:23
  • 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 Jun 11 '14 at 16:35
  • 2
    @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 Jun 11 '14 at 16:36
  • Is the API you are/were concerned with an Apple API? – GoZoner Nov 19 '15 at 22:42
  • @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. (http://apple.co/1SNGtMQ) – GoZoner Nov 20 '15 at 23:43

17 Answers17

183

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

Andrew
  • 15,357
  • 6
  • 66
  • 101
Erik
  • 12,730
  • 5
  • 36
  • 42
  • I'm trying to determine if a new method introduced in iOS8 to UIApplication is present and so far am having no luck. Here is how I am attempting according to the above: if let present = UIApplication.sharedApplication().respondsToSelector(Selector("redactedAsStillUnderNDA")). However I get a compilation error: "Bound value in a conditional binding must be of optional type". – Gruntcakes Jun 11 '14 at 21:31
  • 4
    You'll need to split up those lines *or* remove the `let x = ` portion. Basically, the `if let x = y` structure is to unwrap optional values (similar to `!`). Since you are getting a `Bool` back from `respondsToSelector`, the compiler is complaining that the result is not an optional type (`Bool?`). – Erik Jun 12 '14 at 03:13
  • What would happen to `var`s declared? Do they still respond the same way? In Objective-C I could do `if ( [obj respondsToSelector:@selector(setSomeVar:)] ) { ... }` for a `someVar` property. Does it work the same way with `var`s in Swift? – Alejandro Iván Dec 17 '15 at 15:13
  • What if View controller instance optional is not nil, but the method or function is not implemented in that controller? I guess you will need to check if object responds to selector or not. – Ashish P Jul 21 '16 at 13:14
  • 1
    I am getting "Value of type 'UIViewController' has no member 'respondsToSelector'" – Michał Ziobro Mar 02 '18 at 11:42
60

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()
}
JRG-Developer
  • 12,454
  • 8
  • 55
  • 81
Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 1
    So therefore apps now have to know explicitly what version of the OS they are running on? I want to call a method that only exists in iOS8 and if its not there use the old iOS7 version. So instead of checking for the presence/absense of the new method I instead have to find out if I'm on iOS8 or not? Swift is good but this seems a bit clunky. – Gruntcakes Jun 11 '14 at 16:28
  • @sulthan I'm not sure where youre coming from stating that you shouldn't be using `respondsToSelector:` as Apple's recommendation is explicitly that you should be doing it. See [here](https://developer.apple.com/library/prerelease/ios/documentation/DeveloperTools/Conceptual/cross_development/Using/using.html#//apple_ref/doc/uid/20002000-SW6) – David Berry Jun 11 '14 at 16:34
  • @David Yes, you have found one of the few cases when `respondsToSelector:` is actually good to have. But if you go through the Swift reference, they speak about using subclasses for different system versions. – Sulthan Jun 11 '14 at 16:36
  • If that's not the case the OP is talking about, and they're talking about the optional protocol case, then they also explicitly allow for that using optional chaining (as you've pointed out). Either way, saying "you should avoid doing what Apple has explicitly told you to do" seems like bad advice. Apple has "always" provided mechanisms for determining and exercising optional functionality at run-time. – David Berry Jun 11 '14 at 16:42
  • @David I was going to dispute your "always" statement, then I remembered `Gestalt()`... and shuttered... – Grady Player Jun 11 '14 at 17:44
  • "but you can use it only for @obj protocols which have declared the method as @optional." Or if `someObject` is type `AnyObject`, you can use it on ANY @objc method. – newacct Jun 13 '14 at 01:47
  • _No runtime checking is possible. You can't just call random methods on random objects_ I was thinking exactly the same but then I just stumbled upon this code: `if locationManager.responds(to: #selector(CLLocationManager.requestWhenInUseAuthorization)) {...}` this is the case where I'm not sure what happens for iOS 7 if we just used _optional_. Because `requestWhenInUseAuthorization` was never defined for iOS 7 so I'm guessing it would crash if we do your way. right? Can you clarify this? – mfaani Aug 31 '18 at 18:10
  • @Honey Yes, that's possible, see the end of my answer. However, This answer was written back in Swift 1.2 days when it was not so easy to write a `#selector` in Swift. The functionality is using Obj-C runtime anyway so it's not really pure Swift. – Sulthan Aug 31 '18 at 18:25
  • Sorry I didn't read it through before. For API checking you said, "you _can_ still use". Shouldn't you say "you *must* use"? – mfaani Aug 31 '18 at 18:41
  • 1
    @Honey In Swift we generally use availability directives instead, e.g. `if #available(iOS 10) {` and then call the method directly. – Sulthan Aug 31 '18 at 18:45
44

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()
}
hyouuu
  • 2,471
  • 2
  • 27
  • 37
  • 4
    if let theMethod = delegate?.theOptionalProtocolMethod – john07 Nov 10 '15 at 13:17
  • 3
    Example of selector syntax when there are multiple candidates: `let theMethod = delegate.userNotificationCenter(_:willPresent:withCompletionHandler:)` – Cœur May 04 '18 at 08:47
  • Just another example for clarification: `let willSelectRowAt = delegate?.tableView(_:willSelectRowAt:)` – mbi Sep 10 '20 at 15:43
12

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

Besi
  • 22,579
  • 24
  • 131
  • 223
GoZoner
  • 67,920
  • 20
  • 95
  • 145
  • This would only work if the method returns something, what if it is a void method? – Gruntcakes Jun 11 '14 at 19:53
  • 1
    If void use simply `if object.method?(args) { ... }` - the call of the method, when it exists, will return `Void` which is not `nil` – GoZoner Jun 11 '14 at 19:55
  • 1
    However that results in "Type Void does not conform to protocol "Logic Value"" – Gruntcakes Jun 11 '14 at 21:24
  • Please see the referenced documentation (page 311-312). – GoZoner Jun 11 '14 at 21:44
  • "If the method you are testing for is defined as an optional method in a @objc protocol" Or if `object` has type `AnyObject`, you can test any @objc method. – newacct Jun 13 '14 at 01:48
  • This looks great in the documentation, but does not really work... (Xcode 6.3 IDE rejects this syntax, at least for iOS classes). Maybe this is good for your own SWIFT classes. – Gonen Apr 24 '15 at 11:57
  • For optional protocol methods, you can use "if let" on the method itself! Like this: `if let delegateMethod = delegate.textFieldShouldEndEditing { return delegateMethod(textField) } }` – stone Jun 09 '15 at 06:21
11

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.

Matej Ukmar
  • 2,157
  • 22
  • 27
11

For swift3

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

self.delegate?.method?()

Jason Yu
  • 311
  • 3
  • 15
9

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
}
Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92
7

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
    }
}
tounaobun
  • 14,570
  • 9
  • 53
  • 75
  • I think that's a far better solution than using the `respondsToSelector` approach in Swift. That's also because the selector check wasn't the best solution to this problem (availability checking) in the first place. – JanApotheker Mar 13 '17 at 18:37
6

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)")
    }
}
Kakashi
  • 534
  • 11
  • 16
5

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)
chAlexey
  • 692
  • 8
  • 13
4

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.
Community
  • 1
  • 1
OhadM
  • 4,687
  • 1
  • 47
  • 57
2

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)
}
Bulwinkel
  • 2,111
  • 25
  • 23
2

another possible syntax by swift..

 if let delegate = self.delegate, method = delegate.somemethod{
        method()
    }
Janub
  • 1,594
  • 14
  • 26
0

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)")
    }
}
dichen
  • 1,643
  • 14
  • 19
0

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

let defaultHandler = {}
(delegate?.method ?? defaultHandler)()
ZeroOnet
  • 15
  • 7
-1

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
     }

   }

}
etzuk
  • 436
  • 4
  • 9
-4

The equivalent is the ? operator:

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

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

Ben Gottlieb
  • 85,404
  • 22
  • 176
  • 172
  • But what if myQuestionalObject is something like UIApplication (which is therefore going to exist and thus checking its existence is meaningless) and importandMethod() is a new method introduced in iOS N and you want to determine whether you can call it or have to call the old deprecatedImportantMethod() on iOS N-1? – Gruntcakes Jun 11 '14 at 16:33
  • 2
    you also need a `?` after `importantMethod` – newacct Jun 13 '14 at 01:46