Подтвердить что ты не робот

Как реализовать конструктор копирования в подклассе Swift?

У меня есть следующий пример на игровой площадке Swift, в попытке реализовать конструктор копирования в Swift:

class Shape : NSObject {
    var color : String

    override init() {
        color = "Red"
    }

    init(copyFrom: Shape) {
        color = copyFrom.color
    }
}

class Square : Shape {
    var length : Double

    override init() {
        super.init()
        length = 10.0
    }

    init(copyFrom: Square) { /* Compilation error here! */
        super.init(copyFrom: copyFrom)
        length = copyFrom.length
    }
}

let s : Square = Square()      // {{color "Red"} length 10.0}

let copy = Square(copyFrom: s) // {{color "Red"} length 10.0}

s.color = "Blue"               // {{color "Blue"} length 10.0}
s                              // {{color "Blue"} length 10.0}
copy                           // {{color "Red"} length 10.0}

Проблема заключается в том, что она фактически не компилируется в текущей форме. В методе init(copyFrom: Square) в подклассе Square сообщается об этой ошибке:

Overriding method with selector 'initWithCopyFrom:' has incompatible type '(Square) -> Square'

Эта проблема имеет смысл, если она не была конструктором, как если бы она была регулярной func, вы могли бы передать тип, ожидаемый в суперклассе, но это были переопределены в подклассе, чтобы быть более ограничительными:

let mySquare : Shape = Square()  // Note the var is a SHAPE
mySquare.someShapeMethod("Test") // If Square overrides someShapeMethod() to expect Int, compiler errors out to protect us here.

Но тот факт, что он конструктор заставляет меня поверить, что я должен уметь переопределять его и предоставлять другую сигнатуру метода, так как во время компиляции известно, что такое тип объекта.

Эта проблема исчезает, если я изменяю Shape, чтобы больше не расширять NSObject. Однако из-за включения в существующий код Objective-C необходимо расширить NSObject.

Как мне обновить конструктор копирования, чтобы Shape знал, что он копирует из Shape, и разрешает Square знать его копирование с Square?

4b9b3361

Ответ 1

init(copyFrom: Square) является перегрузкой, а не переопределением init(copyFrom: Shape). Я имею в виду, что это не связанные методы, потому что они принимают разные типы. В Swift это приемлемо. В ObjC это незаконно. В ObjC нет перегрузок.

Инициализаторы Swift не наследуются автоматически. Так что в Swift вы не можете скопировать случайную Shape в виде Square. Инициализатор недоступен. Но в ObjC инициализаторы автоматически наследуют (и вы не можете помешать им сделать это). Поэтому, если у вас есть метод initWithCopyFrom:(*Shape), требуется, чтобы каждый подкласс был готов принять его. Это означает, что вы можете (в ObjC) попытаться создать копию круга в виде квадрата. Это конечно ерунда.

Если это NSObject подкласс, вы должны использовать NSCopying. Вот как бы вы поступили по этому поводу:

import Foundation

class Shape : NSObject, NSCopying { // <== Note NSCopying
  var color : String

  required override init() { // <== Need "required" because we need to call dynamicType() below
    color = "Red"
  }

  func copyWithZone(zone: NSZone) -> AnyObject { // <== NSCopying
    // *** Construct "one of my current class". This is why init() is a required initializer
    let theCopy = self.dynamicType()
    theCopy.color = self.color
    return theCopy
  }
}

class Square : Shape {
  var length : Double

  required init() {
    length = 10.0
    super.init()
  }

  override func copyWithZone(zone: NSZone) -> AnyObject { // <== NSCopying
    let theCopy = super.copyWithZone(zone) as Square // <== Need casting since it returns AnyObject
    theCopy.length = self.length
    return theCopy
  }

}

let s = Square()      // {{color "Red"} length 10.0}

let copy = s.copy() as Square // {{color "Red"} length 10.0} // <== copy() requires a cast

s.color = "Blue"               // {{color "Blue"} length 10.0}
s                              // {{color "Blue"} length 10.0}
copy                           // {{color "Red"}

Свифт 3

class Shape: NSObject, NSCopying {

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

    func copy(with zone: NSZone? = nil) -> Any {
        let copy = type(of: self).init()
        return copy
    }

}

class Square: Shape {

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

    func copy(with zone: NSZone? = nil) -> Any {
        let copy = super.copy(with: zone) as! Square
        copy.foo = self.foo
        ......
        return copy
    }

}

Ответ 2

Простейшим способом сделать это было бы просто изменить имя инициализатора подкласса на init(copyFromSquare: Square), оставив Square с методом init(copyFrom: Shape) неповрежденным (так как вы сократились, наследуя от Shape).

Вы могли бы, конечно, переопределить init(copyFrom: Shape) и проверить, является ли copyFrom Square, и в этом случае вы выполняете один курс действия (задаете длину), иначе нет.

Обратите внимание, что вам нужно установить self.length до, вы вызываете супер.

class Shape : NSObject {
    var color : String

    override init() {
        color = "Red"
    }

    init(copyFrom: Shape) {
        color = copyFrom.color
    }
}

class Square : Shape {
    var length : Double

    override init() {
        self.length = 10.0
        super.init()
    }

    override init(copyFrom: Shape) {
        if copyFrom is Square {
            self.length = (copyFrom as Square).length
        } else {
            self.length = 10.0 // default
        }
        super.init(copyFrom: copyFrom)
    }
}