Avplayer Removing Observer Crash in Swift 2.2

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



Leave a reply



Submit