Який еквівалент 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.