在Swift中实现copy()

chy5wohz  于 2023-02-28  发布在  Swift
关注(0)|答案(9)|浏览(271)

我希望能够在Swift中复制一个自定义类。到目前为止,一切顺利。在Objective-C中,我只需要实现NSCopying协议,这意味着实现copyWithZone
例如,我有一个名为Value的基类,它存储了一个NSDecimalNumber

func copyWithZone(zone: NSZone) -> AnyObject! {
    return Value(value: value.copy() as NSDecimalNumber)
}

在Objective-C中,我可以很容易地调用copy来复制我的对象。在Swift中,似乎没有办法调用copy。即使不需要区域,我真的需要调用copyWithZone吗?我需要传递哪个区域作为参数?

vh0rcniy

vh0rcniy1#

copy方法在NSObject中定义。如果您的自定义类不是从NSObject继承的,则copy将不可用。
可以按以下方式为任何对象定义copy

class MyRootClass {
    //create a copy if the object implements NSCopying, crash otherwise

    func copy() -> Any {
        guard let asCopying = ((self as AnyObject) as? NSCopying) else {
            fatalError("This class doesn't implement NSCopying")
        }

        return asCopying.copy(with: nil)
    }
}

class A : MyRootClass {

}

class B : MyRootClass, NSCopying {

    func copy(with zone: NSZone? = nil) -> Any {
        return B()
    }
}

var b = B()
var a = A()

b.copy()  //will create a copy
a.copy()  //will fail

我猜copy并不是一种纯粹的Swift复制对象的方式,在Swift中,它可能是创建复制构造函数(一种接受相同类型对象的初始化器)的更常见方式。

bpzcxfmw

bpzcxfmw2#

嗯,有一个非常简单的解决方案,你不必创建根类。

protocol Copyable {
    init(instance: Self)
}

extension Copyable {
    func copy() -> Self {
        return Self.init(instance: self)
    }
}

现在,如果你想让你的定制类能够复制,你必须使它符合Copyable协议并提供init(instance: Self)实现。

class A: Copyable {
    var field = 0

    init() {
    }

    required init(instance: A) {
        self.field = instance.field
    }

}

最后,您可以在A类的任何示例上使用func copy() -> Self来创建它的副本。

let a = A()
a.field = 1
let b = a.copy()
iqih9akk

iqih9akk3#

您可以编写自己的复制方法

class MyRootClass {
    var value:Int
    init(_ value:Int) {
        self.value = value
    }
    init(other:MyRootClass) {
        self.value = other.value
    }
    func copy() -> MyRootClass {
        return MyRootClass(other: self)
    }
    func printValues() {
        print("Value: \(value)")
    }
}

这样做的好处是当你在你的项目中使用子类时,你可以调用“copy”命令,它会复制子类。如果你只是初始化一个新的子类来复制,你还必须为每个对象重写那个类...

class MySubclass:MyRootClass {
    var otherValue:Int
    init(_ value:Int, _ otherValue:Int){
        self.otherValue = otherValue
        super.init(value)
    }
    override func copy() -> MySubclass {
        return MySubclass(self.value, self.otherValue)
    }
    override func printValues() {
        print("Values: \(value), \(otherValue)")
    }
}

//this is the class that would need to be rewritten unless
//you use this kind of strategy
class Container {
    var variableObject:MyRootClass
    init(_ object:MyRootClass) {
        self.variableObject = object
    }
    func copy() -> Container {
        return Container(self.variableObject)
    }
    func printObject() {
        variableObject.printValues()
    }
}

let container1 = Container(MyRootClass(2))
let container2 = Container(MySubclass(1, 2))

let container1Copy = container1.copy()
let container2Copy = container2.copy()

container1.printObject() //prints "Value: 2"
container1Copy.printObject() //prints "Value: 2"

container2.printObject() //print "Values: 1, 2"
container2Copy.printObject() //print "Values: 1, 2"

我在创建纸牌游戏时特别使用了这一点,因为无论你在玩什么游戏,你都将需要复制“棋盘”,而棋盘是一个更通用的棋盘的子类。

mftmpeh8

mftmpeh84#

在我的例子中,对象链很大,而且是嵌套的,所以我在寻找更简单的解决方案。
核心概念非常简单...通过新初始化复制数据,我使用EncodeDecode深度复制整个对象,因为我的对象已经符合**Codable**,

