Hayk Ghazaryan
Hayk Ghazaryan

Reputation: 7

Deep copy vs Shallow copy on iOS

What is the difference between deep copy and shallow copy on iOS world?? Please answer to this question very detailized!!

e.g a piece of code

class A {
var name: String

init(name: String) {
    self.name = name
    }
    
}
var a1 = A(name:"Yura")

var a2 = a1 // It is a shallow copy, isnt it?)
 

If we use .copy() method, we will receive deep copy, won't we?

But conceptually which main differences do they have? And isn't there possability to implement deep and shallow copies in other ways?

Upvotes: -1

Views: 1619

Answers (2)

valeCocoa
valeCocoa

Reputation: 344

The way your question is formulated with the accompanying code is not really about copying but rather about reference vs value semantics.

A shallow or a deep copy is related to types adopting reference semantics (i.e. classes).

In Swift there is the NSCopying protocol that is usually adopted and conformed to in order to define how a class type shall perform a copy.

For example this class always performs a deep copy of its properties:

final class Bar: NSCopying {
   private(set) var value: Int
   
   func increment() {
       value += 1
   }
   init(value: Int = 0) { self.value = value }
   
   func copy(with zone: NSZone? = nil) -> Any { 
      Self(value: self.value)
   }

}

Bar always performs a deep copy: it returns a new instance initialised with the same value of the original instance property. Such property is of type Int, which adopts value semantics, hence:

let original = Bar(value: 0)
let clone = original.copy() as! Bar

if original !== clone { print("Different instances") } else { print("Same instance") }
// prints: "Different instances"

clone.increment()
print(original.value)
// prints: 0
print(clone.value)
// prints: 1

What happens if we create another class which has one of its properties of type Bar? How do we define the way such property is gonna be copied? Here comes into play shallow vs deep copy strategy:

final class Foo: NSCopying {
    let name: String
    
    let bar: Bar
    
    init(name: String, initialValue: Int) {
        self.name = name
        self.bar = Bar(value: initialValue)
    }
    
    private init(name: String, bar: Bar) {
         self.name = name
         // we just assign the bar reference here…
         self.bar = bar
    }
    
    func copy(with zone: NSZone?) -> Any {
        // …we won't get a deep copy but a shallow copy instead!!!
        Self(name: self.name, bar: self.bar)
    }

}

In this case we performed a shallow copy of the property bar. That's cause in the copy method we create a new instance of Foo, but we initialise it with the same reference to the Bar instance stored at bar property:

let original = Foo(name: "George", initalValue: 0)
let clone = original.copy() as! Foo

if original !== clone { print("Different instances") } else { print("Same instance") }
// prints: "Different instances"

if original.bar !== clone.bar { print("Different bar instances") } else { print("Shared bar instance") }
// prints: "Shared bar instance"

original.bar.increment()
print(original.bar.value)
// prints: 1
print(clone.bar.value)
// prints: 1 

As you may see here the clone instance of type Foo got the value of its bar property also mutated as side effect of mutating the original Foo instance.

To avoid this behaviour we could have leveraged on Bar NSCopying implementation so to obtain a copy of its bar property:

final class Deep: NSCopying {
    let name: String
     
    let bar: Bar
     
    init(name: String, initialValue: Int) {
        self.name = name
        self.bar = Bar(value: initialValue) 
    }
    
    private init(name: String, bar: Bar) {
         self.name = name
         // We are assigning a copy of the bar parameter…
         self.bar = bar.copy() as! Bar
    }
    
    func copy(with zone: NSZone?) -> Any {
        // …Therefore we really perform a deep copy! 
        Self(name: self.name, bar: self.bar)
    }

}

Now if we were to mutate a copy, we wouldn't get the side effect of also mutating the original and vice-versa:

let original = Deep(name: "George", initialValue: 0)
let clone = original.copy() as! Deep

clone.bar.increment()
print(original.bar.value)
// prints: 0
print(clone.bar.value)
// prints: 1

Here if Bar was immutable, it didn't matter if we made a shallow copy of a property of this type, because there wouldn't be side effects: indeed it would have been better for the memory footprint of our application. On the other hand, since Bar was implemented as a mutable type, then we had to take into account possible side effects of its mutability in another reference having an internal property of this type.

Upvotes: 0

maddy
maddy

Reputation: 4111

Deep copy: we make deep copies, source (personObj) and destination(personObjAnother) objects have their own copies. Changes made to the newly copied objects does not impact source object.

//A person structure with variables personName and personAge

struct Person{
var personName : String?
var personAge : Int?
}
//Lets use the Person struct
//lets create person object
var personObj = Person()

//lets set person properties
personObj.personName = "Alok"
personObj.personAge = 18

//lets create a another person object and copy personObj to personObjAnother object.
var personObjAnother = personObj

//lets set personObjAnother properties
personObjAnother.personName = "Naitvik"
personObjAnother.personAge = 3

//lets print personObj
print(personObj.personName!) //prints "Alok"
print(personObj.personAge!) //prints 18

//lets print personObjAnother
print(personObjAnother.personName!) //prints "Naitvik"
print(personObjAnother.personAge!) //prints 3

Example of Shallow Copy: When we make shallow copies, source (personObj) and destination(personObjAnother) objects have shared copies. Changes made to the newly copied objects does also impact source object.

class PersonC{
var personName : String?
var personAge : Int?
}
//lets create person object
let personObj = PersonC()

//lets set person properties
personObj.personName = "Alok"
personObj.personAge = 18

//lets create a another person object and assign personObj.
let personObjAnother = personObj

//lets set personObjAnother properties
personObjAnother.personName = "Naitvik"
personObjAnother.personAge = 3

//lets print personObj
print(personObj.personName!) //prints "Naitvik"
print(personObj.personAge!) //prints 3

//lets print personObjAnother
print(personObjAnother.personName!) //prints "Naitvik"
print(personObjAnother.personAge!) //prints 3

Creating Deep Copies Of Reference Types:

//A personD class with variables personName and personAge

//We have to confirm NSCopying protocol and implement func copy(with zone: NSZone? = nil) -> Any

class PersonD : NSCopying{
var personName : String?
var personAge : Int?

func copy(with zone: NSZone? = nil) -> Any {
let copy = PersonD()
copy.personName = self.personName
copy.personAge = self.personAge
return copy
}
}

//lets create person object let personObj = PersonD()

//lets set person properties
personObj.personName = "Alok"
personObj.personAge = 18

/*lets create a another person object and assign personObj.
we will use copy method now to perform deep copy.
we have to make sure PersonD confirms to NSCopying protocol
and implements func copy(with zone: NSZone? = nil) -> Any*/
let personObjAnother = personObj.copy() as! PersonD

//lets set personObjAnother properties
personObjAnother.personName = "Naitvik"
personObjAnother.personAge = 3

//lets print personObj
print(personObj.personName!) //prints "Alok"
print(personObj.personAge!)  //prints 18

//lets print personObjAnother
print(personObjAnother.personName!) //prints "Naitvik"
print(personObjAnother.personAge!)  //prints 3

Here is the reference from my own website.

Upvotes: 1

Related Questions