Quel est l'équivalent Swift de respondsToSelector?

195

J'ai fait une recherche sur Google, mais je n'ai pas pu découvrir à quoi correspond l'équivalent rapide respondsToSelector:.

C'est la seule chose que j'ai pu trouver ( alternative Swift à respondsToSelector:) mais n'est pas trop pertinente dans mon cas car elle vérifie l'existence du délégué, je n'ai pas de délégué, je veux juste vérifier si une nouvelle API existe ou non lors de l'exécution sur l'appareil et sinon revenir à une version précédente de l'API.

Gruntcakes
la source
Tous ces éléments sont censés être remplacés par des options et exercés avec un chaînage facultatif
Jack
Étant donné qu'Apple recommande explicitement d'utiliser NSClassFromStringet respondsToSelectorentre autres mécanismes pour vérifier les fonctionnalités nouvellement implémentées, je dois croire que les mécanismes sont déjà en place ou seront là avant la sortie. Essayez de regarder la Advanced Interop...vidéo de la WWDC.
David Berry
2
@Jack Wu Mais que faire si la nouvelle méthode est quelque chose de nouveau introduit sur quelque chose de fondamental comme UIApplication ou UIViewController. Ces objets ne sont pas facultatifs et la nouvelle méthode n'est pas facultative. Comment pouvez-vous vérifier si vous devez appeler par exemple UIApplcation: newVersionOfMethodX ou vous devez appeler UIApplication: deprecatedVersionOfMethodX? (Étant donné que vous pouvez créer et exécuter une application dans Swift sur iOS 7, ce sera un scénario très courant)
Gruntcakes
L'API vous concerne-t-elle / était-elle concernée par une API Apple?
GoZoner
@PotassiumPermanganate - bien sûr, si la réponse est «oui, j'utilisais des API Apple», il peut peut-être utiliser if #available(...)dans Swift 2.x pour éviter de l'utiliser respondsToSelectoren premier lieu. Mais tu le savais. ( apple.co/1SNGtMQ )
GoZoner

Réponses:

170

Comme mentionné, dans Swift la plupart du temps, vous pouvez obtenir ce dont vous avez besoin avec l' ?opérateur de déballage en option . Cela vous permet d'appeler une méthode sur un objet si et seulement si l'objet existe (pas nil) et que la méthode est implémentée.

Dans le cas où vous en avez encore besoin respondsToSelector:, il est toujours là dans le cadre du NSObjectprotocole.

Si vous appelez respondsToSelector:un type Obj-C dans Swift, cela fonctionne de la même manière que vous vous attendez. Si vous l'utilisez dans votre propre classe Swift, vous devrez vous assurer que votre classe dérive NSObject.

Voici un exemple de classe Swift que vous pouvez vérifier si elle répond à un sélecteur:

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

Il est important de ne pas omettre les noms de paramètres. Dans cet exemple, Selector("sleep::")n'est pas identique à Selector("sleep:minutes:").

Erik
la source
J'essaie de déterminer si une nouvelle méthode introduite dans iOS8 à UIApplication est présente et jusqu'à présent, je n'ai pas de chance. Voici comment j'essaye selon ce qui précède: if let present = UIApplication.sharedApplication (). RespondsToSelector (Selector ("redactedAsStillUnderNDA")). Cependant, j'obtiens une erreur de compilation: "La valeur liée dans une liaison conditionnelle doit être de type facultatif".
Gruntcakes
4
Vous devrez diviser ces lignes ou supprimer la let x = partie. Fondamentalement, la if let x = ystructure consiste à dérouler les valeurs facultatives (similaires à !). Puisque vous obtenez un Boolretour de respondsToSelector, le compilateur se plaint que le résultat n'est pas un type facultatif ( Bool?).
Erik
Qu'arriverait-il aux vars déclarés? Répondent-ils toujours de la même manière? En Objective-C je pourrais faire if ( [obj respondsToSelector:@selector(setSomeVar:)] ) { ... }pour une someVarpropriété. Cela fonctionne-t-il de la même manière avec vars dans Swift?
Alejandro Iván
Que faire si l'instance de contrôleur de vue facultative n'est pas nulle, mais que la méthode ou la fonction n'est pas implémentée dans ce contrôleur? Je suppose que vous devrez vérifier si l'objet répond au sélecteur ou non.
Ashish Pisey
Je reçois "La valeur du type 'UIViewController' n'a pas de membre 'respondsToSelector'"
Michał Ziobro
59

Il n'y a pas de véritable remplacement Swift.

Vous pouvez vérifier de la manière suivante:

