unrecognized - swift selector with arguments
¿Cómo resuelvo el error de compilación “uso ambiguo de” con la sintaxis Swift#selector? (1)
[ 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 externo del método 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
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
... aparece un error, "Uso ambiguo de
test()
".
Pero si digo esto:
let selector = #selector(test(_:)) // ok, but...
... el error desaparece, pero ahora me estoy refiriendo al método incorrecto , el que tiene un parámetro. Quiero referirme al uno sin ningún parámetro. ¿Cómo lo hago?
[Nota: el ejemplo no es artificial.
NSObject tiene tanto la
copy
Objective-C como la
copy:
métodos de instancia, Swift
copy()
y
copy(sender:AnyObject?)
;
entonces el problema puede surgir fácilmente en la vida real.]
[ NOTA Esta respuesta 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 externo del método ya no se suprime automáticamente, y un selector debe exponerse explícitamente a Objective-C.]
Puede solucionar este problema enviando 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, revelando que la sintaxis de Swift para referirse a las funciones es inadecuada. Archivé 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 (generalmente "selector no reconocido") que pueden surgir al suministrar un selector como una cadena literal.
#selector()
toma una
referencia de función
, y el compilador verificará que la función realmente exista y resolverá la referencia a un selector de Objective-C por usted.
Por lo tanto, no puede cometer ningún error fácilmente.
(
EDITAR:
De acuerdo, sí, puedes hacerlo. Puedes ser un lunkhead completo y establecer el objetivo en una instancia que no implemente el mensaje de acción especificado por el
#selector
. El compilador no te detendrá y se bloqueará como en los viejos tiempos. Suspiro ...)
Una referencia de función puede aparecer en cualquiera de las 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 de
test
, por lo que este#selector
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 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 métodos de
test
, por lo que debemos diferenciarnos; latest(_:)
notacióntest(_:)
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 un reparto 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 la
test(_:)
. La sobrecarga no puede exponerse 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 desambiguar en lo que respecta a Swift, y el elenco lo hace.(Es esta característica lingüística la que se usa, mal utilizada, 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 diciéndole en qué clase se encuentra la función:
-
Si la clase es la misma que esta, o sube la cadena de la superclase de esta, generalmente no se necesita una resolución adicional (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, utiliza 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 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 la
test
es 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 lo único que importa).