Co to jest Swift równoważny z respondsToSelector?

195

Przeszukałem go, ale nie byłem w stanie dowiedzieć się, jaki jest szybki odpowiednik respondsToSelector:.

Jest to jedyna rzecz, jaką mogłem znaleźć ( szybka alternatywa dla respondsToSelector:), ale nie jest ona zbyt istotna w moim przypadku, ponieważ sprawdza obecność delegata, nie mam delegata, chcę tylko sprawdzić, czy istnieje nowy interfejs API lub nie, gdy działa na urządzeniu, a jeśli nie, powróć do poprzedniej wersji interfejsu API.

Gruntcakes
źródło
Wszystkie te mają być zastąpione Opcjonalnymi i ćwiczone z Opcjonalnym Łączeniem
Jack
Biorąc pod uwagę, że Apple wyraźnie zaleca używanie NSClassFromStringi respondsToSelectormiędzy innymi mechaniki do sprawdzania nowo zaimplementowanej funkcjonalności, muszę uwierzyć, że mechanizmy te już istnieją lub będą dostępne przed wydaniem. Spróbuj obejrzeć Advanced Interop...wideo z WWDC.
David Berry
2
@Jack Wu Ale co, jeśli nowa metoda jest czymś nowym wprowadzonym na czymś fundamentalnym, takim jak UIApplication lub UIViewController. Te obiekty nie są opcjonalne, a nowa metoda nie jest opcjonalna. Jak sprawdzić, czy musisz wywołać na przykład UIApplcation: newVersionOfMethodX, czy musisz wywołać UIApplication: deprecatedVersionOfMethodX? (Biorąc pod uwagę, że możesz zbudować i uruchomić aplikację w Swift na iOS 7, będzie to bardzo powszechny scenariusz)
Gruntcakes
Czy interfejs API dotyczy interfejsu API firmy Apple?
GoZoner,
@ PotasPermanganian - oczywiście, jeśli odpowiedź brzmi „tak, korzystałem z interfejsów API Apple”, być może może on użyć if #available(...)w Swift 2.x, aby uniknąć używania go respondsToSelectorw pierwszej kolejności. Ale ty to wiedziałeś. ( Apple.co/1SNGtMQ )
GoZoner

Odpowiedzi:

170

Jak wspomniano, w Swift przez większość czasu możesz osiągnąć to, czego potrzebujesz dzięki ?opcjonalnemu operatorowi odwijarki . Pozwala to na wywołanie metody na obiekcie wtedy i tylko wtedy, gdy obiekt istnieje (nie nil) i metoda jest zaimplementowana.

W przypadku, gdy nadal potrzebujesz respondsToSelector:, nadal jest dostępny jako część NSObjectprotokołu.

Jeśli respondsToSelector:w Swift wywołujesz typ Obj-C, to działa tak, jak byś się spodziewał. Jeśli używasz go we własnej klasie Swift, musisz upewnić się, że twoja klasa pochodzi NSObject.

Oto przykład klasy Swift, którą możesz sprawdzić, czy odpowiada na selektor:

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

Ważne jest, aby nie pominąć nazw parametrów. W tym przykładzie nieSelector("sleep::") jest taki sam jak .Selector("sleep:minutes:")

Erik
źródło
Próbuję ustalić, czy nowa metoda wprowadzona w iOS8 do UIApplication jest obecna i jak dotąd nie mam szczęścia. Oto jak próbuję zgodnie z powyższym: if let present = UIApplication.sharedApplication (). RespondsToSelector (Selector („redactedAsStillUnderNDA”)). Jednak pojawia się błąd kompilacji: „Wartość powiązana w powiązaniu warunkowym musi być typu opcjonalnego”.
Gruntcakes
4
Musisz podzielić te linie lub usunąć let x = część. Zasadniczo if let x = ystruktura ma rozpakowywać opcjonalne wartości (podobne do !). Ponieważ otrzymujesz odpowiedź Boolz powrotem respondsToSelector, kompilator narzeka, że ​​wynik nie jest typem opcjonalnym ( Bool?).
Erik
Co by się stało z varzadeklarowanym? Czy nadal reagują w ten sam sposób? W Objective-C mógłbym zrobić if ( [obj respondsToSelector:@selector(setSomeVar:)] ) { ... }dla someVarwłasności. Czy działa tak samo, jak vars w Swift?
Alejandro Iván
Co jeśli opcjonalna instancja kontrolera widoku nie ma wartości zero, ale metoda lub funkcja nie jest zaimplementowana w tym kontrolerze? Myślę, że będziesz musiał sprawdzić, czy obiekt reaguje na selektor, czy nie.
Ashish Pisey
Otrzymuję komunikat „Wartość typu„ UIViewController ”nie ma członka” odpowiadaToSelector ””
Michał Ziobro
59