someObject.someMethod?()

Cela appelle la méthode someMethoduniquement si elle est définie sur l'objet someObjectmais vous ne pouvez l'utiliser que pour les @objcprotocoles qui ont déclaré la méthode comme optional.

Swift est intrinsèquement un langage sûr, donc chaque fois que vous appelez une méthode, Swift doit savoir que la méthode est là. Aucune vérification d'exécution n'est possible. Vous ne pouvez pas simplement appeler des méthodes aléatoires sur des objets aléatoires.

Même dans Obj-C, vous devriez éviter de telles choses lorsque cela est possible car il ne fonctionne pas bien avec ARC (ARC déclenche alors des avertissements pour performSelector:).

Cependant, lors de la vérification des API disponibles, vous pouvez toujours utiliser respondsToSelector:, même si Swift, si vous avez affaire à des NSObjectinstances:

@interface TestA : NSObject

- (void)someMethod;

@end

@implementation TestA

//this triggers a warning

@end   


var a = TestA()

if a.respondsToSelector("someMethod") {
   a.someMethod()
}
Sulthan
la source
1
Par conséquent, les applications doivent maintenant savoir explicitement sur quelle version du système d'exploitation elles s'exécutent? Je veux appeler une méthode qui n'existe que dans iOS8 et si ce n'est pas le cas, utiliser l'ancienne version iOS7. Donc, au lieu de vérifier la présence / l'absence de la nouvelle méthode, je dois plutôt savoir si je suis sur iOS8 ou non? Swift est bon mais cela semble un peu maladroit.
Gruntcakes
@sulthan Je ne sais pas d'où vous venez en déclarant que vous ne devriez pas utiliser respondsToSelector:car la recommandation d'Apple est explicitement que vous devriez le faire. Voir ici
David Berry
@ David Oui, vous avez trouvé l'un des rares cas où respondsToSelector:c'est vraiment bien d'avoir. Mais si vous passez par la référence Swift, ils parlent d'utiliser des sous-classes pour différentes versions du système.
Sulthan
Si ce n'est pas le cas dont parle l'OP, et qu'ils parlent du cas du protocole facultatif, ils autorisent également explicitement cela en utilisant le chaînage facultatif (comme vous l'avez souligné). Quoi qu'il en soit, dire "vous devez éviter de faire ce qu'Apple vous a explicitement dit de faire" semble être un mauvais conseil. Apple a "toujours" fourni des mécanismes pour déterminer et exercer des fonctionnalités optionnelles au moment de l'exécution.
David Berry
1
@Honey Dans Swift, nous utilisons généralement des directives de disponibilité à la place, par exemple if #available(iOS 10) {, puis appelons directement la méthode.
Sulthan
40

Mise à jour du 20 mars 2017 pour la syntaxe Swift 3:

Si vous ne vous souciez pas de savoir si la méthode facultative existe, appelez simplement delegate?.optionalMethod?()

Sinon, l'utilisation guardest probablement la meilleure approche:

weak var delegate: SomeDelegateWithOptionals?

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

Réponse originale:

Vous pouvez utiliser l'approche "if let" pour tester un protocole facultatif comme celui-ci:

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
la source
4
si laissez theMethod = delegate? .theOptionalProtocolMethod
john07
1
Exemple de syntaxe de sélecteur lorsqu'il y a plusieurs candidats:let theMethod = delegate.userNotificationCenter(_:willPresent:withCompletionHandler:)
Cœur
11

Il semble que vous deviez définir votre protocole comme un sous-protocole de NSObjectProtocol ... alors vous obtiendrez la méthode respondsToSelector

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

notez que la seule spécification de @objc n'était pas suffisante. Vous devez également faire attention à ce que le délégué réel soit une sous-classe de NSObject - ce qui dans Swift ne l'est peut-être pas.

Matej Ukmar
la source
10

Si la méthode que vous testez est définie comme une méthode facultative dans un protocole @objc (qui ressemble à votre cas), utilisez le modèle de chaînage facultatif comme suit :

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

Lorsque la méthode est déclarée comme retournant Void, utilisez simplement:

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

Voir:

«Méthodes d'appel via le chaînage facultatif»
Extrait de: Apple Inc. «Le langage de programmation Swift».
iBooks. https://itun.es/us/jEUH0.l

