Можете ли вы переопределить между расширениями в Swift или нет? (Компилятор кажется смущенным!)

Я работаю над приложением iOS в Swift (большая часть его перемещается из Objective-C). Я использую Core Data и пытаюсь использовать расширения, чтобы добавить функциональность к классам, автоматически генерируемым из моей модели. Одно, что я легко сделал в Objective-C, заключалось в том, чтобы добавить метод в категорию класса A и переопределить этот метод в категории класса B (который получен из A), и я надеялся сделать то же самое в Swift.

Некоторое время теперь у меня был следующий код в моем проекте (и это всего лишь один пример), и хотя я еще не использовал эту функциональность, компилятор отлично выполнил компиляцию этого кода:

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

Надеюсь, какой-то эксперт Swift уже видит мою проблему, но вот как я узнал об этом: недавно я попытался добавить аналогичную функциональность, используя методы, которые имеют параметры и/или возвращающие значения, которые не построены в типах, но я начал получать эту ошибку: объявления в расширениях еще не могут переопределить.

Чтобы изучить эту проблему, я добавил следующее в один из моих быстрых файлов, считая, что он скомпилируется просто:

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

К моему удивлению, я получил ту же ошибку компилятора (объявления в расширениях еще не могут переопределить). Какие? Но я использовал этот patter несколько раз уже без ошибок компилятора.

Вопросы: Во-первых, существуют ли определенные правила об переопределении в расширениях, которые в некоторых случаях должны работать, но в других случаях это не так? Второе (и более смущающее) почему кажется, что компилятор Swift настолько непоследовательен? Что мне здесь не хватает? Пожалуйста, помогите мне восстановить мою веру в Свифт.

UPDATE:

Как указано в правильном ответе Мартина R, кажется, что вы можете переопределить методы в текущей версии Swift (1.1 через Xcode 6.1), если они (1) включают только классы, полученные из NSObject, и (2) не используйте модификатор inout. Вот несколько примеров:

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

}

Ответ 1

Кажется, что переопределяющие методы и свойства в расширении работают с текущий Swift (Swift 1.1/Xcode 6.1) только для Objective-C совместимых методы и свойства.

Если класс получен из NSObject, то все его члены автоматически доступны в Objective-C (если возможно, см. ниже). Итак,

class A : NSObject { }

ваш примерный код компилируется и работает как ожидалось. Расширение расширения кода кода потому что NSManagedObject является подклассом NSObject.

В качестве альтернативы вы можете использовать атрибут @objc для метода или свойства:

class A { }

class B : A { }

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

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

Методы, которые не могут быть представлены в Objective-C, не могут быть отмечены знаком @objc и не может быть переопределено в расширении подкласса. Это относится, например, к методы, имеющие параметры inout или параметры типа enum.

Ответ 2

Я испытал это на Xcode9. Закрытие и повторное открытие Xcode работало для меня. Вероятно, ошибка в компиляторе.