Який еквівалент Swift ResponseToSelector?


195

Я гуглив, але не зміг дізнатися, що таке швидкий еквівалент respondsToSelector:.

Це єдине, що я міг знайти ( Swift альтернатива respondsToSelector:), але в моєму випадку не надто актуальна, оскільки перевірка існування делегата, у мене немає делегата, я просто хочу перевірити, чи існує новий API чи ні під час роботи на пристрої, і якщо не повернутися до попередньої версії api.


Все це повинно бути замінено на Факультативні та здійснюватись з додатковим ланцюжком
— Джек

Зважаючи на те, що Apple явно рекомендує використовувати NSClassFromStringта respondsToSelectorсеред інших механізмів для перевірки наявних функціональних можливостей, я мушу вважати, що механізми або вже існують, або будуть там перед випуском. Спробуйте переглянути Advanced Interop...відео з WWDC.
— Девід Беррі

2
@Jack Wu Але що робити, якщо новий метод є чимось новим, запроваджений на чомусь фундаментальному, наприклад, UIApplication або UIViewController. Ці об'єкти необов'язкові, і новий метод не є необов'язковим. Як ви можете перевірити, чи потрібно викликати, наприклад, UIApplcation: newVersionOfMethodX, або ви повинні викликати UIApplication: deprecatedVersionOfMethodX? (Враховуючи, що ви можете створити та запустити додаток у Swift на iOS 7, це буде дуже поширеним сценарієм)
— Gruntcakes

Чи є API, який ви / стосувались Apple API?
— GoZoner

@PotaciumPermanganate - звичайно, якщо відповідь "так, я використовував API Apple", то, можливо, він може використовувати if #available(...)в Swift 2.x, щоб уникнути використання respondsToSelectorв першу чергу. Але ти це знав. ( apple.co/1SNGtMQ )
— GoZoner

Відповіді:


170

Як згадувалося, у Swift більшу частину часу ви можете досягти того, що вам потрібно, за допомогою ?додаткового оператора розгортання . Це дозволяє викликати метод на об'єкті лише тоді, коли об'єкт існує (немає nil) і метод реалізований.

У випадку, коли вам все-таки потрібно respondsToSelector:, він все ще є частиною NSObjectпротоколу.

Якщо ви телефонуєте respondsToSelector:типу Obj-C у Swift, він працює так само, як ви очікували. Якщо ви використовуєте його в своєму власному класі Swift, вам потрібно буде переконатися в тому, що ваш клас походить NSObject.

Ось приклад класу Swift, який ви можете перевірити, чи відповідає він селектору:

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

Важливо, щоб ви не залишали поза іменами параметрів. У цьому прикладі, Selector("sleep::")це НЕ те ж саме , як Selector("sleep:minutes:").


Я намагаюся визначити, чи є новий метод, введений в iOS8 до UIApplication, і поки що мені не пощастило. Ось як я намагаюся відповідно до вищезазначеного: якщо нехай присутній = UIApplication.sharedApplication (). RespondsToSelector (Selector ("редактованоAsStillUnderNDA")). Однак я отримую помилку компіляції: "Зв'язане значення в умовному прив'язці повинно бути необов'язкового типу".
— Грунткейк

4
Вам потрібно буде розділити ці рядки або видалити let x = частину. В основному, if let x = yструктура полягає в розгортанні необов'язкових значень (подібних до !). Оскільки ви Boolповертаєтеся назад respondsToSelector, компілятор скаржиться, що результат не є необов'язковим типом ( Bool?).
— Ерік

Що буде varз декларованими s? Вони все ще реагують однаково? У Objective-C я міг би зробити if ( [obj respondsToSelector:@selector(setSomeVar:)] ) { ... }для someVarвласності. Це працює так само, як varі у Swift?
— Алехандро Іван

Що робити, якщо необов'язковий екземпляр контролера не є нульовим, але метод або функція не реалізована в цьому контролері? Я думаю, вам потрібно буде перевірити, чи відповідає об'єкт на селектор чи ні.
— Ашиш Пісей

