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

79

[ NOTA Esta pregunta se formuló originalmente en Swift 2.2. Se ha revisado para Swift 4, lo que implica dos cambios de lenguaje importantes: el primer parámetro externo del método ya no se suprime automáticamente y un selector debe estar expuesto 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 nueva de Swift 2.2 #selectorsintaxis para hacer un selector que corresponde a la primera de estos métodos, func test(). ¿Cómo lo hago? Cuando intento esto:

let selector = #selector(test) // error

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

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

... el error desaparece, pero ahora me refiero al método incorrecto , el que tiene un parámetro. Quiero referirme al que no tiene ningún parámetro. ¿Cómo lo hago?

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

mate
fuente

Respuestas:

110

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

Puede solucionar este problema lanzando su referencia de función a la firma del método correcto:

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

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


Solo para resumir la nueva #selectorsintaxis:

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

( EDITAR: Está bien, sí, puede. Puede ser un loco completo y establecer el objetivo en una instancia que no implementa el mensaje de acción especificado por #selector. El compilador no lo detendrá y fallará como en el buenos viejos tiempos. suspiro ...)

Una referencia de función puede aparecer en cualquiera de estas 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 testmétodo, por lo que se #selectorrefiere a él aunque toma un parámetro y #selectorno menciona el parámetro. El selector de Objective-C resuelto, detrás de escena, 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 . Por ejemplo:

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

    Tenemos dos testmétodos, por lo que debemos diferenciarlos; la notación se test(_:)resuelve en la segunda , la que tiene un parámetro.

  • El nombre de la función con o sin el resto de su firma, más una conversión para mostrar los tipos de 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í, lo hemos sobrecargado test(_:) . La sobrecarga no se puede exponer a Objective-C, porque Objective-C no permite la 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 aún debemos eliminar la ambigüedad en lo que respecta a Swift, y el elenco lo hace.

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

Además, es posible que deba ayudar a Swift a resolver la referencia de la función indicándole en qué clase se encuentra la función:

  • Si la clase es la misma que esta, o sube en la cadena de superclase desde esta, generalmente 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, 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.mpes 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 puntos:

    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 que testes un método de clase en lugar de un método de instancia, pero de todos modos se resolverá correctamente en un selector, que es todo lo que importa).

mate
fuente
2
Hola @Sulthan, es un gusto saber de ti. - No, eso se interpreta como una llamada a función. Simplemente no hay forma de anotar directamente el concepto "el que no tiene parámetros". Es un agujero; parecen haber seguido adelante con esto sin pensarlo completamente (como tantas veces) ...
Matt
4
@Sulthan Como temía, el informe de error volvió "funciona según lo previsto". Entonces mi respuesta es la respuesta: tu tiene que utilizar la asnotación para especificar la variante sin parámetros.
Matt
1
Otro punto a destacar de la experiencia "asombrosa" que es codificar en Swift.
Leo Natan
5
Con la actual Swift 3, usted tiene que poner la lista de argumentos entre paréntesis: let selector = #selector(test as (Void) -> Void).
Martin R
1
Quizás no sea el mejor lugar, pero en Swift 3, ¿cuál será la sintaxis preferida? test as (Void) -> Voido la sintaxis más corta test as () -> ()?
Represa
1

Quiero agregar una desambiguación faltante: acceder a un método de instancia desde fuera de la clase.

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

Desde la perspectiva de la clase, la firma completa del test()método es (Foo) -> () -> Void, que deberá especificar para obtener el Selector.

#selector(Foo.test as (Foo) -> () -> Void)
#selector(Foo.test(_:))

Alternativamente, puede hacer referencia a la Selectors de una instancia como se muestra en la respuesta original.

let foo = Foo()
#selector(foo.test as () -> Void)
#selector(foo.test(_:))
Guy Kogus
fuente
Sí, la notación Foo.xxxya es extraña, porque estos no son métodos de clase externamente. Entonces parece que el compilador le da un pase, pero solo si no hay ambigüedad. Si hay ambigüedad, debe remangarse y usar la notación más larga, que es legal y precisa porque un método de instancia es "secretamente" un método de clase curry. ¡Detección muy fina del caso de borde restante!
Matt
0

En mi caso (Xcode 11.3.1) el error fue solo al usar lldb durante la depuración. Cuando se ejecuta, funciona correctamente.

usuario23
fuente