在Swift中实现copy()

rus*_*y1s 33 swift

我希望能够在Swift中复制自定义类.到现在为止还挺好.在Objective-C中,我只需要实现NSCopying协议,这意味着实现copyWithZone.

举个例子,我有一个叫做Value存储a 的基本类NSDecimalNumber.

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

在Objective-C I中,可以轻松地调用copy复制我的对象.在Swift中,似乎没有办法打电话copy.copyWithZone即使不需要区域,我真的需要打电话吗?我需要将哪个区域作为参数传递?

Sul*_*han 30

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
Run Code Online (Sandbox Code Playgroud)

我想这copy不是一个纯粹的Swift复制对象的方式.在Swift中,它可能是创建复制构造函数(一种采用相同类型的对象的初始化程序)的更常用方法.

  • 感谢您的建议.我做了你在评论中建议的内容,并创建了一个初始化函数,它接受类的现有实例并从中创建新实例.这似乎是一种更"快速"的操作方式,而不必恢复到NSObject的子类. (8认同)

Dan*_*aan 18

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

class MyRootClass {
    var someVariable:Int
    init() {
        someVariable = 2
    }
    init(otherObject:MyRootClass) {
        someVariable = otherObject.someVariable
    }
    func copy() -> MyRootClass {
       return MyRootClass(self)
    }
}
Run Code Online (Sandbox Code Playgroud)

这样做的好处是当您在项目周围使用子类时,可以调用'copy'命令,它将复制子类.如果您只是初始化一个要复制的新类,您还必须为每个对象重写该类...

var object:Object
....
//This code will only work for specific class
var objectCopy = Object()

//vs

//This code will work regardless of whether you are using subClass or      superClass
var objectCopy = object.copy()
Run Code Online (Sandbox Code Playgroud)


Mis*_*ewb 17

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

protocol Copyable {
    init(instance: Self)
}

extension Copyable {
    func copy() -> Self {
        return Self.init(instance: self)
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果要使自定义类能够复制,则必须使其符合Copyable协议并提供init(instance: Self)实现.

class A: Copyable {
    var field = 0

    init() {
    }

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

}
Run Code Online (Sandbox Code Playgroud)

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

let a = A()
a.field = 1
let b = a.copy()
Run Code Online (Sandbox Code Playgroud)

  • 使用该解决方案,您会遇到子类化问题,因为子类必须使用超类类型的参数实现所需的init.然后你需要垂头丧气,听起来不太好. (3认同)

sta*_*Man 7

就我而言,对象链很大并且嵌套,因此正在寻找更简单的解决方案。

核心概念非常简单...通过新的初始化复制数据,我使用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)
}
Run Code Online (Sandbox Code Playgroud)

重构(通用解决方案):

为了简化未来的情况,我们可以创建一个提供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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

extension MyCodableObject: Copyable {}
Run Code Online (Sandbox Code Playgroud)

例子:

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

print(a.name, b.name) //-> "A B"
Run Code Online (Sandbox Code Playgroud)

我们还可以使对象符合ArrayCodable立即访问Copyable该函数:copy

extension Array: Copyable where Element: Codable {}
Run Code Online (Sandbox Code Playgroud)

例子:

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]
Run Code Online (Sandbox Code Playgroud)


小智 5

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

protocol Copyable
{
  init(other: Self)
}

extension Copyable
{
  func copy() -> Self
  {
    return Self.init(other: self)
  }
}
Run Code Online (Sandbox Code Playgroud)

在结构中实现为:

struct Struct : Copyable
{
  var value: String

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

  init(other: Struct)
  {
    value = other.value
  }
}
Run Code Online (Sandbox Code Playgroud)

并且,在一个班级中,作为:

class Shape : Copyable
{
  var color: NSColor

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

  required init(other: Shape)
  {
    color = other.color
  }
}
Run Code Online (Sandbox Code Playgroud)

并且在这样的基类的子类中:

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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  let copies = shapes.copy()
}
Run Code Online (Sandbox Code Playgroud)