Я отримую "Значення типу" UIViewController "не має учасників" respondsToSelector ""
— Michał Ziobro

59

Немає реальної заміни Swift.

Ви можете перевірити таким чином:

someObject.someMethod?()

Це викликає метод someMethod лише в тому випадку, якщо він визначений на об'єкті, someObjectале ви можете використовувати його лише для @objcпротоколів, які оголосили метод як optional.

Swift по суті є безпечною мовою, тому щоразу, коли ви викликаєте метод, Swift повинен знати, що метод є. Неможливо перевірити час виконання. Не можна просто викликати випадкові методи на випадкових об'єктах.

Навіть у Obj-C вам слід уникати подібних речей, коли це можливо, оскільки це не добре грає з ARC (ARC тоді запускає попередження для performSelector: ).

Однак, перевіряючи доступні API, ви все одно можете використовувати respondsToSelector:, навіть якщо Swift, якщо ви маєте справу з NSObjectекземплярами:

@interface TestA : NSObject

- (void)someMethod;

@end

@implementation TestA

//this triggers a warning

@end   


var a = TestA()

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

1
Отже, тепер додатки повинні чітко знати, на якій версії ОС вони працюють? Я хочу викликати метод, який існує лише в iOS8, і якщо його немає, використовуйте стару версію iOS7. Тож замість того, щоб перевіряти наявність / відсутність нового методу, натомість я повинен з’ясувати, чи я на iOS8 чи ні? Стріфт хороший, але це здається трохи незграбним.
— Gruntcakes

@sulthan Я не впевнений, звідки ви беретеся, заявивши, що ви не повинні використовувати, respondsToSelector:оскільки рекомендація Apple явно, що ви повинні це робити. Дивіться тут
— Девід Беррі

@David Так, ви знайшли один з небагатьох випадків, коли respondsToSelector:насправді добре мати. Але якщо ви переходите через посилання Swift, вони говорять про використання підкласів для різних версій системи.
— Султан

Якщо це не так, про що йдеться в ОП, і вони говорять про необов'язковий випадок протоколу, вони також явно дозволяють це використовувати за допомогою додаткового ланцюжка (як ви вказали). У будь-якому випадку, сказати, що "вам слід уникати робити те, що Apple явно сказав вам робити", здається поганою порадою. Apple "завжди" передбачала механізми визначення та здійснення додаткової функціональності під час виконання.
— Девід Беррі

1
@Honey У Swift ми зазвичай використовуємо директиви щодо доступності, наприклад, if #available(iOS 10) {а потім безпосередньо викликаємо метод.
— Султан

40

Оновлення 20 березня 2017 року для синтаксису Swift 3:

Якщо вам все одно, чи існує необов'язковий метод, просто зателефонуйте delegate?.optionalMethod?()

В іншому випадку використання guard, мабуть, найкращий підхід:

weak var delegate: SomeDelegateWithOptionals?

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

Оригінальна відповідь:

Ви можете використовувати підхід "якщо дозволити" для тестування необов'язкового протоколу на зразок цього:

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
якщо дозволити theMethod = делегувати? .theOptionsProtocolMethod
— john07

1
Приклад синтаксису селектора, коли є кілька кандидатів:let theMethod = delegate.userNotificationCenter(_:willPresent:withCompletionHandler:)
— Cœur

11

Здається, вам потрібно визначити ваш протокол як підпротокол NSObjectProtocol ... тоді ви отримаєте метод respondsToSelector

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

зауважте, що лише вказати @objc було недостатньо. Ви також повинні бути обережними, що фактичним делегатом є підклас NSObject - чого у Swift може не бути.


10

Якщо метод, для якого ви тестуєте, визначений як необов'язковий метод у протоколі @objc (який звучить як ваш випадок), тоді використовуйте необов’язковий шаблон ланцюга як:

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

Коли метод оголошується повернутим Void, просто використовуйте:

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

Побачити:

"Методи виклику за допомогою додаткового ланцюга"
Витяг з: Apple Inc. "Швидка мова програмування".
iBooks. https://itun.es/us/jEUH0.l