简单示例:

class MyCodableObject: Codable, CustomStringConvertible {
    var name: String
    var description: String { name }
    
    init(name: String) {
        self.name = name
    }
}

let originalArr = [MyCodableObject(name: "a"), 
                   MyCodableObject(name: "b")]

do {
    let data    = try JSONEncoder().encode(originalArr)
    let copyArr = try JSONDecoder().decode([MyCodableObject].self, from: data)
    
    //modify if required
    copyArr.forEach { obj in
        obj.name = "\(obj.name) modified"
    }

    print(originalArr, copyArr) //-> [a, b] [a modified, b modified]
} catch {
    fatalError(error.localizedDescription)
}

重构(一般解决方案):

为了简化将来的情况,我们可以创建一个提供copy函数的协议。

  • 对于不可编码对象,您必须实现自己的copy函数。*

对于Codable对象,我们可以提供一个默认的实现,这样它就可以随时使用。

protocol Copyable {
    func copy() -> Self
}

extension Copyable where Self: Codable {
    func copy() -> Self {
        do {
            let encoded = try JSONEncoder().encode(self)
            let decoded = try JSONDecoder().decode(Self.self, from: encoded)
            return decoded
        } catch {
            fatalError(error.localizedDescription)
        }
    }
}

现在,我们可以使Codable对象符合Copyable协议,并立即开始使用它。

extension MyCodableObject: Copyable {}

示例:

let a = MyCodableObject(name: "A")
let b = a.copy()
b.name = "B"

print(a.name, b.name) //-> "A B"

我们也可以将Codable对象的ArrayCopyable一致,并立即访问copy函数:

extension Array: Copyable where Element: Codable {}

示例:

let originalArr = [MyCodableObject(name: "a"), 
                   MyCodableObject(name: "b")]

let copyArr = originalArr.copy()
copyArr.forEach { (obj) in
    obj.name = "\(obj.name) modified"
}

print(originalArr, copyArr) //-> [a, b] [a modified, b modified]
x6h2sr28

x6h2sr285#

IMO,实现这一点的最简单方法是:

protocol Copyable
{
  init(other: Self)
}

extension Copyable
{
  func copy() -> Self
  {
    return Self.init(other: self)
  }
}

在结构中实现为:

struct Struct : Copyable
{
  var value: String

  init(value: String)
  {
    self.value = value
  }

  init(other: Struct)
  {
    value = other.value
  }
}

并且,在类中,为:

class Shape : Copyable
{
  var color: NSColor

  init(color: NSColor)
  {
    self.color = color
  }

  required init(other: Shape)
  {
    color = other.color
  }
}

和这样的基类的子类中,如:

class Circle : Shape
{
  var radius: Double = 0.0

  init(color: NSColor, radius: Double)
  {
    super.init(color: color)

    self.radius = radius
  }

  required init(other: Shape)
  {
    super.init(other: other)

    if let other = other as? Circle
    {
      radius = other.radius
    }
  }
}

class Square : Shape
{
  var side: Double = 0.0

  init(color: NSColor, side: Double)
  {
    super.init(color: color)

    self.side = side
  }

  required init(other: Shape)
  {
    super.init(other: other)

    if let other = other as? Square
    {
      side = other.side
    }
  }
}

如果希望能够复制可复制类型的数组:

extension Array where Element : Copyable
{
  func copy() -> Array<Element>
  {
    return self.map { $0.copy() }
  }
}

它允许您执行简单的代码,例如:

{
  let shapes = [Circle(color: .red, radius: 5.0), Square(color: .blue, side: 5.0)]

  let copies = shapes.copy()
}
mspsb9vt

mspsb9vt6#

在我看来,更快捷的方法是在允许定义方法复制的返回类型的可复制协议中使用关联类型。其他方法不允许复制对象树,如:

protocol Copyable {
    associatedtype V
    func copy() -> V
    func setup(v: V) -> V
} 

class One: Copyable {
    typealias T = One
    var name: String?

    func copy() -> V {
        let instance = One()
        return setup(instance)
    }

    func setup(v: V) -> V {
        v.name = self.name
        return v
    }
}

class Two: One {
    var id: Int?
    override func copy() -> Two {
        let instance = Two()
        return setup(instance)
    }

