it-swarm.dev

Czy możesz zastąpić rozszerzenia w Swift, czy nie? (Kompilator wydaje się zdezorientowany!)

Pracowałem nad aplikacją iOS w Swift (większość z nich została przeniesiona z Objective-C). Używam Core Data i próbuję używać rozszerzeń, aby dodać funkcjonalność do klas generowanych automatycznie z mojego modelu. Jedną z rzeczy, które chętnie zrobiłem w Objective-C, było dodanie metody w kategorii klasy A i zastąpienie tej metody w kategorii w klasie B (która wywodzi się z A) i miałem nadzieję zrobić to samo w Swift.

Przez pewien czas miałem w swoim projekcie następujący kod (i to jest tylko jeden przykład) i chociaż nie korzystałem jeszcze z tej funkcjonalności, kompilator pracował dobrze, kompilując ten kod:

// 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
}

Mam nadzieję, że jakiś ekspert Swift już zobaczy mój problem, ale oto, jak dowiedziałem się o tym: Ostatnio próbowałem dodać podobną funkcjonalność za pomocą metod, które mają parametry i/lub zwracają wartości, które nie są wbudowane, ale zacząłem to uzyskiwać błąd: deklaracje w rozszerzeniach nie mogą jeszcze zostać zastąpione.

Aby zbadać ten problem, dodałem następujące elementy do jednego z moich plików Swift, myśląc, że kompiluje się on dobrze:

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??
}

Ku mojemu zdziwieniu otrzymałem ten sam błąd kompilatora (deklaracje w rozszerzeniach nie mogą jeszcze zostać zastąpione). Co? Ale kilkakrotnie użyłem tego pattera bez błędów kompilatora.

Pytania: Po pierwsze, czy istnieją pewne zasady dotyczące nadpisywania rozszerzeń w taki sposób, że w niektórych przypadkach ma to działać, aw innych nie? Po drugie (i bardziej niepokojące), dlaczego wydaje się, że kompilator Swift jest tak niespójny? Czego mi tu brakuje? Pomóż mi przywrócić moją wiarę w Swifta.

AKTUALIZACJA:

Jak zauważyłem w poprawnej odpowiedzi Martina R., wydaje się, że możesz zastąpić metody w obecnej wersji Swift (1.1 przez Xcode 6.1), o ile (1) dotyczą tylko klas pochodzących z NSObject i (2) nie używają inout modyfikator. Oto kilka przykładów:

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) { }

}
33
FTLPhysicsGuy

Wydaje się, że przesłonięcie metod i właściwości w rozszerzeniu działa z bieżącymi Swift (Swift 1.1/Xcode 6.1) tylko dla zgodnych z Object-C metod i właściwości.

Jeśli klasa pochodzi od NSObject, wówczas wszyscy jej członkowie są automatycznie dostępni w Objective-C (jeśli to możliwe, patrz poniżej). Więc z

class A : NSObject { }

twój przykładowy kod kompiluje się i działa zgodnie z oczekiwaniami. Twoje rozszerzenie danych kodu działa, ponieważ NSManagedObject jest podklasą NSObject.

Alternatywnie możesz użyć atrybutu @objc dla metody lub właściwości:

class A { }

class B : A { }

extension A
{
    @objc var y : String { get { return "YinA" } }
}

extension B
{
   @objc override var y : String { get { return "YinB" } }
}

Metod, które nie są reprezentowalne w celu C, nie można oznaczyć @objc i nie można ich przesłonić w rozszerzeniu podklasy. Dotyczy to na przykład metod mających parametry inout lub parametry typu enum.

38
Martin R

Doświadczyłem tego na Xcode9. Zamknięcie i ponowne otwarcie Xcode zadziałało dla mnie. Prawdopodobnie błąd w kompilatorze.

0
Vincent