Можете ли вы переопределить между расширениями в 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 уже увидел мою проблему, но вот как я узнал об этом: недавно я пытался добавить аналогичную функциональность, используя методы, которые имеют параметры и/или возвращаемые значения, которые не встроены в типы, но я начал получать это error: Объявления в расширениях пока не могут переопределять.

Чтобы изучить эту проблему, я добавил следующее в один из моих файлов 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??
}

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

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

ОБНОВЛЕНИЕ:

Как отмечено в правильном ответе Мартина Р., похоже, вы можете переопределить методы в текущей версии 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) { }

}

person FTLPhysicsGuy    schedule 24.11.2014    source источник


Ответы (2)


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

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

class A : NSObject { }

ваш примерный код компилируется и работает как положено. Ваше расширение Code Data переопределяет работу, поскольку 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 типа.

person Martin R    schedule 24.11.2014
comment
Кажется, это правильный ответ. Кроме того, вы, похоже, не можете переопределить методы, использующие inout. Я проверил это сам и добавлю обновление к моему вопросу, чтобы привести несколько простых примеров. Спасибо! - person FTLPhysicsGuy; 24.11.2014
comment
@FTLPhysicsGuy: Вы правы, и мой ответ был неполным. Настоящим критерием является то, совместим ли метод или свойство с Objective-C или нет. Я обновил ответ соответственно. - person Martin R; 24.11.2014
comment
У меня такая же проблема с использованием xCode 7.2. В сообщении говорится, что объявления в расширениях еще не могут быть переопределены. Для меня это огромная проблема, потому что я хочу иметь некоторые методы как для UICollectionViewCell, так и для UICollectionReusableView, которые можно переопределить позже в его подклассах. - person Darius; 25.01.2016

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

person Vincent    schedule 03.10.2017