¿Cuál es el equivalente rápido de respondsToSelector?

195

He buscado en Google pero no he podido averiguar cuál es el equivalente rápido de respondsToSelector: .

Esto es lo único que pude encontrar ( alternativa rápida a respondsToSelector:) pero no es demasiado relevante en mi caso, ya que verifica la existencia del delegado, no tengo un delegado, solo quiero verificar si existe una nueva API o no cuando se ejecuta en el dispositivo y, si no, recurrir a una versión anterior de la API.

Tortas Grunt
fuente
Todos estos están destinados a ser reemplazados con Opcionales, y ejercitados con Encadenamiento Opcional
Jack
Dado que Apple recomienda explícitamente usar NSClassFromStringy respondsToSelectorentre otras mecánicas para verificar la funcionalidad recientemente implementada, tengo que creer que los mecanismos ya están implementados o estarán allí antes del lanzamiento. Intenta ver el Advanced Interop...video de WWDC.
David Berry
2
@Jack Wu Pero, ¿y si el nuevo método es algo nuevo introducido en algo fundamental como UIApplication o UIViewController? Estos objetos no son opcionales y el nuevo método no es opcional. ¿Cómo puede verificar si debe llamar, por ejemplo, UIApplcation: newVersionOfMethodX o debe llamar a UIApplication: deprecatedVersionOfMethodX? (Dado que puede construir y ejecutar una aplicación en Swift en iOS 7, este será un escenario muy común)
Gruntcakes
¿La API que le preocupa / le preocupaba era una API de Apple?
GoZoner
@PotassiumPermanganate: por supuesto, si la respuesta es 'sí, estaba usando las API de Apple', entonces quizás pueda usar if #available(...)Swift 2.x para evitar el uso respondsToSelectoren primer lugar. Pero eso ya lo sabías. ( apple.co/1SNGtMQ )
GoZoner

Respuestas:

170

Como se mencionó, en Swift la mayoría de las veces puede lograr lo que necesita con el ?operador de desenvoltura opcional . Esto le permite llamar a un método en un objeto si y solo si el objeto existe (no nil) y el método está implementado.

En el caso en que aún lo necesite respondsToSelector:, todavía está allí como parte delNSObject protocolo.

Si está llamando respondsToSelector:a un tipo Obj-C en Swift, entonces funciona igual de lo esperado. Si lo está utilizando en su propia clase Swift, deberá asegurarse de que su clase se derive NSObject.

Aquí hay un ejemplo de una clase Swift que puede verificar si responde a un selector:

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 importante que no omita los nombres de los parámetros. En este ejemplo, noSelector("sleep::") es lo mismo que Selector("sleep:minutes:").

Erik
fuente
Estoy tratando de determinar si un nuevo método introducido en iOS8 para UIApplication está presente y hasta ahora no tengo suerte. Así es como estoy intentando de acuerdo con lo anterior: if let present = UIApplication.sharedApplication (). RespondsToSelector (Selector ("redactedAsStillUnderNDA")). Sin embargo, aparece un error de compilación: "El valor enlazado en un enlace condicional debe ser de tipo opcional".
Gruntcakes
44
Tendrá que dividir esas líneas o eliminar la let x = parte. Básicamente, la if let x = yestructura es desenvolver valores opcionales (similares a !). Dado que está recibiendo una Boolcopia de seguridad respondsToSelector, el compilador se queja de que el resultado no es un tipo opcional ( Bool?).
Erik
¿Qué pasaría con vars declarado? ¿Todavía responden de la misma manera? En Objective-C podría hacer if ( [obj respondsToSelector:@selector(setSomeVar:)] ) { ... }por una someVarpropiedad. ¿Funciona de la misma manera con vars en Swift?
Alejandro Iván
¿Qué sucede si la instancia del controlador de vista opcional no es nula, pero el método o la función no están implementados en ese controlador? Supongo que tendrá que verificar si el objeto responde al selector o no.
Ashish Pisey
Estoy obteniendo "El valor del tipo 'UIViewController' no tiene ningún miembro 'respondeToSelector'"
Michał Ziobro
59

No hay un reemplazo real de Swift.

