¿Cómo resuelvo el error de compilación "uso ambiguo de" con la sintaxis de Swift #selector?


[NOTA Esta pregunta se formuló originalmente en Swift 2.2. Se ha revisado para Swift 4, lo que implica dos cambios importantes en el lenguaje: el primer parámetro de método externo ya no se suprime automáticamente, y un selector debe exponerse explícitamente a Objective-C.]

Digamos que tengo estos dos métodos en mi clase:

@objc func test() {}
@objc func test(_ sender:AnyObject?) {}

Ahora quiero usar la nueva sintaxis #selector de Swift 2.2 para hacer un selector correspondiente al primero de estos métodos, func test(). ¿Cómo lo hago? Cuando intento esto:

let selector = #selector(test) // error

... Obtengo un error, " Uso ambiguo de test()."Pero si digo esto:

let selector = #selector(test(_:)) // ok, but...

... el error desaparece, pero ahora estoy refiriendo a la método incorrecto, el con un parámetro. Quiero hacer referencia al sin ningún parámetro. ¿Cómo lo hago?

[Nota: el ejemplo no es artificial. NSObject tiene ambos métodos de instancia Objective-C copy y copy:, Swift copy() y copy(sender:AnyObject?); por lo que el problema puede surgen fácilmente en la vida real.]

Author: matt, 2016-02-26

1 answers

[NOTA Esta respuesta fue formulada originalmente bajo Swift 2.2. Se ha revisado para Swift 4, lo que implica dos cambios importantes en el lenguaje: el primer parámetro de método externo ya no se suprime automáticamente, y un selector debe exponerse explícitamente a Objective-C.]

Puede solucionar este problema mediante fundiendo su referencia de función a la firma de método correcta:

let selector = #selector(test as () -> Void)

(Sin embargo, en mi opinión, no deberías tener que hacer esto. Me considere esta situación como un error, revelando que la sintaxis de Swift para referirse a funciones es inadecuada. Presenté un informe de error, pero fue en vano.)


Solo para resumir la nueva sintaxis #selector:

El propósito de esta sintaxis es evitar los bloqueos de tiempo de ejecución demasiado comunes (típicamente "selector no reconocido") que pueden surgir cuando se suministra un selector como una cadena literal. #selector() toma una referencia de función , y el compilador comprobará que la función realmente existe y resolverá la referencia a un selector de Objective-C para usted. Por lo tanto, no puede cometer ningún error fácilmente.

(EDITAR: Bien, sí puedes. Puede ser un lunkhead completo y establecer el destino en una instancia que no implemente el mensaje de acción especificado por #selector. El compilador no te detendrá y te bloquearás como en los viejos tiempos. Suspiro...)

Una referencia de función puede aparecer en cualquiera de tres formas:

  • El nombre desnudo de la función. Esto es suficiente si la función no es ambigua. Así, por ejemplo:

    @objc func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test)
    }
    

    Solo hay un método test, por lo que este #selector se refiere a él a pesar de que toma un parámetro y el #selector no menciona el parámetro. El selector de Objective-C resuelto, detrás de las escenas, seguirá siendo correctamente "test:" (con los dos puntos, indicando un parámetro).

  • El nombre de La función, junto con el resto de su firma. Para ejemplo:

    func test() {}
    func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test(_:))
    }
    

    Tenemos dos métodos test, por lo que necesitamos diferenciar; la notación test(_:) se resuelve con el segundo , el que tiene un parámetro.

  • El nombre de la función con o sin el resto de su firma, más un cast para mostrar los tipos de los parámetros. Así:

    @objc func test(_ integer:Int) {}
    @nonobjc func test(_ string:String) {}
    func makeSelector() {
        let selector1 = #selector(test as (Int) -> Void)
        // or:
        let selector2 = #selector(test(_:) as (Int) -> Void)
    }
    

    Aquí, hemos sobrecargado test(_:). La sobrecarga no puede ser expuesta a Objective-C, porque Objective-C no permite sobrecarga, por lo que solo uno de ellos está expuesto, y podemos formar un selector solo para el que está expuesto, porque los selectores son una característica de Objective-C. Pero debemos todavía desambiguar en lo que respecta a Swift, y el elenco hace eso.

    (Es esta característica lingüística la que se utiliza - mal utilizada, en mi opinión - como la base de la respuesta anterior.)

Además, es posible que tenga que ayudar a Swift a resolver la referencia de la función diciéndole qué clase la función está en:

  • Si la clase es la misma que esta, o arriba de la cadena de superclase de esta, normalmente no se necesita más resolución (como se muestra en los ejemplos anteriores); opcionalmente, puede decir self, con notación de puntos (por ejemplo, #selector(self.test), y en algunas situaciones puede que tenga que hacerlo.

  • De lo contrario, se usa una referencia a una instancia para la que se implementa el método, con notación de puntos, como en este ejemplo de la vida real (self.mp es un MPMusicPlayerController):

    let pause = UIBarButtonItem(barButtonSystemItem: .pause, 
        target: self.mp, action: #selector(self.mp.pause))
    

    ...o puede usar el nombre de la clase , con notación de punto:

    class ClassA : NSObject {
        @objc func test() {}
    }
    class ClassB {
        func makeSelector() {
            let selector = #selector(ClassA.test)
        }
    }
    

    (Esto parece una notación curiosa, porque parece que estás diciendo test es un método de clase en lugar de un método de instancia, pero se resolverá correctamente a un selector, que es todo lo que importa.)

 88
Author: matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-24 16:35:59