skip to Main Content

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?

2

Answers


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

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search