GoZoner
la source
Cela ne fonctionnerait que si la méthode retourne quelque chose, et s'il s'agit d'une méthode void?
Gruntcakes
1
Si void utilisez simplement if object.method?(args) { ... }- l'appel de la méthode, lorsqu'elle existe, reviendra, Voidce qui n'est pas le casnil
GoZoner
1
Cependant, cela se traduit par "Type Void n'est pas conforme au protocole" Logic Value ""
Gruntcakes
Veuillez consulter la documentation référencée (page 311-312).
GoZoner
"Si la méthode que vous testez est définie comme une méthode facultative dans un protocole @objc" Ou si elle objectest de type AnyObject, vous pouvez tester n'importe quelle méthode @objc.
newacct
9

Les fonctions sont des types de première classe dans Swift, vous pouvez donc vérifier si une fonction facultative définie dans un protocole a été implémentée en la comparant à nil:

if (someObject.someMethod != nil) {
    someObject.someMethod!(someArgument)
} else {
    // do something else
}
Christopher Pickslay
la source
1
Et si la méthode est surchargée? Comment pouvons-nous en vérifier un en particulier?
Nuno Gonçalves
8

Pour swift3

Si vous souhaitez simplement appeler la méthode, exécutez le code ci-dessous.

self.delegate?.method?()

Jason Yu
la source
7

Dans Swift 2, Apple a introduit une nouvelle fonctionnalité appelée API availability checking, qui pourrait remplacer la respondsToSelector:méthode.La comparaison d' extraits de code suivante est copiée à partir de la session 106 de la WWDC2015 Quoi de neuf dans Swift qui, à mon avis, pourrait vous aider, veuillez la vérifier si vous en avez besoin savoir plus.

L'ancienne approche:

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

La meilleure approche:

@IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
    if #available(OSX 10.10.3, *) {
        dropButton.springLoaded = true
    }
}
tounaobun
la source
Je pense que c'est une bien meilleure solution que d'utiliser l' respondsToSelectorapproche dans Swift. C'est aussi parce que la vérification du sélecteur n'était pas la meilleure solution à ce problème (vérification de la disponibilité) en premier lieu.
JanApotheker
6

Pour 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
la source
4

Actuellement (Swift 2.1), vous pouvez le vérifier de 3 manières:

  1. En utilisant respondsToSelector a répondu par @Erik_at_Digit
  2. Utiliser "?" répondu par @Sulthan

  3. Et en utilisant l' as?opérateur:

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

Fondamentalement, cela dépend de ce que vous essayez de réaliser:

  • Si, par exemple, la logique de votre application doit effectuer une action et que le délégué n'est pas défini ou que le délégué pointé n'a pas implémenté la méthode onSuccess () (méthode de protocole), les options 1 et 3 sont le meilleur choix, bien que j'utiliserais option 3 qui est la voie Swift.
  • Si vous ne voulez rien faire lorsque le délégué est nul ou que la méthode n'est pas implémentée, utilisez l'option 2.
OhadM
la source
2

Je viens de l'implémenter moi-même dans un projet, voir le code ci-dessous. Comme le mentionne @Christopher Pickslay, il est important de se rappeler que les fonctions sont des citoyens de première classe et peuvent donc être traitées comme des variables facultatives.

@objc protocol ContactDetailsDelegate: class {

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

...

weak var delegate:ContactDetailsDelegate!

if let deleteContact = delegate.deleteContact {
    deleteContact(contact)
}
Bulwinkel
la source
Et si la méthode est surchargée? Comment ferait-on cela?
Nuno Gonçalves
2

une autre syntaxe possible par swift ..

 if let delegate = self.delegate, method = delegate.somemethod{
        method()
    }
Janub
la source
2

Comme j'ai commencé à mettre à jour mon ancien projet vers Swift 3.2, je devais juste changer la méthode de

respondsToSelector(selector)

à:

responds(to: selector)
chAlexey
la source
0

J'utilise guard let else, donc cela peut faire des trucs par défaut si le func délégué n'est pas implémenté.

@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
la source
-1

Swift 3:

protocole

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

Objet

class SomeObject : NSObject {

weak var delegate:SomeObject?

func delegateMethod() {

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

   }

}
etzuk
la source
-4

L'équivalent est le? opérateur:

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

importantMethod ne sera appelé que si myQuestionableObject existe et l'implémente.

Ben Gottlieb
la source
Mais que se passe-t-il si myQuestionalObject est quelque chose comme UIApplication (qui va donc exister et donc vérifier son existence n'a pas de sens) et importandMethod () est une nouvelle méthode introduite dans iOS N et vous voulez déterminer si vous pouvez l'appeler ou si vous devez appeler le l'ancien obsolèteImportantMethod () sur iOS N-1?
Gruntcakes
2
vous avez également besoin d'un ?aprèsimportantMethod
newacct