extension - swift protocol optional
Anulación de métodos en extensiones Swift (5)
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 estándar / 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 que no se permite la anulación de 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.
public protocol PCopierSerializable {
static func getFieldTable(mCopier : MCopier) -> FieldTable
static func createObject(initTable : [Int : Any?]) -> Any
func doSerialization(mCopier : MCopier)
}
.
public class SimpleClass : PCopierSerializable {
public var aMember : Int32
public init(
aMember : Int32
) {
self.aMember = aMember
}
public class func getFieldTable(mCopier : MCopier) -> FieldTable {
return getFieldTable_SimpleClass(mCopier: mCopier)
}
public class func createObject(initTable : [Int : Any?]) -> Any {
return createObject_SimpleClass(initTable: initTable)
}
public func doSerialization(mCopier : MCopier) {
doSerialization_SimpleClass(mCopier: mCopier)
}
}
.
extension SimpleClass {
class func getFieldTable_SimpleClass(mCopier : MCopier) -> FieldTable {
var fieldTable : FieldTable = [ : ]
fieldTable[376442881] = { () in try mCopier.getInt32A() } // aMember
return fieldTable
}
class func createObject_SimpleClass(initTable : [Int : Any?]) -> Any {
return SimpleClass(
aMember: initTable[376442881] as! Int32
)
}
func doSerialization_SimpleClass(mCopier : MCopier) {
mCopier.writeBinaryObjectHeader(367620, 1)
mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
}
}
.
public class DerivedClass : SimpleClass {
public var aNewMember : Int32
public init(
aNewMember : Int32,
aMember : Int32
) {
self.aNewMember = aNewMember
super.init(
aMember: aMember
)
}
public class override func getFieldTable(mCopier : MCopier) -> FieldTable {
return getFieldTable_DerivedClass(mCopier: mCopier)
}
public class override func createObject(initTable : [Int : Any?]) -> Any {
return createObject_DerivedClass(initTable: initTable)
}
public override func doSerialization(mCopier : MCopier) {
doSerialization_DerivedClass(mCopier: mCopier)
}
}
.
extension DerivedClass {
class func getFieldTable_DerivedClass(mCopier : MCopier) -> FieldTable {
var fieldTable : FieldTable = [ : ]
fieldTable[376443905] = { () in try mCopier.getInt32A() } // aNewMember
fieldTable[376442881] = { () in try mCopier.getInt32A() } // aMember
return fieldTable
}
class func createObject_DerivedClass(initTable : [Int : Any?]) -> Any {
return DerivedClass(
aNewMember: initTable[376443905] as! Int32,
aMember: initTable[376442881] as! Int32
)
}
func doSerialization_DerivedClass(mCopier : MCopier) {
mCopier.writeBinaryObjectHeader(367621, 2)
mCopier.serializeProperty(376443905, .eInt32, { () in mCopier.putInt32(aNewMember) } )
mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
}
}
Como dije en mi introducción, esto realmente no responde 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.
Tiendo a poner solo las necesidades (propiedades almacenadas, inicializadores) en las definiciones de mi clase y mover todo lo demás a su propia
extension
, como una
extension
por bloque lógico que agruparía con
// MARK:
también.
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
ABC
.
Eso tiene poco sentido para mí.
Leí acerca de 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.
Claramente, la llamada en
C
debe al menos ser despachada dinámicamente y producir
C
?
Si
NSObject
la herencia de
NSObject
y hago de
C
una clase raíz, el compilador se queja diciendo que las
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
AAA
como se esperaba, mover solo las
B
produce
ABB
, mover solo las
A
produce
CBC
, la última de las cuales no tiene absolutamente ningún sentido para mí: ni siquiera la que está escrita estáticamente a
A
produce la
A
-salida más!
Agregar la palabra clave
dynamic
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 tenemos
ABA
.
Aquí no puedo hacer que el diseño de Subvistas de UIView sea dinámico de ninguna manera.
Mover ambas anulaciones en su declaración de clase nos
AAA
, solo A o solo B aún nos consigue
ABA
.
dynamic
nuevamente resuelve mis problemas.
En teoría, podría agregar
dynamic
a todas las
override
que hago, pero siento que estoy haciendo algo mal aquí.
¿Es realmente incorrecto usar
extension
para agrupar código como lo hago yo?
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:
// ---------- BaseClass.swift -------------
public class BaseClass
{
public var method1:(Int) -> String { return doMethod1 }
public init() {}
}
// the extension could also be in a separate file
extension BaseClass
{
private func doMethod1(param:Int) -> String { return "BaseClass /(param)" }
}
...
// ---------- ClassA.swift ----------
public class A:BaseClass
{
override public var method1:(Int) -> String { return doMethod1 }
}
// this extension can be in a separate file but not in the same
// file as the BaseClass extension that defines its doMethod1 implementation
extension A
{
private func doMethod1(param:Int) -> String
{
return "A /(param) added to /(super.method1(param))"
}
}
...
// ---------- ClassB.swift ----------
public class B:A
{
override public var method1:(Int) -> String { return doMethod1 }
}
extension B
{
private func doMethod1(param:Int) -> String
{
return "B /(param) added to /(super.method1(param))"
}
}
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
BaseClass().method1(123) --> "BaseClass 123"
A().method1(123) --> "A 123 added to BaseClass 123"
B().method1(123) --> "B 123 added to A 123 added to BaseClass 123"
(B() as A).method1(123) --> "B 123 added to A 123 added to BaseClass 123"
(B() as BaseClass).method1(123) --> "B 123 added to A 123 added to BaseClass 123"
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.
Las extensiones pueden agregar nueva funcionalidad a un tipo, pero no pueden anular la funcionalidad existente.
Guía para desarrolladores de Apple
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
layoutSubviews
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 utilizan 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
load()
e
initialize()
automáticamente al inicializar clases en los procesos de su aplicación.
Sobre el modificador
dynamic
Desde la biblioteca de desarrolladores de iOS
Puede usar el modificador
dynamic
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 lo tanto, la
dynamic
se puede aplicar a su
layoutSubviews
->
UIView Class
ya que está representada 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 le permite usar
override
y
dynamic
.
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 funcionan 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 (elimine
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 la
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.
Use POP (programación orientada al protocolo) para anular funciones en extensiones.
protocol AProtocol {
func aFunction()
}
extension AProtocol {
func aFunction() {
print("empty")
}
}
class AClass: AProtocol {
}
extension AClass {
func aFunction() {
print("not empty")
}
}
let cls = AClass()
cls.aFunction()