如何在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}
的问题是,这实际上并不在其目前的形式编译。论Square
子类中的方法init(copyFrom: 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
复制?
init(copyFrom: Square)
是init(copyFrom: Shape)
的过载而不是覆盖。我的意思是他们是不相关的方法,因为他们接受不同的类型。在Swift中可以接受。在ObjC,这是非法的。 ObjC中没有重载。
Swift初始化器不会自动继承。所以在Swift中,你不能尝试复制Shape
作为Square
。初始化程序不可用。但在ObjC中,初始化程序做了自动继承(并且不能阻止它们这样做)。所以如果你有一个方法initWithCopyFrom:(*Shape)
,那就要求每个子类都愿意接受它。这意味着你可以(在ObjC中)尝试创建一个Circle作为Square的副本。这当然是无稽之谈。
如果这是一个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: NSObject, NSCopying {
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
}
}
做到这一点,简直是改变子类初始化器来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)
}
}
+1我喜欢将方法名更改为'copyFromSquare:'的想法,将其留给子类来命名。 'copyFromTriangle:'等 – 2014-09-12 13:28:28
请注意,这会使copyFrom:ObjC中的Shape可用于所有子类,这将创建不正确的副本。如果您使用类命名方法,请确保您从超类中移除复制初始值设定项,或将其标记为“必需”,以便所有子类被强制以某种方式实现它(即使仅通过抛出'precondition()') 。 – 2014-09-12 13:31:30
感谢罗布 - 可靠的答案。 – 2014-09-12 13:32:41
你也可能会发现这个讨论对于在纯Swift中制作副本很有用:http://stackoverflow.com/questions/25645090/protocol-func-returning-self – 2014-09-12 13:41:17
对于Swift 2/Xcode 7,它会是'let theCopy = self。 dynamicType.init()',当然'as!'而不是'as'(仅仅提到,因为相关的问题出现在这里:http://stackoverflow.com/questions/31885231/using-object-initializers-in-迅速更换的-allocwithzone)。 – 2015-08-07 19:53:56