Tiendo a poner solo las necesidades (propiedades almacenadas, inicializadores) en las definiciones de mi clase y mover todo lo demás a las suyas extension
, como un extension
bloque lógico con el que también agruparía // MARK:
.
Para una subclase de UIView, por ejemplo, terminaría con una extensión para cosas relacionadas con el diseño, una para suscribirse y manejar eventos, etc. En estas extensiones, inevitablemente tengo que anular algunos métodos UIKit, por ejemplo layoutSubviews
. Nunca noté ningún problema con este enfoque, hasta hoy.
Tome esta jerarquía de clases, por ejemplo:
public class C: NSObject {
public func method() { print("C") }
}
public class B: C {
}
extension B {
override public func method() { print("B") }
}
public class A: B {
}
extension A {
override public func method() { print("A") }
}
(A() as A).method()
(A() as B).method()
(A() as C).method()
La salida es A B C
. Eso tiene poco sentido para mí. Leí sobre las Extensiones de protocolo que se envían estáticamente, pero este no es un protocolo. Esta es una clase regular, y espero que las llamadas a métodos se envíen dinámicamente en tiempo de ejecución. ¿Está claro que la llamada C
al menos debería ser despachada y producida dinámicamente C
?
Si elimino la herencia NSObject
y hago C
una clase raíz, el compilador se queja diciendo declarations in extensions cannot override yet
, sobre lo que ya leí. ¿Pero cómo tener NSObject
como clase raíz cambia las cosas?
Mover ambas anulaciones en su declaración de clase produce A A A
como se esperaba, mover solo B
las producciones A B B
, mover solo A
las producciones C B C
, la última de las cuales no tiene absolutamente ningún sentido para mí: ¡ni siquiera la que está escrita estáticamente para A
producir la A
salida!
Agregar la dynamic
palabra clave a la definición o una anulación parece darme el comportamiento deseado 'desde ese punto en la jerarquía de clases hacia abajo' ...
Cambiemos nuestro ejemplo a algo un poco menos construido, lo que realmente me hizo publicar esta pregunta:
public class B: UIView {
}
extension B {
override public func layoutSubviews() { print("B") }
}
public class A: B {
}
extension A {
override public func layoutSubviews() { print("A") }
}
(A() as A).layoutSubviews()
(A() as B).layoutSubviews()
(A() as UIView).layoutSubviews()
Ahora lo tenemos A B A
. Aquí no puedo hacer que las subvistas de diseño de UIView sean dinámicas de ninguna manera.
Mover ambas anulaciones a su declaración de clase nos lleva de A A A
nuevo, solo A o solo B todavía nos consigue A B A
. dynamic
De nuevo resuelve mis problemas.
En teoría, podría agregar dynamic
a todo override
lo que hago, pero siento que estoy haciendo algo más aquí.
¿Es realmente incorrecto usar extension
s para agrupar código como lo hago?
fuente
Respuestas:
Las extensiones no pueden / no deben anularse.
No es posible anular la funcionalidad (como propiedades o métodos) en extensiones como se documenta en la Guía Swift de Apple.
Guía rápida para desarrolladores
El compilador le permite anular la extensión para compatibilidad con Objective-C. Pero en realidad está violando la directiva del lenguaje.
😊 Eso me recordó las " Tres leyes de la robótica " de Isaac Asimov 🤖
Las extensiones ( azúcar sintáctico ) definen métodos independientes que reciben sus propios argumentos. La función que se requiere, es decir,
layoutSubviews
depende del contexto que el compilador conoce cuando se compila el código. UIView hereda de UIResponder que hereda de NSObject, por lo que la anulación en la extensión está permitida pero no debería estarlo .Por lo tanto, no hay nada malo con la agrupación, pero debe anular en la clase, no en la extensión.
Notas directivas
Solo puede
override
un método de superclase, es decir,load()
initialize()
en una extensión de una subclase si el método es compatible con Objective-C.Por lo tanto, podemos ver por qué te permite compilar usando
layoutSubviews
.Todas las aplicaciones de Swift se ejecutan dentro del tiempo de ejecución de Objective-C, excepto cuando se usan marcos de solo Swift puros que permiten un tiempo de ejecución solo de Swift.
Como descubrimos, el tiempo de ejecución de Objective-C generalmente llama a dos métodos principales de clase
load()
yinitialize()
automáticamente al inicializar clases en los procesos de su aplicación.Sobre el
dynamic
modificadorDe la Apple Developer Library (archive.org)
Puede usar el
dynamic
modificador para requerir que el acceso a los miembros se envíe dinámicamente a través del tiempo de ejecución de Objective-C.Cuando las API de Swift son importadas por el tiempo de ejecución de Objective-C, no hay garantías de envío dinámico para propiedades, métodos, subíndices o inicializadores. El compilador Swift aún puede desvirtualizar o acceder en línea a los miembros para optimizar el rendimiento de su código, evitando el tiempo de ejecución de Objective-C. 😳
Por
dynamic
lo tanto, se puede aplicar a sulayoutSubviews
->UIView Class
ya que está representado por Objective-C y el acceso a ese miembro siempre se usa utilizando el tiempo de ejecución de Objective-C.Es por eso que el compilador te permite usar
override
ydynamic
.fuente
Uno de los objetivos de Swift es el despacho estático, o más bien la reducción del despacho dinámico. Obj-C, sin embargo, es un lenguaje muy dinámico. La situación que estás viendo se debe al vínculo entre los 2 idiomas y la forma en que trabajan juntos. Realmente no debería compilar.
Uno de los puntos principales sobre las extensiones es que son para extender, no para reemplazar / anular. Está claro tanto por el nombre como por la documentación que esta es la intención. De hecho, si elimina el enlace a Obj-C de su código (eliminar
NSObject
como la superclase) no se compilará.Entonces, el compilador está tratando de decidir qué puede enviar estáticamente y qué tiene que enviar dinámicamente, y está cayendo a través de un vacío debido al enlace Obj-C en su código. La razón por la que
dynamic
"funciona" es porque está forzando el enlace Obj-C en todo, por lo que todo es siempre dinámico.Por lo tanto, no está mal usar extensiones para agrupar, eso es genial, pero está mal anular extensiones. Cualquier anulación debe estar en la clase principal en sí misma y llamar a los puntos de extensión.
fuente
supportedInterfaceOrientations
enUINavigationController
(a efectos de mostrar diferentes puntos de vista en diferentes orientaciones), no se debe utilizar una clase personalizada una extensión? Muchas respuestas sugieren usar una extensión para anular,supportedInterfaceOrientations
pero me encantaría aclarar. ¡Gracias!Hay una manera de lograr una separación limpia de la firma de clase y la implementación (en extensiones) mientras se mantiene la capacidad de tener anulaciones en las subclases. El truco es utilizar variables en lugar de las funciones.
Si se asegura de definir cada subclase en un archivo fuente rápido por separado, puede usar variables calculadas para las anulaciones mientras mantiene la implementación correspondiente organizada limpiamente en extensiones. Esto eludirá las "reglas" de Swift y hará que la API / firma de su clase esté perfectamente organizada en un solo lugar:
...
...
Las extensiones de cada clase pueden usar los mismos nombres de métodos para la implementación porque son privados y no son visibles entre sí (siempre que estén en archivos separados).
Como puede ver, la herencia (usando el nombre de la variable) funciona correctamente usando super.variablename
fuente
Esta respuesta no apuntó al OP, aparte del hecho de que me sentí inspirado a responder por su declaración, "Tiendo a poner las necesidades (propiedades almacenadas, inicializadores) en las definiciones de mi clase y mover todo lo demás a su propia extensión. .. ". Principalmente soy un programador de C #, y en C # one puedo usar clases parciales para este propósito. Por ejemplo, Visual Studio coloca las cosas relacionadas con la interfaz de usuario en un archivo fuente separado usando una clase parcial, y deja su archivo fuente principal despejado para que no tenga esa distracción.
Si busca "clase parcial rápida", encontrará varios enlaces donde los seguidores de Swift dicen que Swift no necesita clases parciales porque puede usar extensiones. Curiosamente, si escribe "extensión rápida" en el campo de búsqueda de Google, su primera sugerencia de búsqueda es "anulación de extensión rápida", y en este momento esta pregunta de desbordamiento de pila es el primer éxito. Supongo que significa que los problemas con (falta de) capacidades de anulación son el tema más buscado relacionado con las extensiones Swift, y resalto el hecho de que las extensiones Swift no pueden reemplazar clases parciales, al menos si usa clases derivadas en su programación.
De todos modos, para acortar una introducción de largo aliento, me encontré con este problema en una situación en la que quería mover algunos métodos repetitivos / equipaje fuera de los archivos de origen principales para las clases Swift que estaba generando mi programa C # a Swift. Después de encontrarme con el problema de no anulación permitida para estos métodos después de moverlos a extensiones, terminé implementando la siguiente solución simple. Los principales archivos fuente de Swift todavía contienen algunos métodos de código auxiliar pequeños que llaman a los métodos reales en los archivos de extensión, y estos métodos de extensión reciben nombres únicos para evitar el problema de anulación.
.
.
.
.
Como dije en mi introducción, esto realmente no responde a la pregunta del OP, pero espero que esta solución simple pueda ser útil para otras personas que desean mover métodos de los archivos de origen principales a los archivos de extensión y ejecutar el no -sobre el problema.
fuente
Use POP (programación orientada al protocolo) para anular funciones en extensiones.
fuente