¿Cómo puedo lidiar con la desaprobación de la inferencia @objc con #selector() en Swift 4?
Estoy intentando convertir el código fuente de mi proyecto de Swift 3 a Swift 4. Una advertencia que me da Xcode es sobre mis selectores.
Por ejemplo, agrego un objetivo a un botón usando un selector normal como este:
button.addTarget(self, action: #selector(self.myAction), for: .touchUpInside)
Esta es la advertencia que muestra:
El argumento de '#selector' se refiere al método de instancia 'myAction()' en 'ViewController' que depende de la inferencia del atributo '@objc' obsoleto en Swift 4
Agregue '@objc' para exponer este método de instancia a Objective-C
Ahora, presionar Fix
el mensaje de error hace esto en mi función:
// before
func myAction() { /* ... */ }
// after
@objc func myAction() { /* ... */ }
Realmente no quiero cambiar el nombre de todas mis funciones para incluir la @objc
marca y supongo que no es necesario.
¿Cómo reescribo el selector para hacer frente a la desaprobación?
Pregunta relacionada:
- ¿El uso de la inferencia Swift 3 @objc en el modo Swift 4 está obsoleto?
La solución es correcta: no hay nada sobre el selector que pueda cambiar para que el método al que se refiere esté expuesto a Objective-C.
El motivo de esta advertencia en primer lugar es el resultado de SE-0160 . Antes de Swift 4, internal
o superior, se infería que los miembros compatibles con Objective-C de NSObject
las clases heredadas estaban @objc
expuestos a Objective-C, lo que permitía llamarlos mediante selectores (ya que se requiere el tiempo de ejecución de Obj-C para buscar el método). implementación para un selector determinado).
Sin embargo, en Swift 4, este ya no es el caso. Ahora sólo se infiere que declaraciones muy específicas son @objc
, por ejemplo, anulaciones de @objc
métodos, implementaciones de @objc
requisitos de protocolo y declaraciones con atributos que implican @objc
, como @IBOutlet
.
La motivación detrás de esto, como se detalla en la propuesta vinculada anteriormente , es, en primer lugar, evitar que las sobrecargas de métodos en NSObject
las clases heredadas colisionen entre sí debido a que tienen selectores idénticos. En segundo lugar, ayuda a reducir el tamaño binario al no tener que generar procesadores para miembros que no necesitan estar expuestos a Obj-C y, en tercer lugar, mejora la velocidad de los enlaces dinámicos.
Si desea exponer un miembro a Obj-C, debe marcarlo como @objc
, por ejemplo:
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(foo), for: .touchUpInside)
}
@objc func foo() {
// ...
}
}
(el migrador debería hacer esto automáticamente con los selectores cuando se ejecuta con la opción "minimizar inferencia" seleccionada)
Para exponer un grupo de miembros a Obj-C, puede utilizar @objc extension
:
@objc extension ViewController {
// both exposed to Obj-C
func foo() {}
func bar() {}
}
Esto expondrá todos los miembros definidos en él a Obj-C y dará un error en cualquier miembro que no pueda exponerse a Obj-C (a menos que esté marcado explícitamente como @nonobjc
).
Si tiene una clase en la que necesita que todos los miembros compatibles con Obj-C estén expuestos a Obj-C, puede marcar la clase como @objcMembers
:
@objcMembers
class ViewController: UIViewController {
// ...
}
Ahora, todos los miembros que se pueda inferir que lo serán @objc
lo serán. Sin embargo, no recomendaría hacer esto a menos que realmente necesite que todos los miembros estén expuestos a Obj-C, dadas las desventajas mencionadas anteriormente de tener miembros expuestos innecesariamente.
A partir de Swift 4.2, creo que todo lo que necesitas hacer es asignarlo @IBAction
a tu método y evitar la @objc
anotación.
let tap = UITapGestureRecognizer(target: self, action: #selector(self.cancel))
@IBAction func cancel()
{
self.dismiss(animated: true, completion: nil)
}
Como ya se mencionó en otras respuestas, no hay forma de evitar la @objc
anotación de los selectores.
Pero la advertencia mencionada en el OP se puede silenciar siguiendo los siguientes pasos:
- Ir a configuración de compilación
- Busque la palabra clave @objc
- Establezca el valor de la interfaz @objc de Swift 3 en
Off
A continuación se muestra la captura de pantalla que ilustra los pasos mencionados anteriormente:
Espero que esto ayude