    func setup(v: Two) -> Two {
        super.setup(v)
        v.id = self.id
        return v
    }
}

extension Array where Element: Copyable {
    func clone() -> [Element.V] {
        var copiedArray: [Element.V] = []
        for element in self {
            copiedArray.append(element.copy())
        }
        return copiedArray
    }
}

let array = [One(), Two()]
let copied = array.clone()
print("\(array)")
print("\(copied)")
8dtrkrch

8dtrkrch7#

swift中的可复制示例

    • 注意:**这种复制类示例的方法最大的优点是它不依赖NSObject或objc代码,最重要的是它不会使"Data-Style-Class"混乱。相反,它扩展了扩展"Data-Style-Class"的协议。这样,您可以通过将复制代码放在另一个位置而不是数据本身来更好地划分。只要您在类之后对协议建模,类之间的继承也会得到处理。下面是这种方法的一个示例:
protocol IA{var text:String {get set}}
class A:IA{
    var text:String
    init(_ text:String){
        self.text = text
    }
}
extension IA{
    func copy() -> IA {
        return A(text)
    }
}
protocol IB:IA{var number:Int {get set}}
class B:A,IB{
    var number:Int
    init(_ text:String, _ number:Int){
        self.number = number
        super.init(text)
    }
}
extension IB{
    func copy() -> IB {
        return B(text,number)
    }
}
let original = B("hello",42)
var uniqueCopy = original.copy()
uniqueCopy.number = 15
Swift.print("uniqueCopy.number: " + "\(uniqueCopy.number)")//15
Swift.print("original.number: " + "\(original.number)")//42
    • 注意:**要查看此方法在实际代码中的实现:然后看看这个图形框架OSX:(永久链接)www.example.comhttps://github.com/eonist/Element/wiki/Progress2#graphic-framework-for-osx

不同的形状使用相同的样式,但每个样式都使用一个styl.copy()调用来创建一个唯一的示例,然后在这个副本上设置一个新的渐变,而不是在原始引用上,如下所示:

上面示例的代码如下所示:

/*Gradients*/
let gradient = Gradient(Gradients.red(),[],GradientType.Linear,π/2)
let lineGradient = Gradient(Gradients.teal(0.5),[],GradientType.Linear,π/2)
/*Styles*/
let fill:GradientFillStyle = GradientFillStyle(gradient);
let lineStyle = LineStyle(20,NSColorParser.nsColor(Colors.green()).alpha(0.5),CGLineCap.Round)
let line = GradientLineStyle(lineGradient,lineStyle)
/*Rect*/
let rect = RectGraphic(40,40,200,200,fill,line)
addSubview(rect.graphic)
rect.draw()
/*Ellipse*/
let ellipse = EllipseGraphic(300,40,200,200,fill.mix(Gradients.teal()),line.mix(Gradients.blue(0.5)))
addSubview(ellipse.graphic)
ellipse.draw()
/*RoundRect*/
let roundRect = RoundRectGraphic(40,300,200,200,Fillet(50),fill.mix(Gradients.orange()),line.mix(Gradients.yellow(0.5)))
addSubview(roundRect.graphic)
roundRect.draw()
/*Line*/
let lineGraphic = LineGraphic(CGPoint(300,300),CGPoint(500,500),line.mix(Gradients.deepPurple()))
addSubview(lineGraphic.graphic)
lineGraphic.draw()
    • 注:**

复制调用实际上是在mix()方法中完成的,这样做是为了使代码更加紧凑,并且可以方便地立即返回示例。PERMALINK用于本示例的所有支持类:https://github.com/eonist/swift-utils

pxyaymoc

pxyaymoc8#

仅当使用ObjectMapper库时:这样做

let groupOriginal = Group(name:"Abc",type:"Public")    
let groupCopy = Mapper<Group>().mapAny(group.toJSON())! //where Group is Mapable
eblbsuwk

eblbsuwk9#

Swift making copies of passed class instances
如果您在这里使用已接受答案中的代码(OP回答了他们自己的问题),只要您的类是NSObject的子类,并且在该帖子中使用复制协议,它将通过调用copyOfValues()函数按预期工作。
这样,就没有了繁琐的设置或复制功能,您无需将所有示例变量分配给新示例。
我应该知道,我写了代码,并测试了它XD

相关问题