如何检查AVPlayer的keyPath观察者是否已添加并在存在时移除?
currentItem.loadedTimeRanges Great question! Unfortunately, Apple's KVO framework doesn't expose a direct API to check if an observer is already registered for a specific keyPath. But there are straightforward, reliable workarounds to handle this safely.
The Most Reliable Approach: Track Observer State Manually
The simplest and safest way is to maintain your own state flag to track whether the observer has been added. This avoids any guesswork and prevents duplicate additions/removals.
Step 1: Add a State Variable
Define a private boolean property in your class to track the observer's status:
private var isLoadedTimeRangesObserverActive = false
Step 2: Safely Add the Observer
When you want to add the observer, first check the state flag to avoid duplicates:
func setupAVPlayerObserver() { guard let player = audioPlayer, !isLoadedTimeRangesObserverActive else { return } player.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil) isLoadedTimeRangesObserverActive = true }
Step 3: Check and Remove the Observer
In any function where you need to remove the observer, check the flag first to ensure you only remove it if it's active:
func cleanupAVPlayerObserver() { guard let player = audioPlayer, isLoadedTimeRangesObserverActive else { return } player.removeObserver(self, forKeyPath: "currentItem.loadedTimeRanges") isLoadedTimeRangesObserverActive = false }
Bonus: Use a Custom Context for Safer KVO
To avoid conflicts with other KVO observers in your class (or from superclasses), it's best practice to use a custom context pointer instead of nil:
Define a Custom Context
private static let loadedTimeRangesContext = UnsafeMutableRawPointer(bitPattern: 0x1001)!
Update Add/Remove Calls
// Adding the observer player.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: Self.loadedTimeRangesContext) // Removing the observer player.removeObserver(self, forKeyPath: "currentItem.loadedTimeRanges", context: Self.loadedTimeRangesContext)
Update Your observeValue Method
Make sure to only handle the KVO update for your specific context:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { // Delegate to superclass if this isn't our context guard context == Self.loadedTimeRangesContext else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } // Handle the loadedTimeRanges update here if let timeRanges = change?[.newKey] as? [NSValue] { // Process the loaded time ranges } }
Critical Reminder: Clean Up in deinit
Always remember to remove the observer in your class's deinitializer to prevent memory leaks or crashes:
deinit { cleanupAVPlayerObserver() }
What About Just Calling removeObserver Directly?
On iOS 9 and later, calling removeObserver(_:forKeyPath:) for an observer that doesn't exist won't crash (unlike earlier iOS versions). However, relying on this is still risky—you might accidentally remove an observer you didn't mean to, or miss cleanup if your state isn't tracked. Manual state tracking is always the cleaner, more maintainable approach.
内容的提问来源于stack exchange,提问作者user8896788

