Skip to content

Instantly share code, notes, and snippets.

@ahmedk92
Created June 22, 2020 16:29
Show Gist options
  • Save ahmedk92/5fab0e77e322b8e11d121d433828a833 to your computer and use it in GitHub Desktop.
Save ahmedk92/5fab0e77e322b8e11d121d433828a833 to your computer and use it in GitHub Desktop.
Simultaneous accesses to X, but modification requires exclusive access.
class ProgressManager {
init(progressStore: ProgressStoring) {
self.progressStore = progressStore
self.progressStore.didReceiveNewProgress = { [weak self] progress in
guard let self = self else { return }
self.progress = progress
}
}
private var progress: Float = 0 {
didSet {
guard progress != oldValue else { return }
progressStore.store(progress: progress)
}
}
private var progressStore: ProgressStoring
}
// Making this protocol conform to AnyObject solves the problem.
protocol ProgressStoring/*: AnyObject*/ {
func store(progress: Float)
var didReceiveNewProgress: ((Float) -> Void)? { get set }
}
class ProgressStore: ProgressStoring {
func store(progress: Float) {
guard progress != self.progress else { return }
self.progress = progress
}
var didReceiveNewProgress: ((Float) -> Void)? {
didSet {
didReceiveNewProgress?(progress)
}
}
private var progress: Float = 1
}
let progressStore = ProgressStore()
let progressManager = ProgressManager(progressStore: progressStore)
@ahmedk92
Copy link
Author

@ahmedk92
Copy link
Author

ahmedk92 commented Jun 22, 2020

In a nutshell: without explicitly stating ProgressStoring is a reference type, Swift will just assume the progressStore property in ProgressManager to be a value type, even if the implementer is a class. So, it will enforce exclusive access protection for it. The protection if cannot be done in compile-time, Swift guarantees it will be done in run-time, which happens in our case. Exclusive access violation happens when a write access is granted (i.e. mutation begins), and in meanwhile a read access is requested. In detail:

The write access is granted as just when we assign didReceiveNewProgress:

self.progressStore.didReceiveNewProgress = { [weak self] progress in
            guard let self = self else { return }
            self.progress = progress
}

The conflicting read access is requested deep a little in the call stack when we assign the received progress value self.progress = progress. This triggers the didSet of the progress property. If we inspect the didSet implementation:

private var progress: Float = 0 {
        didSet {
            guard progress != oldValue else { return }
            progressStore.store(progress: progress)
        }
}

Here we call progressStore.store(progress: progress) which is a read access to progressStore. But we're already in the call stack of the closure passed to the mutating assignment of didReceiveNewProgress on progressStore which means we're already granted a write access and the writing is not finished. This means a violation = crash.

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