iosdictionaryreferenceswift5deep-copy

Is it absolutely the case that Swift >will not< deep copy a large array, when one "guard let" the array?


typealias Stuff = [[String]] // typically zillions of items
var stuff: [String: Stuff] = [:] // tens of these

When I deep copy† a Stuff,

spare: Stuff = original{
    let line = $0.map{ $0 }
    return line
}

it takes 1/2 sec on a new phone, so of course to avoid.

Now say I am working with a Stuff, like this calculations(on: "Cats"), strictly reading from stuff["Cats"]

///Strictly reading from Stuff
func calculations(on: String) {
  guard stuff[on] != nil else { return }
  .. = stuff[on]!.blah.blah
  .. = stuff[on]!.blah.blah
  ..
  .. dozens and dozens of reads from stuff[on]!
}

Of course it is much tidier if you

///Strictly reading from Stuff
func calculations(on: String) {
  guard let p = stuff[on] else { return }
  .. = p.blah.blah
  .. = p.blah.blah
  ..
  .. dozens and dozens of reads from p
}

If I do that,

As a related question, I believe, but I'm not sure,

that this would be how you check if indeed p is not a deep copy of stuff["Cats"] ->

withUnsafePointer(to: &p[0][0]) {
    print("p is at \($0)")
}
withUnsafePointer(to: &stuff["Cats"][0][0]) {
    print("whereas stuff["Cats"][0][0] is at \($0)")
}

Looks right?


As an aside, I really don't know if that is the best way to deep copy a 2D string array in Swift.


Solution

  • Yes it is guaranteed that no copies of the array storage are made. Array has copy-on-write behaviour:

    Arrays, like all variable-size collections in the standard library, use copy-on-write optimization. Multiple copies of an array share the same storage until you modify one of the copies.

    [...]

    This means that if an array is sharing storage with other copies, the first mutating operation on that array incurs the cost of copying the array. An array that is the sole owner of its storage can perform mutating operations in place.

    Though let p = stuff[on] copies the value of stuff[on] to p, the value of the Array struct is small - only 8 bytes on my machine (see MemoryLayout<[[String]]>.size).

    The 8-byte struct value includes a pointer to some storage location for the array, and p and stuff[on] would be pointing to the same storage location, as the documentation says. From the standard library source, this would be an instance of the class __ContiguousArrayStorageBase.

    The array storage will only be copied if you modify stuff[on] by e.g. adding another [String] to it. p and stuff[on] would now point to different storage locations. But even then, the inner arrays will still share storage, since you didn't modify the inner arrays.

    Related: How are updates to var arrays handled in Swift memory (confused about CoW)


    As for comparing the pointers, your code is comparing the locations of the elements in the inner array. As I said above, the outer array's storage could be copied even when the inner arrays are not, and vice versa, so you might want to compare &stuff["Cats"][0] and &p["Cats"][0] too.