如何在Swift子类中实现复制构造函数?

时间:2021-10-24 14:31:51

I have the following example in a Swift playground, in an attempt to implement a copy constructor in 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}

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:

问题是,这实际上并没有以其当前形式进行编译。在Square子类中的init(copyFrom:Square)方法上,报告此错误:

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

使用选择器'initWithCopyFrom:'的覆盖方法具有不兼容的类型'(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.

如果我将Shape更改为不再扩展NSObject,则此问题将消失。但是,由于包含现有的Objective-C代码,它需要扩展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?

如何更新我的复制构造函数以允许Shape知道它是从Shape复制,并允许Square知道它是从Square复制?

2 个解决方案

#1


19  

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.

init(copyFrom:Square)是init的重载,而不是覆盖(copyFrom:Shape)。我的意思是它们是不相关的方法,因为它们接受不同的类型。在斯威夫特这是可以接受的。在ObjC,这是非法的。 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.

Swift初始值设定项不会自动继承。所以在Swift中,你无法尝试将随机Shape复制为Square。初始化程序不可用。但是在ObjC中,初始化器会自动继承(并且你无法阻止它们这样做)。因此,如果你有一个方法initWithCopyFrom:(* Shape),则要求每个子类都愿意接受它。这意味着您可以(在ObjC中)尝试创建Circle的副本作为Square。那当然是胡说八道。

If this is an NSObject subclass, you should use NSCopying. Here's how you would go about that:

如果这是一个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"}

Swift 3

斯威夫特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
    }

}

#2


2  

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).

最简单的方法就是将子类初始化程序的名称更改为init(copyFromSquare:Square),保留Square与init(copyFrom:Shape)方法的完整性(因为您通过继承自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.

您当然可以覆盖init(copyFrom:Shape),并测试copyFrom是否为Square,在这种情况下,您采取一个操作过程(设置长度),否则不行。

Note also that you need to set self.length before you call the super.

另请注意,在调用super之前需要设置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


19  

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.

init(copyFrom:Square)是init的重载,而不是覆盖(copyFrom:Shape)。我的意思是它们是不相关的方法,因为它们接受不同的类型。在斯威夫特这是可以接受的。在ObjC,这是非法的。 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.

Swift初始值设定项不会自动继承。所以在Swift中,你无法尝试将随机Shape复制为Square。初始化程序不可用。但是在ObjC中,初始化器会自动继承(并且你无法阻止它们这样做)。因此,如果你有一个方法initWithCopyFrom:(* Shape),则要求每个子类都愿意接受它。这意味着您可以(在ObjC中)尝试创建Circle的副本作为Square。那当然是胡说八道。

If this is an NSObject subclass, you should use NSCopying. Here's how you would go about that:

如果这是一个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"}

Swift 3

斯威夫特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
    }

}

#2


2  

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).

最简单的方法就是将子类初始化程序的名称更改为init(copyFromSquare:Square),保留Square与init(copyFrom:Shape)方法的完整性(因为您通过继承自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.

您当然可以覆盖init(copyFrom:Shape),并测试copyFrom是否为Square,在这种情况下,您采取一个操作过程(设置长度),否则不行。

Note also that you need to set self.length before you call the super.

另请注意,在调用super之前需要设置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)
    }
}