Swift. Copy-On-Write
Here is a quick introduction, along with the full articles list
… is all about improving the performance of value types
Copying on write is only about structures that are stored on the heap. It makes real copies of instances only when that copies are being changed
String and all collection types — Array, Dictionary, and Set support this technique (they’re implicitly stored on the heap). But, copy-on-write doesn’t work for all structures by default. When simply copying a structure, it is copied even if its copies are not supposed to be changed. Though the compiler is free to optimize any structure access and effectively give you copy-on-write semantics, this is not guaranteed
Assignment, initialization, and arguments passing create a new copy of value type data or one more reference to reference type data
Array
Array filtering makes a new array
var array = [1, 2, 3, 4, 5]
// 0x6000038a0200var filteredArray = array.filter { $0 > 3 }
// 0x6000015b3380
Casting to a new type makes a new array
var array = [1, 2, 3, 4, 5]
// 0x6000034393d0var array2 = array as [Any]
// 0x600002234ce0
Creating a new array from a given one makes one more new array
var array = [1, 2, 3, 4, 5]
// 0x600001127860var array2 = Array(arrayLiteral: array)
// 0x600003c3f6b0
An array supports copy-on-write
var array = [1, 2, 3, 4, 5]
// 0x600002e30160var array2 = array
// 0x600002e30160array2.append(6)
// 0x600002636320
ArraySlice
ArraySlice supports copy-on-write
final class Element {
let id: Int
init(id: Int) {
self.id = id
}
deinit {
print("deinit of '\(id)' is called")
}
}var array: Array<Element>? = [Element(id: 1), Element(id: 2)]
// array is [Element(1), Element(2)]
var slice: ArraySlice<Element>? = array!.dropFirst()
// slice is [Element(2)]slice![slice!.endIndex - 1] = Element(id: 3)
// slice is [Element(3)]
// array is [Element(1), Element(2)]array = nil
// deinit of '1' is called
// deinit of '2' is called
var array2: Array<Element>? = [Element(id: 10), Element(id: 20)]
// array2 is [Element(10), Element(20)]
var slice2: ArraySlice<Element>? = array2!.dropFirst()
// slice2 is [Element(20)]slice2!.append(Element(id: 30))
// slice2 is [Element(20), Element(30)]
// array2 is [Element(10), Element(20)]array2 = nil
// deinit of '10' is called
var array3: Array<Element>? = [Element(id: 100), Element(id: 200)]
// array3 is [Element(100), Element(200)]var slice3: ArraySlice<Element>? = array3!.dropFirst()
// slice3 is [Element(200)]array3![array3!.count - 1] = Element(id: 300)
// array3 is [Element(100), Element(300)]
// slice3 is [Element(200)]array3 = nil
// deinit of '300' is called
var array4: Array<Element>? = [Element(id: 1000), Element(id: 2000)]
// array4 is [Element(1000), Element(2000)]var slice4: ArraySlice<Element>? = array4!.dropFirst()
// slice4 is [Element(2000)]array4!.append(Element(id: 3000))
// array4 is [Element(1000), Element(2000), Element(3000)]
// slice4 is [Element(2000)]array4 = nil
// deinit of '3000' is called
When a slice is changed, it loses a strong link to the original array’s entire storage. But if the original array is changed, its slices still have a strong link to the old version of the array
Set
A set supports copy-on-write
var set: Set = [1, 2]
// set is [1, 2]var set2 = set
// set2 is [1, 2]set2.insert(3)
// set2 is [1, 2, 3], set is [1, 2]
Dictionary
A dictionary supports copy-on-write
var dictionary = [1: "One", 2: "Two", 3: "Three"]
var dictionary2 = dictionary
dictionary2[1] = "1"// dictionary = [3: "Three", 1: "One", 2: "Two"]
// dictionary2 = [3" "Three", 1: "1", 2: "Two"]
Manual Copy-On-Write
struct User {
var id: Int
var age: Int
}final class ReferenceTo<T> {
var value: T init(value: T) {
self.value = value
}
}struct Box<T> {
private var reference: ReferenceTo<T>
init(value: T) {
reference = ReferenceTo(value: value)
} var value: T {
get { return reference.value }
set {
// returns true if there is only one strong reference
if isKnownUniquelyReferenced(&reference) {
reference.value = newValue
}
reference = ReferenceTo(value: newValue)
}
}
}var boxedUser = Box<User>(value: User(id: 1, age: 18))
// boxedUser2.value refers to boxedUser.value
var boxedUser2 = boxedUser
boxedUser2.value.id = 2 // copying on write
// boxedUser2.value.id is 2, boxedUser.value.id is 1
isKnownUniquelyReferenced
The function considers only strong references:
- true == strong
- false == strong, strong
- false == weak, unowned
- true == strong, weak, unowned