在 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】:
在我的例子中,对象链很大并且是嵌套的,所以正在寻找更简单的解决方案。
核心概念很简单...通过新的初始化复制数据,我使用Encode
和Decode
深度复制整个对象,因为我的对象已经符合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
对象的Array
与Copyable
一致,并立即访问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