El método que no es '@ objc' no satisface el requisito opcional del protocolo '@objc'

103

Visión general:

  • Tengo un protocolo P1 que proporciona una implementación predeterminada de una de las funciones opcionales de Objective-C.
  • Cuando proporciono una implementación predeterminada de la función opcional, hay una advertencia

Advertencia del compilador:

Non-'@objc' method 'presentationController(_:viewControllerForAdaptivePresentationStyle:)' does not satisfy optional requirement of '@objc' protocol 'UIAdaptivePresentationControllerDelegate'

Versión:

  • Rápido: 3
  • Xcode: 8 (lanzamiento público)

Intentos realizados:

  • Intenté agregar @objcpero no ayudó

Pregunta:

  • ¿Cómo resolví esto?
  • ¿Hay alguna solución?

Código:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {

}

extension P1 where Self : UIViewController {

    func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return UIViewController()
    }
}


class A : UIViewController, P1 {

}
usuario1046037
fuente
¿Tiene la versión más reciente de Xcode? No obtengo ningún error si @objc
elimino
Estoy usando Xcode 8 (última versión pública). No hay ningún error, pero habrá una advertencia
user1046037

Respuestas:

182

Si bien creo que puedo responder a su pregunta, no es una respuesta que le guste.

TL; DR: las @objc funciones pueden no estar actualmente en extensiones de protocolo. En su lugar, podría crear una clase base, aunque esa no es una solución ideal.

Extensiones de protocolo y Objective-C

Primero, esta pregunta / respuesta ( Can Swift Method Defined on Extensions on Protocols Accessed in Objective-c ) parece sugerir que debido a la forma en que se envían las extensiones de protocolo bajo el capó, los métodos declarados en las extensiones de protocolo no son visibles para la objc_msgSend()función, y por lo tanto, no son visibles para el código Objective-C. Dado que el método que está tratando de definir en su extensión debe ser visible para Objective-C (así que UIKitpuede usarlo), le grita por no incluirlo @objc, pero una vez que lo incluye, le grita porque @objcno está permitido en extensiones de protocolo. Esto probablemente se deba a que las extensiones de protocolo actualmente no pueden ser visibles para Objective-C.

También podemos ver que el mensaje de error una vez que agregamos @objcdice "@objc solo se puede usar con miembros de clases, protocolos @objc y extensiones concretas de clases". Esta no es una clase; una extensión de un protocolo @objc no es lo mismo que estar en la propia definición del protocolo (es decir, en los requisitos), y la palabra "concreto" sugeriría que una extensión de protocolo no cuenta como una extensión de clase concreta.

Solución alterna

Desafortunadamente, esto prácticamente le impide usar extensiones de protocolo cuando las implementaciones predeterminadas deben ser visibles para los marcos Objective-C. Al principio, pensé que tal vez @objcno estaba permitido en su extensión de protocolo porque Swift Compiler no podía garantizar que los tipos conformes fueran clases (aunque lo haya especificado específicamente UIViewController). Así que puse un classrequisito P1. Esto no funcionó.

Quizás la única solución es simplemente usar una clase base en lugar de un protocolo aquí, pero obviamente esto no es completamente ideal porque una clase puede tener una sola clase base pero cumplir con múltiples protocolos.

Si elige seguir esta ruta, tenga en cuenta esta pregunta ( Método de protocolo opcional de Swift 3 ObjC no llamado en subclase ). Parece que otro problema actual en Swift 3 es que las subclases no heredan automáticamente las implementaciones de requisitos de protocolo opcionales de su superclase. La respuesta a esas preguntas utiliza una adaptación especial de @objcpara sortearla.

Informar el problema

Creo que esto ya se está discutiendo entre los que trabajan en los proyectos de código abierto de Swift, pero puede estar seguro de que lo saben utilizando el Informe de errores de Apple , que probablemente llegará al Swift Core Team, o el informador de errores de Swift . Sin embargo, cualquiera de estos puede encontrar su error demasiado amplio o ya conocido. El equipo de Swift también puede considerar lo que está buscando como una nueva función de idioma, en cuyo caso primero debe consultar las listas de correo .

