Was ist das Swift-Äquivalent von respondsToSelector?

195

Ich habe gegoogelt, konnte aber nicht herausfinden, was das schnelle Äquivalent respondsToSelector:ist.

Dies ist das einzige, was ich finden konnte ( schnelle Alternative zu responsondsToSelector :), aber in meinem Fall nicht allzu relevant, da es die Existenz des Delegaten überprüft. Ich habe keinen Delegaten, den ich nur überprüfen möchte, ob eine neue API vorhanden ist oder nicht, wenn auf dem Gerät ausgeführt wird und wenn nicht auf eine frühere Version der API zurückgegriffen wird.

Gruntcakes
quelle
All dies soll durch Optionals ersetzt und mit optionaler Verkettung
Jack
In Anbetracht , dass Apple ausdrücklich empfiehlt die Verwendung NSClassFromStringund respondsToSelectorunter anderem Mechanik für die Überprüfung für neu implementierte Funktionalität, ich habe zu glauben , dass die Mechanismen , entweder an Ort und Stelle sind bereits oder wird es vor der Freigabe. Versuchen Sie, das Advanced Interop...Video von WWDC anzusehen.
David Berry
2
@ Jack Wu Aber was ist, wenn die neue Methode etwas Neues ist, das auf etwas Grundlegendem wie UIApplication oder UIViewController eingeführt wurde? Diese Objekte sind nicht optional und die neue Methode ist nicht optional. Wie können Sie überprüfen, ob Sie beispielsweise UIApplcation: newVersionOfMethodX oder UIApplication: deprecatedVersionOfMethodX aufrufen müssen? (Angesichts der Tatsache, dass Sie eine App in Swift unter iOS 7 erstellen und ausführen können, wird dies ein sehr häufiges Szenario sein.)
Gruntcakes
Handelt es sich bei der API um eine Apple-API?
GoZoner
@PotassiumPermanganate - Wenn die Antwort "Ja, ich habe Apple-APIs verwendet" lautet, kann er sie möglicherweise if #available(...)in Swift 2.x verwenden, um die Verwendung überhaupt zu vermeiden respondsToSelector. Aber das wusstest du. ( apple.co/1SNGtMQ )
GoZoner

Antworten:

170

Wie bereits erwähnt, können Sie in Swift die meiste Zeit mit dem ?optionalen Unrapper-Operator das erreichen, was Sie benötigen . Auf diese Weise können Sie eine Methode für ein Objekt genau dann aufrufen, wenn das Objekt vorhanden ist (nicht nil) und die Methode implementiert ist.

Falls Sie es noch benötigen respondsToSelector:, ist es als Teil des NSObjectProtokolls noch vorhanden.

Wenn Sie respondsToSelector:in Swift einen Obj-C-Typ aufrufen , funktioniert dies genauso, wie Sie es erwarten würden. Wenn Sie es in Ihrer eigenen Swift-Klasse verwenden, müssen Sie sicherstellen, dass Ihre Klasse von abgeleitet ist NSObject.

Hier ist ein Beispiel für eine Swift-Klasse, mit der Sie überprüfen können, ob sie auf einen Selektor reagiert:

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

Es ist wichtig, dass Sie die Parameternamen nicht auslassen. In diesem Beispiel Selector("sleep::")ist nicht dasselbe wie Selector("sleep:minutes:").

Erik
quelle
Ich versuche festzustellen, ob eine neue Methode vorhanden ist, die in iOS8 für UIApplication eingeführt wurde, und habe bisher kein Glück. Folgendes versuche ich wie folgt: if let present = UIApplication.sharedApplication (). RespondsToSelector (Selector ("redactedAsStillUnderNDA")). Es wird jedoch ein Kompilierungsfehler angezeigt: "Der gebundene Wert in einer bedingten Bindung muss vom optionalen Typ sein".
Gruntcakes
4
Sie müssen diese Zeilen aufteilen oder den let x = Teil entfernen . Grundsätzlich besteht die if let x = yStruktur darin, optionale Werte (ähnlich wie !) zu entpacken . Da Sie eine BoolRückmeldung erhalten respondsToSelector, beschwert sich der Compiler, dass das Ergebnis kein optionaler Typ ist ( Bool?).
Erik
Was würde mit vars deklariert passieren ? Reagieren sie immer noch genauso? In Objective-C könnte ich if ( [obj respondsToSelector:@selector(setSomeVar:)] ) { ... }für eine someVarImmobilie tun . Funktioniert es genauso mit vars in Swift?
Alejandro Iván
Was ist, wenn die optionale View-Controller-Instanz nicht Null ist, die Methode oder Funktion jedoch nicht in diesem Controller implementiert ist? Ich denke, Sie müssen überprüfen, ob das Objekt auf den Selektor reagiert oder nicht.
Ashish Pisey
Ich erhalte "Wert vom Typ 'UIViewController' hat kein Mitglied 'respondsToSelector'"
Michał Ziobro
59

Es gibt keinen echten Swift-Ersatz.

