Что такое Swift-эквивалент responsedsToSelector?

195

Я погуглил, но не смог выяснить, что такое быстрый эквивалент respondsToSelector:.

Это единственное, что я могу найти ( альтернатива Swift для responsedsToSelector:), но это не слишком уместно в моем случае, так как он проверяет наличие делегата, у меня нет делегата, я просто хочу проверить, существует ли новый API или нет при запуске на устройстве и, если нет, вернуться к предыдущей версии API.

Gruntcakes
источник
Все они предназначены для замены опциональными и осуществляются с опциональной цепочкой
Джек
Учитывая, что Apple явно рекомендует использовать NSClassFromStringи respondsToSelectorсреди других механизмов для проверки недавно реализованной функциональности, я должен верить, что механизмы либо уже существуют, либо будут там до выпуска. Попробуйте посмотреть Advanced Interop...видео с WWDC.
Дэвид Берри
2
@Jack Wu Но что, если новый метод - это что-то новое, введенное в нечто фундаментальное, такое как UIApplication или UIViewController. Эти объекты не являются обязательными, и новый метод не является обязательным. Как вы можете проверить, нужно ли вам вызывать, например, UIApplcation: newVersionOfMethodX или вы должны вызывать UIApplication: deprecatedVersionOfMethodX? (Учитывая, что вы можете создать и запустить приложение в Swift на iOS 7, это будет очень распространенный сценарий)
Gruntcakes
Является ли API, которым вы занимались, или интересовался Apple API?
GoZoner
@PotaniumPermanganate - конечно, если ответ «да, я использовал Apple API», то, возможно, он может использовать 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:").

Erik
источник
Я пытаюсь определить, присутствует ли новый метод, введенный в iOS8 для UIApplication, и пока что мне не везет. Вот как я пытаюсь в соответствии с вышеизложенным: если пусть present = UIApplication.sharedApplication (). RespondsToSelector (Selector ("redactedAsStillUnderNDA")). Однако я получаю ошибку компиляции: «Связанное значение в условной привязке должно быть необязательного типа».
Gruntcakes
4
Вам нужно будет разделить эти строки или удалить let x = часть. По сути, if let x = yструктура заключается в развертывании необязательных значений (аналогично !). Поскольку вы получаете Boolответ respondsToSelector, компилятор жалуется, что результат не является необязательным типом ( Bool?).
Эрик
Что случилось бы varс объявленным? Они по-прежнему реагируют одинаково? В Objective-C я мог бы сделать if ( [obj respondsToSelector:@selector(setSomeVar:)] ) { ... }для someVarсобственности. Работает ли так же с vars в Swift?
Алехандро Иван
Что, если необязательный экземпляр View контроллера не равен nil, но метод или функция не реализованы в этом контроллере? Я думаю, вам нужно будет проверить, реагирует ли объект на селектор или нет.
Ашиш Писей
Я получаю "Значение типа 'UIViewController' не имеет члена '
respdsToSelector
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()
}
Sulthan
источник
1
Поэтому приложения теперь должны четко знать, в какой версии ОС они работают? Я хочу вызвать метод, который существует только в iOS8, и если его там нет, используйте старую версию iOS7. Поэтому вместо того, чтобы проверять наличие / отсутствие нового метода, я должен выяснить, нахожусь ли я на iOS8 или нет? Свифт это хорошо, но это кажется немного неуклюжим.
Gruntcakes
@ Sulthan Я не уверен, откуда вы пришли, заявив, что вы не должны использовать, так respondsToSelector:как рекомендация Apple явно, что вы должны это делать. Смотрите здесь
Дэвид Берри
@ Дэвид Да, вы нашли один из немногих случаев, когда respondsToSelector:это действительно полезно иметь. Но если вы перейдете по ссылке на Swift, они говорят об использовании подклассов для разных версий системы.
Sulthan
Если это не тот случай, о котором говорит OP, и они говорят о случае необязательного протокола, то они также явно разрешают это, используя необязательную цепочку (как вы указали). В любом случае, говорить «вам следует избегать делать то, что Apple явно вам сказала», кажется плохим советом. Apple «всегда» предоставляет механизмы для определения и использования дополнительных функций во время выполнения.
Дэвид Берри
1
@Honey В Swift мы обычно используем вместо этого директивы доступности, например, if #available(iOS 10) {а затем вызываем метод напрямую.
Sulthan
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()
}
hyouuu
источник
4
если позволить theMethod = делегировать? .theOptionalProtocolMethod
john07
1
Пример синтаксиса селектора при наличии нескольких кандидатов:let theMethod = delegate.userNotificationCenter(_:willPresent:withCompletionHandler:)
Cœur
11

