Qual è l'equivalente di Swift di respondsToSelector?


195

Ho cercato su Google ma non sono stato in grado di scoprire qual è il rapido equivalente respondsToSelector:.

Questa è l'unica cosa che ho potuto trovare ( alternativa rapida a respondsToSelector:) ma non è troppo rilevante nel mio caso in quanto verifica l'esistenza del delegato, non ho un delegato, voglio solo verificare se esiste una nuova API oppure no quando si esegue sul dispositivo e se non si ricorre a una versione precedente dell'API.


Tutti questi sono pensati per essere sostituiti con Optionals ed esercitati con il Concatenamento opzionale
Jack

Dato che Apple consiglia esplicitamente di utilizzare NSClassFromStringe, respondsToSelectortra le altre meccaniche, per verificare la funzionalità appena implementata, devo credere che i meccanismi o siano già in atto o saranno presenti prima del rilascio. Prova a guardare il Advanced Interop...video dal WWDC.
David Berry,

2
@ Jack Wu Ma cosa succede se il nuovo metodo è qualcosa di nuovo introdotto su qualcosa di fondamentale come UIApplication o UIViewController. Questi oggetti non sono opzionali e il nuovo metodo non è facoltativo. Come è possibile verificare se è necessario chiamare UIApplcation ad esempio: newVersionOfMethodX o è necessario chiamare UIApplication: deprecatedVersionOfMethodX? (Dato che puoi creare ed eseguire un'app in Swift su iOS 7, questo sarà uno scenario molto comune)
Gruntcakes,

L'API che ti interessa / era interessata a un'API Apple?
GoZoner,

@PotassiumPermanganate - ovviamente, se la risposta è "sì, stavo usando le API di Apple", forse potrebbe usare if #available(...)Swift 2.x per evitare di usarlo respondsToSelectorin primo luogo. Ma lo sapevi. ( apple.co/1SNGtMQ )
GoZoner

Risposte:


170

Come accennato, in Swift il più delle volte è possibile ottenere ciò di cui si ha bisogno con l' ?operatore opzionale del wrapper . Ciò consente di chiamare un metodo su un oggetto se e solo se l'oggetto esiste (nonil ) e il metodo è implementato.

Nel caso in cui sia ancora necessario respondsToSelector:, è ancora lì come parte delNSObject protocollo.

Se stai chiamando respondsToSelector: un tipo Obj-C in Swift, allora funziona esattamente come ti aspetteresti. Se lo stai usando nella tua classe Swift, dovrai assicurarti che la tua classe derivi NSObject.

Ecco un esempio di una classe Swift che puoi verificare se risponde a un selettore:

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

È importante non tralasciare i nomi dei parametri. In questo esempio, nonSelector("sleep::") è lo stesso di .Selector("sleep:minutes:")


Sto cercando di determinare se è presente un nuovo metodo introdotto in iOS8 per UIApplication e finora non ho avuto fortuna. Ecco come sto tentando in base a quanto sopra: if let present = UIApplication.sharedApplication (). RespondsToSelector (Selector ("redactedAsStillUnderNDA")). Tuttavia, viene visualizzato un errore di compilazione: "Il valore associato in un'associazione condizionale deve essere di tipo facoltativo".
Gruntcakes,

4
Dovrai dividere quelle linee o rimuovere la let x = parte. Fondamentalmente, la if let x = ystruttura deve scartare i valori opzionali (simili a !). Dal momento che stai ottenendo un Boolritorno da respondsToSelector, il compilatore si lamenta che il risultato non è un tipo facoltativo ( Bool?).
Erik,

Cosa succederebbe se fosse vardichiarato? Rispondono ancora allo stesso modo? In Objective-C potrei fare if ( [obj respondsToSelector:@selector(setSomeVar:)] ) { ... }per una someVarproprietà. Funziona allo stesso modo con vars in Swift?
Alejandro Iván,

Cosa fare se l'opzione Visualizza istanza controller opzionale non è nulla, ma il metodo o la funzione non sono implementati in quel controller? Suppongo che dovrai verificare se l'oggetto risponde al selettore o meno.
Ashish Pisey,

Ricevo "Il valore del tipo 'UIViewController' non ha membri 'respondsToSelector'"
Michał Ziobro

59

Non esiste una vera sostituzione Swift.

Puoi verificare nel modo seguente:

someObject.someMethod?()

Questo chiama il metodo someMethodsolo se è definito sull'oggetto someObjectma è possibile utilizzarlo solo per i @objcprotocolli che hanno dichiarato il metodo come optional.

Swift è intrinsecamente un linguaggio sicuro, quindi ogni volta che chiami un metodo Swift deve sapere che il metodo è lì. Nessun controllo di runtime è possibile. Non puoi semplicemente chiamare metodi casuali su oggetti casuali.

Anche in Obj-C dovresti evitare queste cose quando possibile perché non gioca bene con ARC (ARC quindi innesca avvisi performSelector:).

Tuttavia, quando si verificano le API disponibili, è comunque possibile utilizzare respondsToSelector:, anche se Swift, se si ha a che fare con le NSObjectistanze:

@interface TestA : NSObject

- (void)someMethod;

@end

@implementation TestA

//this triggers a warning

@end   


var a = TestA()

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

1
Quindi le app ora devono sapere esplicitamente su quale versione del sistema operativo sono in esecuzione? Voglio chiamare un metodo che esiste solo in iOS8 e se non c'è usa la vecchia versione di iOS7. Quindi, invece di verificare la presenza / assenza del nuovo metodo, devo invece scoprire se sono su iOS8 o no? Swift è buono ma questo sembra un po 'goffo.
Gruntcakes,

@sulthan Non sono sicuro da dove vieni affermando che non dovresti usare respondsToSelector:come la raccomandazione di Apple è esplicitamente che dovresti farlo. Vedi qui
David Berry,

@David Sì, hai trovato uno dei pochi casi in cui respondsToSelector:è davvero bello avere. Ma se si passa attraverso il riferimento Swift, parlano dell'utilizzo di sottoclassi per diverse versioni del sistema.
Sulthan,

Se non è questo il caso di cui parla l'OP e stanno parlando del caso del protocollo opzionale, allora lo consentono anche esplicitamente usando il concatenamento opzionale (come hai sottolineato). Ad ogni modo, dire "dovresti evitare di fare ciò che Apple ti ha esplicitamente detto di fare" sembra un cattivo consiglio. Apple ha "sempre" fornito meccanismi per determinare ed esercitare funzionalità opzionali in fase di esecuzione.
David Berry,

1
@Honey In Swift generalmente utilizziamo invece direttive sulla disponibilità, ad es. if #available(iOS 10) {E poi chiamiamo direttamente il metodo.
Sulthan,

40

Aggiornamento del 20 marzo 2017 per la sintassi di Swift 3:

Se non ti importa se esiste il metodo opzionale, chiama delegate?.optionalMethod?()

Altrimenti, l'utilizzo guardè probabilmente l'approccio migliore:

weak var delegate: SomeDelegateWithOptionals?

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

Risposta originale:

È possibile utilizzare l'approccio "if let" per testare un protocollo opzionale come questo:

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

4
if let theMethod = delegate? .theOptionalProtocolMethod
john07

1
Esempio di sintassi del selettore in presenza di più candidati:let theMethod = delegate.userNotificationCenter(_:willPresent:withCompletionHandler:)
Cœur

11

Sembra che tu debba definire il tuo protocollo come sottoprotocollo di NSObjectProtocol ... quindi otterrai il metodo di risposta

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

notare che solo specificare @objc non è stato sufficiente. Dovresti anche fare attenzione che il delegato effettivo sia una sottoclasse di NSObject, che in Swift potrebbe non esserlo.


10

Se il metodo per il quale stai testando è definito come un metodo opzionale in un protocollo @objc (che suona come il tuo caso), usa il modello di concatenamento opzionale come:

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

Quando il metodo viene dichiarato come ritorno Void, utilizzare semplicemente:

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

Vedere:

"Metodi di chiamata tramite concatenamento opzionale"
Estratto da: Apple Inc. "Il linguaggio di programmazione Swift".
iBook. https://itun.es/us/jEUH0.l


Funzionerebbe solo se il metodo restituisse qualcosa, e se fosse un metodo vuoto?
Gruntcakes,

1
Se void usa semplicemente if object.method?(args) { ... }- la chiamata del metodo, quando esiste, restituirà ciò Voidche non lo ènil
GoZoner,

1
Tuttavia, il risultato "Tipo Void non è conforme al protocollo" Valore logico ""
Gruntcakes,

Consultare la documentazione di riferimento (pagina 311-312).
GoZoner,

"Se il metodo che stai testando è definito come metodo opzionale in un protocollo @objc" O se objectha un tipo AnyObject, puoi testare qualsiasi metodo @objc.
nuovo

9

Le funzioni sono tipi di prima classe in Swift, quindi puoi verificare se una funzione opzionale definita in un protocollo è stata implementata confrontandola con zero:

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

1
Cosa succede se il metodo è sovraccarico? Come possiamo verificarne uno specifico?
Nuno Gonçalves,

8

Per swift3

Se vuoi solo chiamare il metodo, esegui il codice qui sotto.

self.delegate?.method?()


7

In Swift 2, Apple ha introdotto una nuova funzionalità chiamata API availability checking, che potrebbe essere una sostituzione per il respondsToSelector:metodo. Il seguente confronto di frammenti di codice viene copiato dalla Sessione 106 WWDC2015 Novità di Swift che pensavo potesse aiutarti, controlla se hai bisogno di saperne di più.

Il vecchio approccio:

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

L'approccio migliore:

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

Penso che sia una soluzione molto migliore rispetto all'uso respondsToSelectordell'approccio in Swift. Questo anche perché il controllo selettore non era la soluzione migliore a questo problema (controllo disponibilità) in primo luogo.
JanApotheker,

6

Per 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

Attualmente (Swift 2.1) puoi verificarlo in 3 modi:

  1. Utilizzando respondsToSelector risposto @Erik_at_Digit
  2. Usando '?' ha risposto @Sulthan

  3. E usando l' as?operatore:

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

Fondamentalmente dipende da cosa stai cercando di ottenere:

  • Se ad esempio la logica della tua app deve eseguire alcune azioni e il delegato non è impostato o il delegato appuntito non ha implementato il metodo onSuccess () (metodo del protocollo), quindi le opzioni 1 e 3 sono la scelta migliore, anche se userei opzione 3 che è il modo rapido.
  • Se non si desidera eseguire alcuna operazione quando il delegato è nullo o il metodo non è implementato, utilizzare l'opzione 2.

2

Lo implemento da solo in un progetto, vedi il codice qui sotto. Come menzionato da @Christopher Pickslay, è importante ricordare che le funzioni sono cittadini di prima classe e possono quindi essere trattate come variabili opzionali.

@objc protocol ContactDetailsDelegate: class {

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

...

weak var delegate:ContactDetailsDelegate!

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

Cosa succede se il metodo è sovraccarico? Come si farebbe?
Nuno Gonçalves,

2

un'altra possibile sintassi di swift ..

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

2

Quando ho iniziato ad aggiornare il mio vecchio progetto a Swift 3.2, avevo solo bisogno di cambiare il metodo da

respondsToSelector(selector)

per:

responds(to: selector)

0

Io uso guard let else, in modo che possa fare alcune cose predefinite se la funzione delegato non è implementata.

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

-1

Swift 3:

protocollo

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

Oggetto

class SomeObject : NSObject {

weak var delegate:SomeObject?

func delegateMethod() {

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

   }

}

-4

L'equivalente è il? operatore:

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

importantMethod verrà chiamato solo se myQuestionableObject esiste e lo implementa.


Ma cosa succede se myQuestionalObject è qualcosa come UIApplication (che quindi esisterà e quindi verificarne l'esistenza non ha senso) e importandMethod () è un nuovo metodo introdotto in iOS N e vuoi determinare se puoi chiamarlo o chiamare vecchio deprecatedImportantMethod () su iOS N-1?
Gruntcakes,

2
hai anche bisogno di un ?afterimportantMethod
newacct
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.