-
-
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