Puede verificar de la siguiente manera:

someObject.someMethod?()

Esto llama al método someMethodsolo si está definido en un objeto, someObjectpero puede usarlo solo para @objcprotocolos que han declarado el método comooptional .

Swift es un lenguaje inherentemente seguro, por lo que cada vez que llama a un método Swift tiene que saber que el método está allí. No es posible verificar el tiempo de ejecución. No puede simplemente llamar a métodos aleatorios en objetos aleatorios.

Incluso en Obj-C debe evitar tales cosas cuando sea posible porque no funciona bien con ARC (ARC luego activa advertencias para performSelector: ).

Sin embargo, al verificar las API disponibles, aún puede usar respondsToSelector:, incluso si Swift, si se trata de NSObjectinstancias:

@interface TestA : NSObject

- (void)someMethod;

@end

@implementation TestA

//this triggers a warning

@end   


var a = TestA()

if a.respondsToSelector("someMethod") {
   a.someMethod()
}
Sulthan
fuente
1
Entonces, ¿las aplicaciones ahora tienen que saber explícitamente en qué versión del sistema operativo se están ejecutando? Quiero llamar a un método que solo existe en iOS8 y si no está allí, use la versión anterior de iOS7. Entonces, en lugar de verificar la presencia / ausencia del nuevo método, ¿tengo que averiguar si estoy en iOS8 o no? Swift es bueno, pero esto parece un poco torpe.
Gruntcakes
@sulthan No estoy seguro de dónde vienes diciendo que no deberías usarlo, respondsToSelector:ya que la recomendación de Apple es explícitamente que debes hacerlo. Ver aquí
David Berry
@David Sí, ha encontrado uno de los pocos casos en los que respondsToSelector:es realmente bueno tenerlo. Pero si revisa la referencia de Swift, hablan sobre el uso de subclases para diferentes versiones del sistema.
Sulthan
Si ese no es el caso del que habla el OP, y están hablando del caso del protocolo opcional, entonces también lo permiten explícitamente usando el encadenamiento opcional (como ya ha señalado). De cualquier manera, decir "debes evitar hacer lo que Apple te ha dicho explícitamente que hagas" parece un mal consejo. Apple "siempre" ha proporcionado mecanismos para determinar y ejercer la funcionalidad opcional en tiempo de ejecución.
David Berry
1
@Honey En Swift generalmente usamos directivas de disponibilidad, por ejemplo, if #available(iOS 10) {y luego llamamos al método directamente.
Sulthan
40

Actualización del 20 de marzo de 2017 para la sintaxis de Swift 3:

Si no le importa si existe el método opcional, simplemente llame delegate?.optionalMethod?()

De lo contrario, usar guardes probablemente el mejor enfoque:

weak var delegate: SomeDelegateWithOptionals?

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

Respuesta original:

Puede usar el enfoque "if let" para probar un protocolo opcional como este:

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
fuente
44
si se deja theMethod = delegado .theOptionalProtocolMethod?
john07
1
Ejemplo de sintaxis de selector cuando hay múltiples candidatos:let theMethod = delegate.userNotificationCenter(_:willPresent:withCompletionHandler:)
Cœur
11

Parece que necesita definir su protocolo como un subprotocolo de NSObjectProtocol ... entonces obtendrá el método respondsToSelector

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

tenga en cuenta que solo especificar @objc no fue suficiente. También debe tener cuidado de que el delegado real sea una subclase de NSObject, que en Swift podría no serlo.

Matej Ukmar
fuente
10

Si el método que está probando se define como un método opcional en un protocolo @objc (que suena como su caso), entonces use el patrón de encadenamiento opcional como:

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

Cuando el método se declara como de retorno Void, simplemente use:

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

Ver:


Extracto de "Métodos de llamada mediante encadenamiento opcional" de : Apple Inc. "El lenguaje de programación Swift".
iBooks https://itun.es/us/jEUH0.l

