associated - swift protocol extension
¿Puedes anular entre extensiones en Swift o no?(¡El compilador parece confundido!) (2)
He estado trabajando en una aplicación de iOS en Swift (gran parte de ella se movió de Objective-C). Estoy usando Core Data y trato de usar extensiones para agregar funcionalidad a las clases generadas automáticamente desde mi modelo. Una cosa que hice fácilmente en Objective-C fue agregar un método en una categoría en la clase A y anular ese método en una categoría en la clase B (que deriva de A), y esperaba hacer lo mismo en Swift.
Hace un tiempo que tengo el siguiente código en mi proyecto (y este es solo un ejemplo), y aunque todavía no he usado la funcionalidad, el compilador ha trabajado muy bien compilando este código:
// From CellType.swift -- NOTE: Imports from Foundation and CoreData
@objc(CellType)
class CellType: NSManagedObject {
@NSManaged var maxUses: NSNumber
@NSManaged var useCount: NSNumber
// Other properties removed for brevity
}
// From SwitchCellType.swift -- NOTE: Imports from Foundation and CoreData
@objc(SwitchCellType)
class SwitchCellType: CellType {
@NSManaged var targetCellXIndex: NSNumber
@NSManaged var targetCellYIndex: NSNumber
@NSManaged var targetCellType: CellType
// Other properties removed for brevity
}
// From CellTypeLogic.swift -- NOTE: Imports from Foundation and CoreData
extension CellType
{
var typeLabel : String { get { return "Empty"; } }
func isEqualToType(otherCellType : CellType) -> Bool
{
return (self.typeLabel == otherCellType.typeLabel &&
self.maxUses.isEqualToNumber(otherCellType.maxUses) &&
self.useCount.isEqualToNumber(otherCellType.useCount));
}
// Code removed for brevity
}
// From SwitchCellTypeLogic.swift -- NOTE: Imports from Foundation and CoreData
extension SwitchCellType // YES, this compiles with the overrides!
{
override var typeLabel : String { get { return "Switch"; } }
override func isEqualToType(otherCellType : CellType) -> Bool
{
var answer = false;
if let otherSwitchCellType = otherCellType as? SwitchCellType
{
answer = super.isEqualToType(otherCellType) &&
self.targetCellXIndex.isEqualToNumber(otherSwitchCellType.targetCellXIndex) &&
self.targetCellYIndex.isEqualToNumber(otherSwitchCellType.targetCellYIndex) &&
self.targetCellType.isEqualToType(otherSwitchCellType.targetCellType);
}
return answer;
}
// Code removed for brevity
}
Afortunadamente, algún experto amable de Swift ya ve mi problema, pero así es como me enteré: Recientemente traté de agregar funcionalidades similares utilizando métodos que tienen parámetros y / o valores de retorno que no están incorporados en los tipos, pero comencé a obtener esto error: las declaraciones en extensiones no pueden anular aún.
Para explorar este tema, agregué lo siguiente a uno de mis archivos rápidos, pensando que compilaría muy bien:
class A
{
}
class B : A
{
}
extension A
{
var y : String { get { return "YinA"; } }
}
extension B
{
override var y : String { get { return "YinB"; } } // Compiler error (see below) -- What??
}
Para mi sorpresa, recibí el mismo error de compilación (las declaraciones en extensiones no pueden anular aún). ¿Qué? Pero ya he usado ese patrón varias veces sin errores de compilación.
Preguntas: Primero, ¿existen ciertas reglas sobre anulación en las extensiones de modo que en algunos casos se supone que debe funcionar pero en otros casos no? Segundo (y más desconcertante) ¿por qué parece que el compilador de Swift es tan inconsistente? ¿Que me estoy perdiendo aqui? Por favor, ayúdame a restaurar mi fe en Swift.
ACTUALIZAR:
Como se señala en la respuesta correcta de Martin R, parece que puede anular los métodos en la versión actual de Swift (1.1 a través de Xcode 6.1) siempre que (1) involucren solo clases derivadas de NSObject y (2) no utilicen el comando inout modificador Aquí hay algunos ejemplos:
class A : NSObject { }
class B : A { }
class SubNSObject : NSObject {}
class NotSubbed {}
enum SomeEnum { case c1, c2; }
extension A
{
var y : String { get { return "YinA"; } }
func f() -> A { return A(); }
func g(val: SubNSObject, test: Bool = false) { }
func h(val: NotSubbed, test: Bool = false) { }
func j(val: SomeEnum) { }
func k(val: SubNSObject, inout test: Bool) { }
}
extension B
{
// THESE OVERIDES DO COMPILE:
override var y : String { get { return "YinB"; } }
override func f() -> A { return A(); }
override func g(val: SubNSObject, test: Bool) { }
// THESE OVERIDES DO NOT COMPILE:
//override func h(val: NotSubbed, test: Bool = false) { }
//override func j(val: SomeEnum) { }
//override func k(val: SubNSObject, inout test: Bool) { }
}
Experimenté esto en Xcode9. Cerrando y volviendo a abrir, Xcode funcionó para mí. Probablemente un error en el compilador.
Parece que los métodos y propiedades superiores en una extensión funcionan con el Swift actual (Swift 1.1 / Xcode 6.1) solo para los métodos y propiedades compatibles con Objective-C .
Si una clase se deriva de NSObject
, todos sus miembros estarán disponibles automáticamente en Objective-C (si es posible, ver a continuación). Entonces con
class A : NSObject { }
su código de ejemplo compila y funciona como se esperaba. Su extensión de Código de datos anula el trabajo porque NSManagedObject
es una subclase de NSObject
.
Alternativamente, puede usar el atributo @objc para un método o propiedad:
class A { }
class B : A { }
extension A
{
@objc var y : String { get { return "YinA" } }
}
extension B
{
@objc override var y : String { get { return "YinB" } }
}
Los métodos que no son representables en Objective-C no se pueden marcar con @objc
y no se pueden sobrescribir en una extensión de subclase. Eso se aplica, por ejemplo, a los métodos que tienen parámetros o parámetros de un tipo enum
.