Nie ma prawdziwej wymiany Swift.

Możesz to sprawdzić w następujący sposób:

someObject.someMethod?()

Wywołuje tę metodę someMethodtylko wtedy, gdy jest zdefiniowana na obiekcie, someObjectale można jej używać tylko w przypadku @objcprotokołów, które zadeklarowały metodę jako optional.

Swift jest z natury bezpiecznym językiem, więc za każdym razem, gdy wywołujesz metodę, Swift musi wiedzieć, że metoda istnieje. Sprawdzanie czasu wykonywania nie jest możliwe. Nie można po prostu wywoływać losowych metod na losowych obiektach.

Nawet w Obj-C powinieneś unikać takich rzeczy, gdy jest to możliwe, ponieważ nie działa to dobrze z ARC (ARC wywołuje wtedy ostrzeżenia performSelector:).

Jednak podczas sprawdzania dostępnych interfejsów API nadal możesz używać respondsToSelector:, nawet jeśli Swift, jeśli masz do czynienia z NSObjectinstancjami:

@interface TestA : NSObject

- (void)someMethod;

@end

@implementation TestA

//this triggers a warning

@end   


var a = TestA()

if a.respondsToSelector("someMethod") {
   a.someMethod()
}
Sułtan
źródło
1
Dlatego aplikacje muszą teraz wyraźnie wiedzieć, na której wersji systemu operacyjnego działają. Chcę wywołać metodę, która istnieje tylko w iOS8, a jeśli jej nie ma, użyj starej wersji iOS7. Więc zamiast sprawdzania obecności / nieobecności nowej metody muszę zamiast tego dowiedzieć się, czy korzystam z iOS8, czy nie? Szybki jest dobry, ale wydaje się nieco niezręczny.
Gruntcakes
@sulthan Nie jestem pewien, skąd pochodzisz, stwierdzając, że nie powinieneś używać, respondsToSelector:ponieważ zalecenie Apple wyraźnie mówi, że powinieneś to robić. Zobacz tutaj
David Berry,
@David Tak, znalazłeś jeden z niewielu przypadków, w których respondsToSelector:warto go mieć. Ale jeśli przejrzysz odniesienie do Swift, mówią o używaniu podklas dla różnych wersji systemu.
Sulthan
Jeśli tak nie jest, o czym mówi OP, a dotyczy opcjonalnego przypadku protokołu, to również jawnie zezwala na to, używając opcjonalnego łączenia (jak wskazałeś). Tak czy inaczej, powiedzenie „powinieneś unikać robienia tego, co wyraźnie powiedział ci Apple”, wydaje się złą radą. Firma Apple „zawsze” zapewniała mechanizmy określania i wykonywania opcjonalnych funkcji w czasie wykonywania.
David Berry,
1
@Honey W Swift zazwyczaj używamy zamiast tego dyrektyw dostępności, np. if #available(iOS 10) {A następnie wywołujemy metodę bezpośrednio.
Sulthan
40

Zaktualizuj 20 marca 2017 dla składni Swift 3:

Jeśli nie obchodzi Cię, czy istnieje opcjonalna metoda, po prostu wywołaj delegate?.optionalMethod?()

W przeciwnym razie użycie guardjest prawdopodobnie najlepszym podejściem:

weak var delegate: SomeDelegateWithOptionals?

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

Oryginalna odpowiedź:

Możesz użyć metody „jeśli pozwól”, aby przetestować opcjonalny protokół w następujący sposób:

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
źródło
4
jeśli niech theMethod = delegate? .theOptionalProtocolMethod
john07
1
Przykład składni selektora, gdy jest wielu kandydatów:let theMethod = delegate.userNotificationCenter(_:willPresent:withCompletionHandler:)
Cœur
11

Wygląda na to, że musisz zdefiniować swój protokół jako podprotokę NSObjectProtocol ... wtedy otrzymasz metodę ResponsToSelector

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

zwróć uwagę, że samo podanie @objc nie wystarczyło. Należy również uważać, aby faktyczny delegat był podklasą NSObject - co w Swift może nie być.

Matej Ukmar
źródło
10

Jeśli metoda, którą testujesz, jest zdefiniowana jako opcjonalna metoda w protokole @objc (która brzmi jak twoja sprawa), użyj opcjonalnego wzoru łączenia jako:

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

Gdy metoda jest deklarowana jako zwracana Void, po prostu użyj:

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

Widzieć:

„Metody wywoływania poprzez opcjonalne tworzenie łańcuchów”
Fragment: Apple Inc. „Swift Programming Language”.
iBooks. https://itun.es/us/jEUH0.l

