Внедрение NSCopying в Swift с подклассами

Рассмотрим два класса. Первый - это Vehicle, подкласс NSObject, который соответствует NSCopying:

class Vehicle : NSObject, NSCopying {

    var wheels = 4

    func copyWithZone(zone: NSZone) -> AnyObject {
        let vehicle = self.dynamicType()
        vehicle.wheels = self.wheels
        return vehicle
    }
}

Второй класс Starship наследует от Vehicle:

class Starship : Vehicle {

    var photonTorpedos = 6
    var antiGravity = true

    override func copyWithZone(zone: NSZone) -> AnyObject {
        let starship = super.copyWithZone(zone) as Starship

        starship.photonTorpedos = self.photonTorpedos
        starship.antiGravity = self.antiGravity
        return starship
    }
}

Этот код не компилируется, потому что:

Построение объекта типа класса "Транспортное средство" с значением метатипа должно использовать "требуемый" инициализатор.

Итак, я иду и добавляю требуемый инициализатор:

required override init () {
    super.init()
}

И теперь приложение компилируется, а Starship объекты отвечают на copy() правильно.

Два вопроса:

  • Почему для построения объекта с метатипом нужен инициализатор required? (Кажется, инициализатор, который я написал, ничего не делает.)
  • Есть ли что-то, что я написал неправильно, или добавить в инициализатор? Есть ли случай, который я не рассматриваю?

Ответ 1

Короткий ответ

Вы не можете использовать self.dynamicType() без маркировки init() как required, потому что нет никаких подклассов Vehicle, которые также будут реализованы init().

Изучение проблемы

Взглянув на Язык быстрого программирования: Инициализация, он упомянул, как

Подклассы

не наследуют свои инициализаторы суперкласса по умолчанию

Ситуации, в которых подкласс будет наследовать свои инициализаторы суперкласса:

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

Правило 1

Если ваш подкласс не определяет какие-либо назначенные инициализаторы, он автоматически наследует все его инициализаторы, назначенные суперклассам.

Правило 2

Если ваш подкласс обеспечивает реализацию всего своего суперкласса назначенные инициализаторы - либо путем наследования их в соответствии с правилом 1, либо путем предоставляя пользовательскую реализацию как часть ее определения - тогда это автоматически наследует все инициализаторы удобства суперкласса.

Взгляните на пример:

class MySuperclass {
    let num = 0

    // MySuperclass is given `init()` as its default initialiser
    // because I gave `num` a default value.
}

class MySubclass : MySuperclass {
    let otherNum: Int

    init(otherNum: Int) {
        self.otherNum = otherNum
    }
}  

В соответствии с приведенной выше информацией, поскольку MySubclass определил свойство otherNum без начального значения, оно не наследует автоматически init() из MySuperclass.

Теперь предположим, что я хочу добавить следующий метод в MySuperclass:

func myMethod() {
    println(self.dynamicType().num)
}

Вы получите описанную вами ошибку, потому что нет никаких подклассов MySuperclass для реализации MySuperclass будет реализовывать init() (и в этом примере это не так).

Чтобы решить эту проблему, вам необходимо пометить init() как required, чтобы гарантировать, что все подклассы MySuperclass реализуют init(), и поэтому вызов self.dynamicType() - это правильная вещь. Это та же проблема, что и в вашем вопросе: Swift знает Vehicle реализует init(), однако он не знает, что какие-либо подклассы будут реализовывать init(), и поэтому вам нужно сделать это required.

Альтернативным решением, которое не подходит в вашем примере, является отметка Vehicle как final, то есть Vehicle не может быть подклассифицирована. Тогда вы сможете использовать self.dynamicType(); но вы можете просто использовать Vehicle() в этом случае.

Ответ 2

Вам нужен инициализатор required, потому что для него требуется реализация подкласса требуемого инициализатора в Swift.

Per Swift Documentation Обязательные nitializers:

Вы также должны написать требуемый модификатор перед каждым подклассом реализации требуемого инициализатора, чтобы указать, что Требование инициализатора применяется к дополнительным подклассам в цепочке.