I have the following example in a Swift playground, in an attempt to implement a copy constructor in 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}
The problem is that this doesn't actually compile in its current form. On the init(copyFrom: Square)
method in the Square
subclass, this error is reported:
Overriding method with selector 'initWithCopyFrom:' has incompatible type '(Square) -> Square'
This issue would make sense if it wasn't a constructor, as if it were a regular func
, you could potentially pass in a type that is expected in the superclass, but that has been overridden in the subclass to be more restrictive:
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.
But the fact that it's a constructor leads me to believe that I should be able to override it and provide a different method signature, since it's absolutely known at compile time what the type of the object is.
This issue disappears if I alter Shape
to no longer extend NSObject
. However, due to inclusion with an existing Objective-C code, it needs to extend NSObject
.
How can I update my copy constructor to allow a Shape
to know it's copying from a Shape
, and allow a Square
to know it's copying from a Square
?
init(copyFrom: Square)
is an overload, not an override, of init(copyFrom: Shape)
. What I mean is that they are unrelated methods because they accept different types. In Swift that's acceptable. In ObjC, that's illegal. There are no overloads in ObjC.
Swift initializers don't automatically inherit. So in Swift, you couldn't try to copy a random Shape
as a Square
. The initializer isn't available. But in ObjC, initializers do automatically inherit (and you can't stop them from doing so). So if you have a method initWithCopyFrom:(*Shape)
, it is required that every subclass be willing to accept it. That means you could (in ObjC) try to create a copy of a Circle as a Square. That's of course nonsense.
If this is an NSObject
subclass, you should use NSCopying
. Here's how you would go about that:
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"}
Swift 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
}
}
The simplest way to do it would simply be to change the name of the subclass initialiser to init(copyFromSquare: Square)
, leaving Square
with the init(copyFrom: Shape)
method intact (as you have contracted by inheriting from Shape
).
You could of course override init(copyFrom: Shape)
, and test whether copyFrom
is a Square
, in which case you take one course of action (set the length), otherwise not.
Note also that you need to set self.length
before you call the super.
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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With