40

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 copyWithZone.

As an example, I have a basic class called Value which stores a NSDecimalNumber.

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

In Objective-C I, could easily just call copy to copy my object. In Swift, there seems to be no way to call copy. Do I really need to call copyWithZone even if no zone is needed? And which zone do I need to pass as a parameter?

Arya McCarthy
  • 8,554
  • 4
  • 34
  • 56
rusty1s
  • 419
  • 1
  • 4
  • 6
  • Does `Value` extend `NSObject`? – Sulthan Jun 16 '14 at 11:42
  • No. When I do so, I can call `copy()`. Thanks. But it is recommended to subclass all my custom classes from NSObject? – rusty1s Jun 16 '14 at 11:47
  • Extended my answer a bit. `Copy()` is something very connected with Obj-C. The best solution is to avoid it in pure swift. If you need it, subclassing `NSObject` is a good idea. – Sulthan Jun 16 '14 at 11:56
  • for `Array()` the function `unshare()` can be usable, but no such method is available in general in _Swift_. – holex Jun 16 '14 at 12:03
  • @rusty1s doubt you're still looking for an answer to this, but I posted a new answer for you that is probably a bit easier to understand – daredevil1234 Aug 04 '17 at 01:58

9 Answers9

34

The copy method is defined in NSObject. If your custom class does not inherit from NSObject, copy won't be available.

You can define copy for any object in the following way:

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

I guess that copy isn't really a pure Swift way of copying objects. In Swift it is probably a more common way to create a copy constructor (an initializer that takes an object of the same type).

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 8
    Thanks for the advice. I did what you suggested in the comments and made an initialiser function that takes an existing instance of the class and creates the new one from it. This seems like a more "Swift" way of operation instead of having to revert to sub-classing NSObject. – SarahR Oct 24 '14 at 04:47
  • Not a swift way of doing things at all. – Daniel K Feb 26 '23 at 03:51
22

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()
Misternewb
  • 1,066
  • 9
  • 12
  • 4
    With that solution you have a problem with subclassing, since the subclass has to implement required init with a parameter of the type of the super class. You would then need to downcast, which does not sound good. – Binarian May 23 '16 at 09:50
  • How to copy all properties of a class at once rather than doing the assignment one by one. Because you see i have a class with around 50 properties, and there is a high chance that i might miss assignment of some of them. Any way out ? – zulkarnain shah Jul 10 '17 at 12:12
  • @zulkarnainshah In this case I would suggest using structs, as this behavior is available for structs by default. – Misternewb Jul 10 '17 at 12:27
  • 1
    As @Binarian pointed out, this creates a mess of required inits. Beyond that, this isn't the way protocols are meant to be used. Protocols are meant to describe similar operations to similar objects, and copy is a widely different operation for widely different objects. – Daniel K Feb 26 '23 at 03:50
20

You can just write your own copy method

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

The benefit of this is when you are using subclasses around your project, you can call the 'copy' command and it will copy the subclass. If you just init a new one to copy, you will also have to rewrite that class for each object...

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"

I used this in particular when creating card games because regardless of what game you are playing, you will need to copy the "board" and the board is a subclass of a more general board.

Daniel K
  • 1,119
  • 10
  • 20
7

In my case the object chain was large and nested so was looking for simpler solutions.

The core concept being simple enough... duplicate the data by new initialization, I used Encode and Decode to deep-copy the entire object since my objects were already conforming to Codable,

Simple Example:

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

Refactor (Generic Solution):

To simplify future cases we can create a protocol that will provide a copy function.
For Non-Codable objects, you will have to implement your own copy function.
For Codable objects, we can provide a default implementation so it's ready-to-use. Like so:

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

We can now conform a Codable object to our Copyable protocol and start using it immediately.

extension MyCodableObject: Copyable {}

Example:

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

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

We can also conform an Array of Codable objects to Copyable and access the copy function instantly:

extension Array: Copyable where Element: Codable {}

Example:

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]
staticVoidMan
  • 19,275
  • 6
  • 69
  • 98
5

IMO, the simplest way to achieve this is :

protocol Copyable
{
  init(other: Self)
}

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

Implemented in a struct as :

struct Struct : Copyable
{
  var value: String

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

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

And, in a class, as :

class Shape : Copyable
{
  var color: NSColor

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

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

And in subclasses of such a base class as :

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
    }
  }
}

If you want to be able to copy an array of Copyable types :

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

Which then allows you to do simple code like :

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

  let copies = shapes.copy()
}
Joanna Carter
  • 179
  • 2
  • 7
4

In my opinion, more Swifty way is to use associated type in Copyable protocol which allows define return type for method copy. Other ways don't allow to copy an object tree like this:

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)")
dydus0x14
  • 111
  • 4
3

Copyable instances in swift

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:

StyleKit Graphic Framework example

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

Sentry.co
  • 5,355
  • 43
  • 38
1

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
goto
  • 7,908
  • 10
  • 48
  • 58
SPatel
  • 4,768
  • 4
  • 32
  • 51
0

Swift making copies of passed class instances

If you use the code in the accepted answer(the OP answered their own question) here, so long as your class is a subclass of NSObject and uses the Copying protocol in that post it will work as expected by calling the copyOfValues() function.

With this, no tedious setup or copy functions where you need to assign all the instance variables to the new instance.

I should know, I wrote that code and just tested it XD

daredevil1234
  • 1,303
  • 1
  • 10
  • 34