0

I have the following structure, which I want to make a deep copy of, so I can treat those differently.

class NearbyStopsViewModel {
    var stopViewModels: [StopViewModel]
}

class StopViewModel {
    var stop: Stop
    var name: String
    var departures: [DepartureViewModel]?
}

class DepartureViewModel: NSObject {
    var departure: Departure
    var name: String
}

I having a hard time wrapping my head around making a deep copy of this structure, any ideas?

Recusiwe
  • 1,594
  • 4
  • 31
  • 54

2 Answers2

0

To create a 'deep' copy of a class instance you need to conform to NSCopying for those relevant classes, Structs are passed by value. I added my own implementation for the missing structures.

import Foundation

struct Departure {
    var to: Stop
    var from: Stop
    
    init(from: Stop, to: Stop) {
        self.from = from
        self.to = to
    }
}

struct Stop {
    var name: String = ""
    
    init(named: String) {
        self.name = named
    }
}

class NearbyStopsViewModel: NSCopying {
    var stopViewModels: [StopViewModel]
    
    init(stops: [StopViewModel] = []) {
        self.stopViewModels = stops
    }
    
    func copy(with zone: NSZone? = nil) -> Any {
        
        var stops: [StopViewModel] = []
        self.stopViewModels.forEach { (stop) in
            stops.append(stop.copy() as! StopViewModel)
        }
        
        let nearbysvm = NearbyStopsViewModel.init(stops: stops)
        return nearbysvm
        
    }
}

class StopViewModel: NSCopying {
    
    var stop: Stop
    var name: String = ""
    var departures: [DepartureViewModel]
    
    init(stop: Stop, named: String, with departures: [DepartureViewModel] = [] ) {
        self.stop = stop
        self.name = named
        self.departures = departures
    }
    
    func copy(with zone: NSZone? = nil) -> Any {
        
        var departuresCopy: [DepartureViewModel] = []
        self.departures.forEach { (departure) in
            departuresCopy.append(departure.copy() as! DepartureViewModel)
        }
        
        let stopvm = StopViewModel.init(stop: self.stop, named: self.name, with: departuresCopy)
        return stopvm
    }
}


class DepartureViewModel: NSObject, NSCopying {
    
    var departure: Departure
    var name: String = ""
    
    init(name: String, departure: Departure) {
        self.name = name
        self.departure = departure
    }
    
    func copy(with zone: NSZone? = nil) -> Any {
        let departure = DepartureViewModel.init(name: self.name, departure: self.departure)
        return departure
    }
}


// Structs are passed by Value, making a 'minimal' copy of themselves as they move.
var pennStation = Stop(named: "Pennsylvania Station")
print(pennStation)
let copyByValue = pennStation
print(copyByValue) // "Pennsylvania Station"
pennStation.name = "Penn. Station"
print(copyByValue) // "Pennsylvania Station"

// Classes are passed by Reference
let clarkLake = Stop(named: "Clark and Lake")
let stateLake = Stop(named: "State and Lake")

let clarkToState = Departure(from: clarkLake, to: stateLake)

// DepartureViewModel is your lowest level class that conforms to NSCopying
let departure = DepartureViewModel(name: "clark to state", departure: clarkToState)
print(departure) // This Memory Address should never change.
let referenceDeparture = departure
departure.name = "Unexpected delay"
print(referenceDeparture.name)
print(referenceDeparture) // Same Address as departure.

let deepCopyOfDeparture = departure.copy() as! DepartureViewModel // Copy() and mutableCopy() will return a passed-by-value copy.
print(deepCopyOfDeparture) // Different Memory Address as departure


let stopvm = StopViewModel(stop: pennStation, named: "Penn. Station", with: [departure])
print("Stop View Model's Child Departure Instance(s): \(stopvm.departures)")
let copyOfSVM = stopvm.copy() as! StopViewModel
print("Copy of SVM Child Departure Instance(s): \(copyOfSVM.departures)")

Output:

Stop(name: "Pennsylvania Station")

Stop(name: "Pennsylvania Station")

Stop(name: "Pennsylvania Station")

<StackOverflow.DepartureViewModel: 0x149dc4de0>

Unexpected delay

<StackOverflow.DepartureViewModel: 0x149dc4de0>

<StackOverflow.DepartureViewModel: 0x149da89a0>

<Stop View Model's Child Departure Instance(s): <StackOverflow.DepartureViewModel: 0x149dc4de0>

<Copy of SVM Child Departure Instance(s): <StackOverflow.DepartureViewModel: 0x149dc7630>

Community
  • 1
  • 1
RLoniello
  • 2,309
  • 2
  • 19
  • 26
  • Would you advice me to use structs over classes in the case? – Recusiwe Aug 26 '18 at 20:12
  • Personally, I rarely use `structs` unless it is for a small object(s) that needs to be [passed by value](https://stackoverflow.com/a/27366050/1361672) or contain a mutating property or function. Classes are typically easier to work with and extend if needed. Ultimately this decision is up to you. You should review your data management and see how data flows through your app. if you are creating multiple different instances and using them in multiple places go with a `class`. if you are creating objects in-scope and then releasing them then go with a `struct`. – RLoniello Aug 26 '18 at 20:52
  • WIth these objects I only fetch them from the API and show them, I don't change anything in them. – Recusiwe Aug 26 '18 at 22:43
  • I'm interessted in a function which makes a total copy of the above class hierachy, any ideas? I need it for updating my datasource for a tableview, so firstly it returns the latest 5 departure pr. stop, and when a function is called this results in return the 5 plus 5 more. – Recusiwe Aug 26 '18 at 23:31
0

You can take advantage of value types, which are deeply copied by default, the caveat here is that all members are also value types (structs, enums, tuples), and those members contain only value types, and so on:

struct NearbyStopsViewModel {
    var stopViewModels: [StopViewModel]
}

struct StopViewModel {
    var stop: Stop
    var name: String
    var departures: [DepartureViewModel]?
}

struct DepartureViewModel: NSObject {
    var departure: Departure
    var name: String
}

struct Departure {
    // fields needed for the struct
}

With a hierarchy like this, every assign to any of the above types will result in a deep copy of all underlying members. You let the compiler do all the work for you.

Beware, though, there might be some performance issues if you work with a large amount of these values (for a fair enough amount the performance thing is unnoticeable).

Cristik
  • 30,989
  • 25
  • 91
  • 127