在 Swift 中实现 copy()

Posted

技术标签:

【中文标题】在 Swift 中实现 copy()【英文标题】:Implementing copy() in Swift 【发布时间】:2014-06-16 11:26:47 【问题描述】:

我希望能够在 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 吗?我需要将哪个区域作为参数传递?

【问题讨论】:

Value 是否扩展 NSObject 没有。当我这样做时,我可以致电copy()。谢谢。但建议从 NSObject 继承我所有的自定义类? 稍微扩展了我的答案。 Copy() 与 Obj-C 密切相关。最好的解决方案是在纯粹的 swift 中避免它。如果需要,子类化 NSObject 是个好主意。 对于Array(),函数unshare()可以使用,但是Swift中一般没有这样的方法。 @rusty1s 怀疑您仍在寻找答案,但我为您发布了一个可能更容易理解的新答案 【参考方案1】:

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 中,创建复制构造函数(一个接受相同类型对象的初始化器)可能是一种更常见的方法。

【讨论】:

感谢您的建议。我按照您在 cmets 中的建议进行了操作,并创建了一个初始化函数,该函数采用该类的现有实例并从中创建新实例。这似乎是一种更“Swift”的操作方式,而不是必须恢复为子类 NSObject。【参考方案2】:

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

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

【讨论】:

使用该解决方案,您会遇到子类化问题,因为子类必须使用超类类型的参数来实现所需的 init。然后你需要低调,这听起来不太好。 如何一次复制一个类的所有属性,而不是一个一个地做赋值。因为您看到我有一个包含大约 50 个属性的类,并且我很可能会错过其中一些属性的分配。有什么办法吗? @zulkarnainshah 在这种情况下,我建议使用结构,因为默认情况下这种行为适用于结构。【参考方案3】:

你可以自己写复制方法

class MyRootClass 
    var someVariable:Int
    init() 
        someVariable = 2
    
    init(otherObject:MyRootClass) 
        someVariable = otherObject.someVariable
    
    func copy() -> MyRootClass 
       return MyRootClass(self)
    

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

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

【讨论】:

新关键字在这里没有意义:new Object() 编辑修复了几个错别字。 好主意。使用它时要小心,因为如果类属性不是按值传递(如整数 someVariable),而是按引用传递(如 UIView),则可能会产生强引用循环。【参考方案4】:

在我的例子中,对象链很大并且是嵌套的,所以正在寻找更简单的解决方案。

核心概念很简单...通过新的初始化复制数据,我使用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 函数。 em> 对于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]

【讨论】:

【参考方案5】:

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
    
  

如果您希望能够复制一组 Copyable 类型:

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

【讨论】:

【参考方案6】:

在我看来,更 Swifty 的方法是在 Copyable 协议中使用关联类型,它允许为方法复制定义返回类型。其他方式不允许像这样复制对象树:

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

【讨论】:

源码在这里:gist.github.com/dydus0x14/6fbfabcbf077e7d84ff04ca98b5cd791【参考方案7】:

swift 中的可复制实例

注意: 这种复制 Class 实例的方法的好处在于它不依赖 NSObject 或 objc 代码,最重要的是它不会弄乱“Data-Style-Class”。相反,它扩展了扩展“数据样式类”的协议。这样,您可以通过将复制代码放在另一个地方而不是它自己的数据来更好地划分。只要您在类之后对协议进行建模,类之间的继承也会得到处理。这是这种方法的一个示例:

protocol IAvar 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:IAvar 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 图形框架:(PERMALINK)https://github.com/eonist/Element/wiki/Progress2#graphic-framework-for-osx

不同的形状使用相同的样式,但每种样式都使用 style.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

【讨论】:

【参考方案8】:

仅当您使用 ObjectMapper 库时: 这样做

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

【讨论】:

【参考方案9】:

Swift making copies of passed class instances

如果您在此处使用已接受答案中的代码(OP 回答了他们自己的问题),只要您的类是 NSObject 的子类并在该帖子中使用复制协议,它就会通过调用 copyOfValues( ) 函数。

有了这个,您无需繁琐的设置或复制功能,您需要将所有实例变量分配给新实例。

我应该知道,我写了那个代码并刚刚测试了它XD

【讨论】:

以上是关于在 Swift 中实现 copy()的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Webview 中实现自己的 SelectAll、Cut、Copy、Paste

在 Swift 中实现 PopOver

在 Swift 中实现搜索栏

在类中实现委托(iOS/Swift)

在 Swift 中实现 RCTEventEmitter 但未设置桥接异常

如何在 Swift iOS 中实现 iMessage 渐变?