AVPlayer removing observer crash in Swift 2.2
In Swift 2, the libs got annoyingly strict about errors that are truly unexpected (which throw) versus errors that the programmer could have prevented (which do not throw, but just crash your app).
(I’m not a fan of this distinction, or at least not of all the specific decisions Apple made about which errors fall in which category. The JSON API verges on the nonsensical in this department. But…we work with the API we’ve got.)
The NSKeyValueObserving
docs say:
It is an error to call
removeObserver:forKeyPath:
if the object has not been registered as an observer.
“It is an error” is Apple code for “you are responsible for never doing this, and if you do, your app will crash in an uncatchable way.”
In these situations, there is usually an API call you can make to check the validity of the thing you’re about to do. However, AFAIK, there’s no KVO API call you can make to ask, “Is X observing key path Y of object Z?” That means you have three options:
- Figure out why you’re trying to remove an observer from something you’re not observing, and prevent that using your program’s own internal logic.
- Keep a weak instance var for “player I’m observing,” and check that for a match before attempting to remove the observer.
- Add self as an observer before removing it. (I’m pretty sure that a redundant add is OK.)
App crashes sometimes when removing observer from AVPlayer
You can deal with: NSKeyValueObservation
var observer: NSKeyValueObservation?
self.observer = myPlayer.observe(\.rate, options: [.new, .old], changeHandler: { (player, change) in
if player.rate == 1 {
print("Playing")
}else{
print("Stop")
}
})
// Later You Can Remove Observer
self.observer?.invalidate()
self.observer = nil
Swift -AVPlayer' KVO in cell's parent vc causing Xcode to freeze
The problem is that, in your property observer, you are making a change in the property that you are observing. That is a vicious circle, an infinite recursion; Xcode displays this by freezing your app until ultimately you crash with (oh, the irony) a stack overflow.
Let's take a simpler, self-contained example. We have a UISwitch in the interface. It's On. If the user switches it Off, we want to detect that and switch it back to On. The example is a silly way to do this, but it perfectly illustrates the issue you are facing:
class ViewController: UIViewController {
@IBOutlet weak var theSwitch: UISwitch!
class SwitchHelper: NSObject {
@objc dynamic var switchState : Bool = true
}
let switchHelper = SwitchHelper()
var observer: NSKeyValueObservation!
override func viewDidLoad() {
super.viewDidLoad()
self.observer = self.switchHelper.observe(\.switchState, options: .new) {
helper, change in
self.theSwitch.isOn.toggle()
self.theSwitch.sendActions(for: .valueChanged)
}
}
@IBAction func doSwitch(_ sender: Any) {
self.switchHelper.switchState = (sender as! UISwitch).isOn
}
}
What's going to happen? The user turns the switch Off. We are observing that, as switchState
; in response, we switch the switch back to On and call sendActions
. And sendActions
changes switchState
. But we are still in the middle of the code that observes switchState
! So we do it again and it happens again. And again and it happens again. Infinite loop...
How would you get out of this? You need to break the recursion somehow. I can think of two obvious ways. One is to think to yourself, "Well, I only care about a switch from On to Off. I don't care about the other way." Assuming that's true, you can solve the problem with a simple if
, rather like the solution you elected to use:
self.observer = self.switchHelper.observe(\.switchState, options: .new) {
helper, change in
if let val = change.newValue, !val {
self.theSwitch.isOn.toggle()
self.theSwitch.sendActions(for: .valueChanged)
}
}
A more elaborate solution, which I like to use sometimes, is to stop observing when the observer is triggered, make whatever the change is, and then start observing again. You have to plan ahead a little to implement that, but it's sometimes worth it:
var observer: NSKeyValueObservation!
func startObserving() {
self.observer = self.switchHelper.observe(\.switchState, options: .new) {
helper, change in
self.observer?.invalidate()
self.observer = nil
self.theSwitch.isOn.toggle()
self.theSwitch.sendActions(for: .valueChanged)
self.startObserving()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.startObserving()
}
That looks recursive, because startObserving
calls itself, but it isn't really, because what it does when it is called is to configure the observation; the code in the inner curly braces doesn't run until we get an observed change.
(In real life, I would probably make the NSKeyValueObservation a local variable in that configuration. But that's just an additional bit of elegance, not essential to the point of the example.)
Related Topics
Detecting Double and Single Tap on Uitableviewcell to Perform Two Action
Case Insensitive Matching Search in String Array Swift 3
Ad-Hoc Distributed Application Failed to Launch in Time
iPhone /iOS: Presenting HTML 5 Keyboard for Postal Codes
How to Detect If The HTML5 Autoplay Attribute Is Supported
Terminating App Due to Uncaught Exception 'Nsinvalidargumentexception' - iOS Google Sign In
Left-To-Right Mark Not Working in Swift
Problems with Layout of Some Rows in Swiftui List
Wkwebview Allowslinkpreview to False Breaks Text Selection
How to Get a Low Res Image, or Thumbnail from the Alassetrepresentation in Swift
Adding an Skscene to a Uiviewcontroller
App Could Not Authenticate with Facebook and Firebase After Conversion to Swift 3 Syntax
iOS - Combine/Concatenate Multiple Audio Files
Fixed Background for Part of a Site (Div) in iOS
How to Use Pod in Notification Service Extension
Dynamic Height for a Uitableview Based on a Dynamic Collection View