Actualizar

En diciembre de 2016, se informó de este problema a la comunidad de Swift. El problema aún está marcado como abierto con una prioridad media, pero se agregó el siguiente comentario:

Esto es intencionado. No hay forma de agregar la implementación del método a cada adoptante, ya que la extensión podría agregarse después de la conformidad con el protocolo. Sin embargo, supongo que podríamos permitirlo si la extensión está en el mismo módulo que el protocolo.

Sin embargo, dado que su protocolo está en el mismo módulo que su extensión, es posible que pueda hacer esto en una versión futura de Swift.

Actualización 2

En febrero de 2017, uno de los miembros del Swift Core Team cerró oficialmente este problema como "No funciona" con el siguiente mensaje:

Esto es intencional: las extensiones de protocolo no pueden introducir puntos de entrada @objc debido a las limitaciones del tiempo de ejecución de Objective-C. Si desea agregar puntos de entrada @objc a NSObject, amplíe NSObject.

Extender NSObjecto incluso UIViewControllerno logrará exactamente lo que desea, pero desafortunadamente no parece que sea posible.

En el futuro (muy) a largo plazo, es posible que podamos eliminar por @objccompleto la dependencia de los métodos, pero es probable que ese momento no llegue pronto, ya que los marcos de Cocoa no están escritos actualmente en Swift (y no pueden estarlo hasta que tenga un ABI estable) .

Actualización 3

A partir del otoño de 2019, esto se está convirtiendo en un problema menor porque cada vez se escriben más marcos de Apple en Swift. Por ejemplo, si usa en SwiftUIlugar de UIKit, elude el problema por completo porque @objcnunca sería necesario al referirse a un SwiftUImétodo.

Los marcos de Apple escritos en Swift incluyen:

  • SwiftUI
  • RealityKit
  • Combinar
  • CryptoKit

Uno esperaría que este patrón continúe con el tiempo ahora que Swift es oficialmente ABI y el módulo es estable a partir de Swift 5.0 y 5.1, respectivamente.

Matthew Seaman
fuente
1
@ user1046037 Yo también lo hago, ya que puedo verme encontrando este problema muchas veces en el desarrollo futuro.
Matthew Seaman
2
Tienes razón, tu respuesta sigue siendo válida a pesar de que Swift 4no hay otra alternativa a partir de ahora.
user1046037
1
Tuve exactamente el mismo código trabajando para mí por un tiempo, pero rompí en una versión posterior de Xcode. Esto es bastante irritante. Hay tantos métodos opcionales en los protocolos Objective-C.
Departamento B
0

Me encontré con esto después de habilitar la 'estabilidad del módulo' (activando 'Construir bibliotecas para distribución') en un marco rápido que uso.

Lo que tenía era algo como esto:

class AwesomeClass: LessAwesomeClass {
...
}

extension AwesomeClass: GreatDelegate {
  func niceDelegateFunc() {
  }
}

La función en la extensión tenía estos errores:

  • El método de instancia '@objc' en extensión de la subclase de 'LessAwesomeClass' requiere iOS 13.0.0

  • El método que no es '@ objc' 'niceDelegateFunc' no satisface el requisito del protocolo '@objc' 'GreatDelegate'

Mover las funciones a la clase en lugar de a una extensión resolvió el problema.

CMash
fuente
0

Aquí hay otra solución. También me encontré con este problema y todavía no puedo cambiar de UIKit a SwiftUI. Mover las implementaciones predeterminadas a una clase base común tampoco fue una opción para mí. Mis implementaciones predeterminadas eran bastante extensas, por lo que realmente no quería tener todo ese código duplicado. La solución alternativa que terminé usando fue usar funciones contenedoras en el protocolo y luego simplemente llamar a esas funciones desde cada clase. No es bonito, pero puede ser mejor que la alternativa, según la situación. Su código entonces se vería así:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {
}

extension P1 where Self : UIViewController {
    func wrapPresentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return UIViewController()
    }
}

class A : UIViewController, P1 {
    func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return wrapPresentationController(controller, viewControllerForAdaptivePresentationStyle: style)
    }
}
rene
fuente