GoZoner
źródło
Działa to tylko wtedy, gdy metoda zwraca coś, co jeśli jest to metoda void?
Gruntcakes
1
Jeśli nieważne, użyj po prostu if object.method?(args) { ... }- wywołanie metody, jeśli istnieje, zwróci się, Voidco nie jestnil
GoZoner
1
Jednak to powoduje, że „Type Void nie jest zgodny z protokołem„ Logic Value ””
Gruntcakes
Proszę zapoznać się z odnośną dokumentacją (strona 311-312).
GoZoner,
„Jeśli metoda, którą testujesz, jest zdefiniowana jako opcjonalna metoda w protokole @objc” lub jeśli objectma typ AnyObject, możesz przetestować dowolną metodę @objc.
newacct
9

Funkcje są pierwszorzędnymi typami w Swift, dzięki czemu można sprawdzić, czy zaimplementowano opcjonalną funkcję zdefiniowaną w protokole, porównując ją do zera:

if (someObject.someMethod != nil) {
    someObject.someMethod!(someArgument)
} else {
    // do something else
}
Christopher Pickslay
źródło
1
Co jeśli metoda jest przeciążona? Jak możemy sprawdzić konkretny?
Nuno Gonçalves,
8

Dla swift3

Jeśli chcesz tylko wywołać metodę, uruchom poniższy kod.

self.delegate?.method?()

Jason Yu
źródło
7

W Swift 2 Apple wprowadził nową funkcję o nazwie API availability checking, która może być zamiennikiem respondsToSelector:metody. Poniższe porównanie fragmentów kodu jest kopiowane z sesji WWDC2015 106 Co nowego w Swift, które moim zdaniem mogą ci pomóc, sprawdź to, jeśli potrzebujesz wiedzieć więcej.

Stare podejście:

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

Lepsze podejście:

@IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
    if #available(OSX 10.10.3, *) {
        dropButton.springLoaded = true
    }
}
tounaobun
źródło
Myślę, że to znacznie lepsze rozwiązanie niż stosowanie respondsToSelectorpodejścia w Swift. Jest tak również dlatego, że sprawdzenie selektora nie było najlepszym rozwiązaniem tego problemu (sprawdzanie dostępności).
JanApotheker
6

Dla szybkiego 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
źródło
4

Obecnie (Swift 2.1) możesz to sprawdzić na 3 sposoby:

  1. Korzystanie z respondsToSelector, na które odpowiedział @Erik_at_Digit
  2. Używasz „?” odpowiedział @Sulthan

  3. I za pomocą as?operatora:

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

Zasadniczo zależy to od tego, co próbujesz osiągnąć:

  • Jeśli na przykład logika aplikacji musi wykonać jakąś akcję, a delegat nie jest ustawiony lub wskazany delegat nie wdrożył metody onSuccess () (metoda protokołu), więc opcje 1 i 3 są najlepszym wyborem, chociaż użyłbym opcja 3, która jest szybka.
  • Jeśli nie chcesz nic robić, gdy delegowanie jest zerowe lub metoda nie jest zaimplementowana, użyj opcji 2.
OhadM
źródło
2

Po prostu implementuję to sam w projekcie, patrz kod poniżej. Jak wspomina @Christopher Pickslay, ważne jest, aby pamiętać, że funkcje są pierwszorzędnymi obywatelami i dlatego mogą być traktowane jak zmienne opcjonalne.

@objc protocol ContactDetailsDelegate: class {

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

...

weak var delegate:ContactDetailsDelegate!

if let deleteContact = delegate.deleteContact {
    deleteContact(contact)
}
Bulwinkel
źródło
Co jeśli metoda jest przeciążona? Jak by to zrobić?
Nuno Gonçalves,
2

inna możliwa składnia przez swift ..

 if let delegate = self.delegate, method = delegate.somemethod{
        method()
    }
Janub
źródło
2

Gdy zacząłem aktualizować mój stary projekt do Swift 3.2, po prostu musiałem zmienić metodę z

respondsToSelector(selector)

do:

responds(to: selector)
chAlexey
źródło
0

Używam guard let else, więc mogę zrobić pewne rzeczy domyślne, jeśli delegata func nie jest zaimplementowana.

@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
źródło
-1

Swift 3:

protokół

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

Obiekt

class SomeObject : NSObject {

weak var delegate:SomeObject?

func delegateMethod() {

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

   }

}
etzuk
źródło
-4

Odpowiednikiem jest? operator:

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

importantMethod zostanie wywołany tylko wtedy, gdy myQuestionableObject istnieje i implementuje go.

Ben Gottlieb
źródło
Ale co jeśli myQuestionalObject jest czymś w rodzaju UIApplication (który dlatego będzie istniał, a zatem sprawdzanie jego istnienia jest bez znaczenia), a importandMethod () to nowa metoda wprowadzona w iOS N i chcesz ustalić, czy możesz ją wywołać, czy też wywołać metodę stare przestarzałeImportantMethod () na iOS N-1?
Gruntcakes
2
potrzebujesz również ?poimportantMethod
newacct