Swift中KVO代码出现运行时并发访问问题求解决方案
Hey there, let's break down why you're hitting this runtime error and how to fix it.
First, that error message points to a data race: some code is modifying a value while your KVO callback tries to read it at the exact same time. Looking at your code line:
if (context == &KVOContext && keyPath == contentOffsetKeyPath && object as? UIScrollView == scrollView) { //... }
The most likely culprit is contentOffsetKeyPath — if it's declared as a mutable var instead of an immutable let, other threads could be changing it while your callback compares it to the incoming keyPath. Swift's strict memory safety checks catch this to prevent corrupted state.
Here are three solid solutions to fix this:
1. Make contentOffsetKeyPath a constant
The simplest fix is to turn your key path into an immutable constant. If you had this before:
private var contentOffsetKeyPath = "contentOffset"
Change it to:
private let contentOffsetKeyPath = "contentOffset"
Since constants can't be modified after initialization, there's no way for a data race to occur when comparing it in your KVO callback.
2. Add a lock around access to mutable key paths
If you absolutely need contentOffsetKeyPath to be mutable (e.g., dynamically switching observed keys), protect access to it with an exclusive lock.
First, define a lock alongside your key path:
private let keyPathLock = NSLock() private var contentOffsetKeyPath: String = "contentOffset"
Then, wrap the key path comparison in the lock within your KVO callback:
// Safely check the key path with lock protection keyPathLock.lock() let matchesKeyPath = keyPath == contentOffsetKeyPath keyPathLock.unlock() if context == &KVOContext && matchesKeyPath && object as? UIScrollView == scrollView { // Your existing logic here }
Important: Wrap any code that modifies contentOffsetKeyPath with the same lock too — otherwise the race will still happen.
3. Switch to Swift's type-safe KeyPath (and closure-based KVO)
For a modern, safer approach, ditch string key paths entirely and use Swift's strong-typed KeyPath with the closure-based KVO API. This eliminates manual key path/context checks and avoids the race issue entirely.
First, define your key path as a constant KeyPath:
private let contentOffsetKeyPath = \UIScrollView.contentOffset
Then, register your observer using the closure-based method instead of the old-style callback:
// Register observer with closure-based KVO scrollView.observe(contentOffsetKeyPath, options: [.new], context: &KVOContext) { [weak self] scrollView, change in // Your logic goes directly here — no need to check keyPath or context // The closure only triggers for this specific key path on this scroll view guard let self = self else { return } // Do your work with scrollView.contentOffset }
This approach is cleaner, safer, and automatically avoids data races because the key path is a constant, and you don't have to do manual comparisons in a shared callback.
内容的提问来源于stack exchange,提问作者S.Shakir