Sie können folgendermaßen prüfen:

someObject.someMethod?()

Dies ruft die Methode someMethodnur auf, wenn sie für ein Objekt definiert ist. someObjectSie können sie jedoch nur für @objcProtokolle verwenden, die die Methode als deklariert haben optional.

Swift ist von Natur aus eine sichere Sprache. Jedes Mal, wenn Sie eine Methode aufrufen, muss Swift wissen, dass die Methode vorhanden ist. Eine Laufzeitprüfung ist nicht möglich. Sie können nicht einfach zufällige Methoden für zufällige Objekte aufrufen.

Selbst in Obj-C sollten Sie solche Dinge nach Möglichkeit vermeiden, da sie mit ARC nicht gut funktionieren (ARC löst dann Warnungen für aus performSelector:).

Wenn Sie jedoch nach verfügbaren APIs suchen, können Sie respondsToSelector:auch dann noch Swift verwenden, wenn Sie mit NSObjectInstanzen arbeiten:

@interface TestA : NSObject

- (void)someMethod;

@end

@implementation TestA

//this triggers a warning

@end   


var a = TestA()

if a.respondsToSelector("someMethod") {
   a.someMethod()
}
Sulthan
quelle
1
Daher müssen Apps jetzt explizit wissen, auf welcher Version des Betriebssystems sie ausgeführt werden. Ich möchte eine Methode aufrufen, die nur in iOS8 vorhanden ist, und wenn sie nicht vorhanden ist, die alte iOS7-Version verwenden. Anstatt nach dem Vorhandensein / Fehlen der neuen Methode zu suchen, muss ich stattdessen herausfinden, ob ich auf iOS8 bin oder nicht? Swift ist gut, aber das scheint etwas klobig.
Gruntcakes
@sulthan Ich bin mir nicht sicher, woher du kommst, dass du nicht verwenden solltest, respondsToSelector:da Apples Empfehlung ausdrücklich lautet, dass du es tun sollst. Siehe hier
David Berry
@ David Ja, Sie haben einen der wenigen Fälle gefunden, in denen respondsToSelector:es wirklich gut ist, sie zu haben. Wenn Sie jedoch die Swift-Referenz durchgehen, wird über die Verwendung von Unterklassen für verschiedene Systemversionen gesprochen.
Sulthan
Wenn dies im OP nicht der Fall ist und es sich um den optionalen Protokollfall handelt, können sie dies auch explizit mithilfe der optionalen Verkettung zulassen (wie Sie bereits betont haben). In jedem Fall scheint es ein schlechter Rat zu sein, zu sagen, "Sie sollten es vermeiden, das zu tun, was Apple Ihnen ausdrücklich empfohlen hat". Apple hat "immer" Mechanismen zur Bestimmung und Ausübung optionaler Funktionen zur Laufzeit bereitgestellt.
David Berry
1
@Honey In Swift verwenden wir im Allgemeinen stattdessen Verfügbarkeitsanweisungen, z. B. if #available(iOS 10) {und rufen die Methode dann direkt auf.
Sulthan
40

Update 20. März 2017 für Swift 3-Syntax:

Wenn es Ihnen egal ist, ob die optionale Methode vorhanden ist, rufen Sie einfach auf delegate?.optionalMethod?()

Ansonsten ist die Verwendung guardwahrscheinlich der beste Ansatz:

weak var delegate: SomeDelegateWithOptionals?

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

Ursprüngliche Antwort:

Sie können den "if let" -Ansatz verwenden, um ein optionales Protokoll wie das folgende zu testen:

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
quelle
4
Wenn lassen Sie die Methode = delegieren? .theOptionalProtocolMethod
john07
1
Beispiel für Selektor Syntax , wenn es mehrere Kandidaten:let theMethod = delegate.userNotificationCenter(_:willPresent:withCompletionHandler:)
Cœur
11

Es scheint, dass Sie Ihr Protokoll als Unterprotokoll von NSObjectProtocol definieren müssen ... dann erhalten Sie die Methode respondsToSelector

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

Beachten Sie, dass es nicht ausreicht, nur @objc anzugeben. Sie sollten auch darauf achten, dass der eigentliche Delegat eine Unterklasse von NSObject ist - was in Swift möglicherweise nicht der Fall ist.

Matej Ukmar
quelle
10

Wenn die Methode, auf die Sie testen, als optionale Methode in einem @ objc- Protokoll definiert ist (was wie Ihr Fall klingt), verwenden Sie das optionale Verkettungsmuster wie folgt:

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

Wenn die Methode als zurückgegeben deklariert wird Void, verwenden Sie einfach:

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

Sehen:

"Aufrufen von Methoden durch optionale Verkettung"
Auszug aus: Apple Inc. "Die schnelle Programmiersprache".
iBooks. https://itun.es/us/jEUH0.l

