-
-
Save ahmedk92/5fab0e77e322b8e11d121d433828a833 to your computer and use it in GitHub Desktop.
| 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) |
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.
More on this here. https://docs.swift.org/swift-book/LanguageGuide/MemorySafety.html