I want to be able to copy a custom class in Swift. So far, so good. In Objective-C I just had to implement the NSCopying
protocol, which means implementing
NOTE: The great thing about this approach to copying Class instances is that it doesn't rely on NSObject or objc code, and most importantly it doesn't clutter up the "Data-Style-Class". Instead it extends the protocol that extends the "Data-Style-Class". This way you can compartmentalize better by having the copy code in another place than the data it self. The inheritance between classes is also taken care of as long as you model the protocols after the classes. Here is an example of this approach:
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
NOTE: To see an implementation of this approach in real code: Then check out this Graphic Framework for OSX: (PERMALINK) https://github.com/eonist/Element/wiki/Progress2#graphic-framework-for-osx
The different shapes uses the same style but each style uses a style.copy() call to create an unique instance. Then a new gradient is set on this copy rather than on the original reference like this:
The code for the above example goes like this:
/*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()
NOTE:
The copy call is actually done in the mix() method. This is done so that code can be more compact and an instance is conveniently returned right away.
PERMALINK for all the supporting classes for this example: https://github.com/eonist/swift-utils
I was looking for a simpler solution. The concept was simple enough, duplicate the data by initialization however in my case the object chain was large and nested.
Luckily my objects were already conforming to Codable
so I simply used Encode
and Decode
to deep-copy the entire object.
class MyObject: Codable, CustomStringConvertible {
var name: String
var description: String { name }
init(name: String) {
self.name = name
}
}
let originalArr = [MyObject(name: "a"), MyObject(name: "b")]
do {
let data = try JSONEncoder().encode(originalArr)
let copyArr = try JSONDecoder().decode([MyObject].self, from: data)
//modify
copyArr.forEach { obj in
obj.name = "\(obj.name) modified"
}
print(originalArr, copyArr) //-> [a, b] [a modified, b modified]
} catch {
fatalError(error.localizedDescription)
}
To simplify future cases we can create a protocol and conform to it, allowing interesting ways to use it.
protocol Copyable: Codable {
func copy() -> Self
}
extension Copyable {
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)
}
}
}
That's it. We can then conform our object to Copyable
class MyObject: Copyable, CustomStringConvertible {
var name: String
var description: String { name }
init(name: String) {
self.name = name
}
}
let a = MyObject(name: "A")
let b = a.copy()
b.name = "B"
print(a.name, b.name) //-> "A B"
We can quickly add support on Array
as well:
extension Array where Element: Copyable {
func copy() -> [Element] {
return self.map { $0.copy() }
}
}
let originalArr = [MyObject(name: "a"), MyObject(name: "b")]
let copyArr = originalArr.copy()
copyArr.forEach { (obj) in
obj.name = "\(obj.name) modified"
}
print(originalArr, copyArr) //-> [a, b] [a modified, b modified]
Well, there is a really easy solution for this and you do not have to create root class.
protocol Copyable {
init(instance: Self)
}
extension Copyable {
func copy() -> Self {
return Self.init(instance: self)
}
}
Now, if you want to make your custom class be able to copy, you have to conform it to Copyable
protocol and provide init(instance: Self)
implementation.
class A: Copyable {
var field = 0
init() {
}
required init(instance: A) {
self.field = instance.field
}
}
Finally, you can use func copy() -> Self
on any instance of A
class to create a copy of it.
let a = A()
a.field = 1
let b = a.copy()
Only if you are using ObjectMapper library : do like this
let groupOriginal = Group(name:"Abc",type:"Public")
let groupCopy = Mapper<Group>().mapAny(group.toJSON())! //where Group is Mapable