GoZoner
quelle
Dies würde nur funktionieren, wenn die Methode etwas zurückgibt. Was ist, wenn es sich um eine void-Methode handelt?
Gruntcakes
1
Wenn nichtig, verwenden Sie einfach if object.method?(args) { ... }- der Aufruf der Methode, wenn es existiert, wird zurückkehren, Voidwas nicht istnil
GoZoner
1
Dies führt jedoch zu "Type Void entspricht nicht dem Protokoll" Logic Value "
Gruntcakes
Bitte beachten Sie die Dokumentation, auf die verwiesen wird (Seite 311-312).
GoZoner
"Wenn die Methode, auf die Sie testen, als optionale Methode in einem @ objc-Protokoll definiert ist" Oder wenn sie objecteinen Typ hat AnyObject, können Sie eine beliebige @ objc-Methode testen.
Newacct
9

Funktionen sind in Swift erstklassige Typen. Sie können also überprüfen, ob eine in einem Protokoll definierte optionale Funktion implementiert wurde, indem Sie sie mit nil vergleichen:

if (someObject.someMethod != nil) {
    someObject.someMethod!(someArgument)
} else {
    // do something else
}
Christopher Pickslay
quelle
1
Was ist, wenn die Methode überladen ist? Wie können wir nach einem bestimmten suchen?
Nuno Gonçalves
8

Für swift3

Wenn Sie nur die Methode aufrufen möchten, führen Sie den folgenden Code aus.

self.delegate?.method?()

Jason Yu
quelle
7

In Swift 2 hat Apple eine neue Funktion namens "eingeführt" API availability checking, die möglicherweise die respondsToSelector:Methode ersetzt. Der folgende Code-Snippet-Vergleich wurde aus der WWDC2015-Sitzung 106 " Was ist neu in Swift" kopiert, von der ich dachte, dass sie Ihnen helfen könnte mehr wissen.

Der alte Ansatz:

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

Der bessere Ansatz:

@IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
    if #available(OSX 10.10.3, *) {
        dropButton.springLoaded = true
    }
}
tounaobun
quelle
Ich denke, das ist eine weitaus bessere Lösung als der respondsToSelectorAnsatz in Swift. Dies liegt auch daran, dass die Auswahlprüfung nicht die beste Lösung für dieses Problem war (Verfügbarkeitsprüfung).
JanApotheker
6

Für schnelle 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
quelle
4

Derzeit (Swift 2.1) können Sie dies auf drei Arten überprüfen:

  1. Verwenden von respondsToSelector, beantwortet von @Erik_at_Digit
  2. Verwenden von '?' beantwortet von @Sulthan

  3. Und mit as?Operator:

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

Grundsätzlich hängt es davon ab, was Sie erreichen möchten:

  • Wenn zum Beispiel Ihre App-Logik eine Aktion ausführen muss und der Delegat nicht festgelegt ist oder der spitze Delegat die onSuccess () -Methode (Protokollmethode) nicht implementiert hat, sind Option 1 und 3 die beste Wahl, obwohl ich sie verwenden würde Option 3, die Swift Way ist.
  • Wenn Sie nichts tun möchten, wenn der Delegat Null ist oder die Methode nicht implementiert ist, verwenden Sie Option 2.
OhadM
quelle
2

Ich implementiere dies einfach selbst in einem Projekt, siehe Code unten. Wie von @Christopher Pickslay erwähnt, ist es wichtig zu bedenken, dass Funktionen erstklassige Bürger sind und daher wie optionale Variablen behandelt werden können.

@objc protocol ContactDetailsDelegate: class {

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

...

weak var delegate:ContactDetailsDelegate!

if let deleteContact = delegate.deleteContact {
    deleteContact(contact)
}
Bulwinkel
quelle
Was ist, wenn die Methode überladen ist? Wie würde man das machen?
Nuno Gonçalves
2

eine andere mögliche Syntax von Swift ..

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

Als ich anfing, mein altes Projekt auf Swift 3.2 zu aktualisieren, musste ich nur die Methode von ändern

respondsToSelector(selector)

zu:

responds(to: selector)
chAlexey
quelle
0

Ich benutze guard let else, damit das einige Standard-Sachen machen kann, wenn die Delegatenfunktion nicht implementiert ist.

@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
quelle
-1

Swift 3:

Protokoll

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

Objekt

class SomeObject : NSObject {

weak var delegate:SomeObject?

func delegateMethod() {

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

   }

}
etzuk
quelle
-4

Das Äquivalent ist das? Operator:

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

ImportantMethod wird nur aufgerufen, wenn myQuestionableObject vorhanden ist und es implementiert.

Ben Gottlieb
quelle
Was aber, wenn myQuestionalObject so etwas wie UIApplication ist (das also existieren wird und somit seine Existenz bedeutungslos ist) und importandMethod () eine neue Methode ist, die in iOS N eingeführt wurde und Sie bestimmen möchten, ob Sie es aufrufen können oder aufrufen müssen alt veraltetImportantMethod () unter iOS N-1?
Gruntcakes
2
Sie müssen auch ein ?nachimportantMethod
newacct