Как реализовать конструктор копирования в подклассе 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
?
Ответы
Ответ 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)
}
}