GoZoner
fuente
Esto solo funcionaría si el método devuelve algo, ¿y si es un método nulo?
Gruntcakes
1
Si se anula el uso simplemente if object.method?(args) { ... }, la llamada del método, cuando exista, regresará, lo Voidcual no es asínil
GoZoner
1
Sin embargo, eso resulta en "Tipo vacío no se ajusta al protocolo" Valor lógico ""
Gruntcakes
Consulte la documentación referenciada (página 311-312).
GoZoner
"Si el método que está probando se define como un método opcional en un protocolo @objc" O si objecttiene tipo AnyObject, puede probar cualquier método @objc.
newacct
9

Las funciones son tipos de primera clase en Swift, por lo que puede verificar si se ha implementado una función opcional definida en un protocolo comparándola con nil:

if (someObject.someMethod != nil) {
    someObject.someMethod!(someArgument)
} else {
    // do something else
}
Christopher Pickslay
fuente
1
¿Qué pasa si el método está sobrecargado? ¿Cómo podemos verificar si hay uno específico?
Nuno Gonçalves
8

Para swift3

Si solo desea llamar al método, ejecute el código a continuación.

self.delegate?.method?()

Jason Yu
fuente
7

En Swift 2, Apple introdujo una nueva función llamada API availability checking, que podría ser un reemplazo para el respondsToSelector:método. La siguiente comparación de fragmentos de código se copia de la sesión 106 de WWDC2015 Novedades en Swift que pensé que podría ayudarlo, por favor, compruébelo si necesita saber más.

El viejo enfoque:

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

El mejor enfoque:

@IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
    if #available(OSX 10.10.3, *) {
        dropButton.springLoaded = true
    }
}
tounaobun
fuente
Creo que es una solución mucho mejor que usar el respondsToSelectorenfoque en Swift. Eso también se debe a que la verificación del selector no fue la mejor solución para este problema (verificación de disponibilidad) en primer lugar.
JanApotheker
6

Para 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)")
    }
}
Kakashi
fuente
4

Actualmente (Swift 2.1) puede verificarlo de 3 maneras:

  1. Usando respondsToSelector respondido por @Erik_at_Digit
  2. Usando '?' respondido por @Sulthan

  3. Y usando el as?operador:

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

Básicamente depende de lo que intentes lograr:

  • Si, por ejemplo, la lógica de su aplicación necesita realizar alguna acción y el delegado no está configurado o el delegado puntiagudo no implementó el método onSuccess () (método de protocolo), entonces las opciones 1 y 3 son la mejor opción, aunque usaría opción 3, que es la forma rápida.
  • Si no desea hacer nada cuando el delegado es nulo o el método no está implementado, use la opción 2.
OhadM
fuente
2

Solo lo implemento yo mismo en un proyecto, vea el código a continuación. Como menciona @Christopher Pickslay, es importante recordar que las funciones son ciudadanos de primera clase y, por lo tanto, pueden tratarse como variables opcionales.

@objc protocol ContactDetailsDelegate: class {

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

...

weak var delegate:ContactDetailsDelegate!

if let deleteContact = delegate.deleteContact {
    deleteContact(contact)
}
Bulwinkel
fuente
¿Qué pasa si el método está sobrecargado? ¿Cómo haría uno eso?
Nuno Gonçalves
2

otra sintaxis posible por swift ..

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

Cuando comencé a actualizar mi antiguo proyecto a Swift 3.2, solo necesitaba cambiar el método de

respondsToSelector(selector)

a:

responds(to: selector)
chAlexey
fuente
0

Yo uso guard let else, por lo que puede hacer algunas cosas predeterminadas si el delegado func no está implementado.

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

Swift 3:

protocolo

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

Objeto

class SomeObject : NSObject {

weak var delegate:SomeObject?

func delegateMethod() {

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

   }

}
etzuk
fuente
-4

El equivalente es el? operador:

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

importantMethod solo se llamará si myQuestionableObject existe y lo implementa.

Ben Gottlieb
fuente
Pero, ¿qué pasa si myQuestionalObject es algo así como UIApplication (que, por lo tanto, va a existir y, por lo tanto, verificar que su existencia no tiene sentido) y importandMethod () es un nuevo método introducido en iOS N y desea determinar si puede llamarlo o tener que llamar al viejo deprecatedImportantMethod () en iOS N-1?
Gruntcakes
2
También necesita un ?afterimportantMethod
newacct