Skip to content

Instantly share code, notes, and snippets.

@drewmccormack
Created February 2, 2019 10:25
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save drewmccormack/b1c4487935cf3c3e0a5feaf488a95ebd to your computer and use it in GitHub Desktop.
Save drewmccormack/b1c4487935cf3c3e0a5feaf488a95ebd to your computer and use it in GitHub Desktop.
Demonstrates how a Swift value constant can mutate when using Copy-on-Write (CoW) and multi-threading.
import Foundation
struct NuclearPowerStationOperator {
class Storage {
var turnOffCores: Bool = false
func copy() -> Storage {
let new = Storage()
new.turnOffCores = turnOffCores
return new
}
}
private var storage: Storage = Storage()
var turnOffCores: Bool {
get {
return storage.turnOffCores
}
set {
if isKnownUniquelyReferenced(&storage) {
Thread.sleep(forTimeInterval: 1.0) // Sleep to simulate race condition
storage.turnOffCores = newValue
} else {
storage = storage.copy()
storage.turnOffCores = newValue
}
}
}
var description: String {
return "\(turnOffCores ? "We are in danger" : "We are safe")"
}
}
// Create a mutable value
var crazyOperator = NuclearPowerStationOperator()
DispatchQueue.global(qos: .background).async {
Thread.sleep(forTimeInterval: 0.5) // Sleep a little to give main thread time to start setting property
let saneOperator = crazyOperator // Create a constant copy of the operator from main thread
print(saneOperator.description) // Print our intial property value
Thread.sleep(forTimeInterval: 2.0) // Simulate race by waiting for setter on main thread to finish
print(saneOperator.description) // Test property (it will be different)
}
// Update the value. Note that the setter simulates a race condition by being very slow
crazyOperator.turnOffCores = true
@ogres
Copy link

ogres commented Feb 4, 2019

You have to add crazyOperator to a capture list of the closure to get the behaviour you wanted. Without it, closure will have a reference to your variable, it will not copy it on creation.

Change

DispatchQueue.global(qos: .background).async {

into

DispatchQueue.global(qos: .background).async { [crazyOperator] in

To see the difference between a closure with and without a capture list, have a look at the following code:

import Foundation

var int: Int? = 100
var array: [Int]? = [200]

DispatchQueue.global(qos: .background).async {
    let copy_int = int
    let copy_array = array
    print("Without capture list int: \(String(describing: copy_int)), array: \(String(describing: copy_array))")
}

DispatchQueue.global(qos: .background).async { [int, array] in
    let copy_int = int
    let copy_array = array
    print("With capture list int: \(String(describing: copy_int)), array: \(String(describing: copy_array))")
}

int = nil
array = nil
Thread.sleep(forTimeInterval: 1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment