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]
// 0x6000038a0200
var filteredArray = array.filter { $0 > 3 }
// 0x6000015b3380

Casting to a new type makes a new array

var array = [1, 2, 3, 4, 5]
// 0x6000034393d0
var 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]
// 0x600001127860
var array2 = Array(arrayLiteral: array)
// 0x600003c3f6b0

An array supports copy-on-write

var array = [1, 2, 3, 4, 5]
// 0x600002e30160
var array2 = array
// 0x600002e30160
array2.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

Resources

--

--

--

Software Developer

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

How to Code on LeetCode from the Terminal

Basic and (mostly) Free Resources to Begin Your Web Development Journey

What is HTML and its basic HTML tags?

Use MassTransit + RabbitMQ with .Net Core 3.1

Newly-Introduced JUnit 5 Annotations and Classes. Tagged tests

<<“EXPLORING TIME”>>

Learning Python: Inheritance and Polymorphism

Ruby Return Values

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Maxim Krylov

Maxim Krylov

Software Developer

More from Medium

swift 001

Cross-Platform encrypt decrypt (iOS, Android, Web)

Candy Crush Game on iOS