Це спрацює лише в тому випадку, якщо метод щось поверне, що робити, якщо це недійсний метод?
— Gruntcakes

1
Якщо недійсне використання просто if object.method?(args) { ... }- виклик методу, коли він існує, повернеться, Voidякого немаєnil
— GoZoner

1
Однак у результаті "Тип Void не відповідає протоколу" Логічне значення ""
— Gruntcakes

Будь ласка, дивіться посилану документацію (стор. 311-312).
— GoZoner

"Якщо метод, для якого ви тестуєте, визначений як необов'язковий метод у протоколі @objc" Або якщо він objectмає тип AnyObject, ви можете протестувати будь-який метод @objc.
— newacct

9

Функції є першокласними типами в Swift, тому ви можете перевірити, чи була реалізована необов'язкова функція, визначена в протоколі, порівнявши її з нульовою:

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

1
Що робити, якщо метод перевантажений? Як ми можемо перевірити конкретний?
— Нуно Гонсальвес

8

Для швидкого3

Якщо ви просто хочете викликати метод, запустіть код нижче.

self.delegate?.method?()


7

У Swift 2 Apple представила нову функцію під назвою API availability checking, яка може бути заміною respondsToSelector:методу. Наступне порівняння фрагментів коду скопійовано із сесії WWDC2015 106 Що нового у Swift, і я подумав, що може допомогти вам, будь ласка, перевірте, чи потрібно знати більше.

Старий підхід:

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

Кращий підхід:

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

Я думаю, що це набагато краще рішення, ніж використання respondsToSelectorпідходу в Swift. Це також тому, що перевірка селектора була не найкращим рішенням цієї проблеми (перевірка наявності) в першу чергу.
— JanApotheker

6

Для швидкого 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

Наразі (Swift 2.1) ви можете перевірити це за допомогою 3-х способів:

  1. Використовуючи відповідіToSelector, відповів @Erik_at_Digit
  2. Використання "?" відповів @Sulthan

  3. І за допомогою as?оператора:

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

В основному це залежить від того, що ви намагаєтеся досягти:

  • Якщо, наприклад, вашій програмі логіці потрібно виконати якусь дію, і делегат не встановлений, або вказаний делегат не реалізував метод onSuccess () (метод протоколу), тому параметри 1 і 3 - найкращий вибір, хоча я б використовував варіант 3, який є швидким способом.
  • Якщо ви не хочете нічого робити, коли делегат нульовий або метод не реалізований, скористайтеся опцією 2.

2

Я просто реалізую це сам у проекті, дивіться код нижче. Як згадує @Christopher Pickslay, важливо пам’ятати, що функції є громадянами першого класу, і тому їх можна трактувати як необов'язкові змінні.

@objc protocol ContactDetailsDelegate: class {

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

...

weak var delegate:ContactDetailsDelegate!

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

Що робити, якщо метод перевантажений? Як би це зробити?
— Nuno Gonçalves

2

інший можливий синтаксис від swift ..

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

2

Коли я почав оновлювати свій старий проект на Swift 3.2, мені просто потрібно було змінити метод

respondsToSelector(selector)

до:

responds(to: selector)

0

Я використовую guard let else, так що я можу робити деякі речі за замовчуванням, якщо функція делегата не реалізована.

@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

Швидкий 3:

протокол

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

Об'єкт

class SomeObject : NSObject {

weak var delegate:SomeObject?

func delegateMethod() {

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

   }

}

-4

Еквівалент є? оператор:

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

ВажливийМетод буде викликатися лише в тому випадку, якщо існує myQuestionableObject і реалізує його.


Але що робити, якщо myQuestionalObject є чимось на зразок UIApplication (який, отже, буде існувати і, таким чином, перевірити його існування безглуздо), а importandMethod () - це новий метод, введений в iOS N, і ви хочете визначити, чи можете ви його викликати чи потрібно викликати старий застарілий важливий метод () на iOS N-1?
— Gruntcakes

2
вам також потрібно ?післяimportantMethod
— newacct
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.