[ 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 #selector
sintaxis:
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 test
método, por lo que se #selector
refiere a él aunque toma un parámetro y #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 test
mé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)
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.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 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 todo lo que importa).
as
notación para especificar la variante sin parámetros.let selector = #selector(test as (Void) -> Void)
.test as (Void) -> Void
o la sintaxis más cortatest as () -> ()
?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 elSelector
.#selector(Foo.test as (Foo) -> () -> Void) #selector(Foo.test(_:))
Alternativamente, puede hacer referencia a la
Selector
s de una instancia como se muestra en la respuesta original.let foo = Foo() #selector(foo.test as () -> Void) #selector(foo.test(_:))
fuente
Foo.xxx
ya 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!En mi caso (Xcode 11.3.1) el error fue solo al usar lldb durante la depuración. Cuando se ejecuta, funciona correctamente.
fuente