Кажется, вам нужно определить свой протокол как подпроток NSObjectProtocol ... тогда вы получите метод responsedsToSelector

@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. «Язык программирования Swift».
интерактивные книги. https://itun.es/us/jEUH0.l

GoZoner
источник
Это будет работать, только если метод возвращает что-то, что если это пустой метод?
Gruntcakes
1
Если void использовать просто if object.method?(args) { ... }- вызов метода, если он существует, вернет, Voidкоторого нетnil
GoZoner
1
Однако это приводит к тому, что «Тип Void не соответствует протоколу« Значение логики »»
Gruntcakes
Пожалуйста, смотрите ссылочную документацию (стр. 311-312).
GoZoner
«Если метод, который вы тестируете, определен как необязательный метод в протоколе @objc» Или, если он objectимеет тип AnyObject, вы можете протестировать любой метод @objc.
newacct
9

Функции являются первоклассными типами в Swift, поэтому вы можете проверить, была ли реализована дополнительная функция, определенная в протоколе, сравнив ее с nil:

if (someObject.someMethod != nil) {
    someObject.someMethod!(someArgument)
} else {
    // do something else
}
Кристофер Пикслей
источник
1
Что если метод перегружен? Как мы можем проверить для конкретного?
Нуно Гонсалвеш
8

Для swift3

Если вы просто хотите вызвать метод, запустите код ниже.

self.delegate?.method?()

Джейсон Ю
источник
7

В Swift 2 Apple представила новую функцию под названием API availability checking, которая может заменить respondsToSelector:метод. Следующее сравнение фрагментов кода скопировано из сеанса 106 WWDC2015 « Что нового в 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
    }
}
tounaobun
источник
Я думаю, что это гораздо лучшее решение, чем использование respondsToSelectorподхода в Swift. Это также потому, что проверка селектора не была лучшим решением этой проблемы (проверка доступности).
JanApotheker
6

Для 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

В настоящее время (Swift 2.1) вы можете проверить это тремя способами:

  1. Использование respondsToSelector ответил @Erik_at_Digit
  2. Используя '?' ответил @Sulthan

  3. И с помощью as?оператора:

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

В основном это зависит от того, чего вы пытаетесь достичь:

  • Например, если вашей логике приложения необходимо выполнить какое-либо действие, а делегат не установлен, или указанный делегат не реализовал метод onSuccess () (метод протокола), то варианты 1 и 3 - лучший выбор, хотя я бы использовал вариант 3, который является быстрым способом.
  • Если вы не хотите ничего делать, когда делегат равен nil или метод не реализован, используйте опцию 2.
OhadM
источник
2

Я просто сам реализую это в проекте, см. Код ниже. Как упоминает @Christopher Pickslay, важно помнить, что функции являются гражданами первого класса и поэтому могут рассматриваться как необязательные переменные.

@objc protocol ContactDetailsDelegate: class {

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

...

weak var delegate:ContactDetailsDelegate!

if let deleteContact = delegate.deleteContact {
    deleteContact(contact)
}
Bulwinkel
источник
Что если метод перегружен? Как можно это сделать?
Нуно Гонсалвеш
2

другой возможный синтаксис по быстрому ..

 if let delegate = self.delegate, method = delegate.somemethod{
        method()
    }
Janub
источник
2

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

respondsToSelector(selector)

чтобы:

responds(to: selector)
chAlexey
источник
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)")
    }
}
Dichen
источник
-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
     }

   }

}
etzuk
источник
-4

Эквивалент? оператор:

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

важный метод будет вызываться только в том случае, если myQuestionableObject существует и реализует его.

Бен Готлиб
источник
Но что, если myQuestionalObject является чем-то вроде UIApplication (который, следовательно, будет существовать и, следовательно, проверять его существование, не имеет смысла), а importandMethod () - это новый метод, представленный в iOS N, и вы хотите определить, можете ли вы вызывать его или вызывать старый устаревший ImportantMethod () на iOS N-1?
Gruntcakes
2
вам также нужно ?послеimportantMethod
newacct