0

Is there a good way to use key-value-observation and blocks together? I have a function that takes a completion block, and I want this completion block to run when the observed status changes into AVPlayerItemStatusReadyToPlay. Can I pass the block using the context of the observer somehow, or would this break the fundamentals of KVO programming?

- (void)setVideoWithURL:(NSURL *)url completed:(PlayerCompletedWithFinishedBlock)completedBlock {
    ...
    AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:NULL];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([change isEqual: @"AVPlayerItemStatusReadyToPlay"]) {
        // Is there a way to run the completion block from here?
    }
}
Daniel Larsson
  • 6,278
  • 5
  • 44
  • 82

2 Answers2

1

Just store the block in a copy property and call it in the -observeValueForKeyPath:... method. Don't forget to clear that strong reference when you remove the observation.

You really should be using a unique value for the context when adding the observer and you need to check it in -observeValueForKeyPath:.... However, it's not a good idea to use the block. For one thing, you still need to keep a strong reference to the block, so it doesn't avoid the need to store such a strong reference.

The context should be a means for an object to a) determine that a call to -observeValueForKeyPath:... corresponds to that code's own observation; and b) remove the observation in a way that distinguishes it from any other observation that may have been set up by other code (using -removeObserver:forKeyPath:context:). Therefore, the context should identify the code that's using it, not any specific observer or observee. The usual approach is to define a static variable and use its address.

Finally, even the fragmentary -observeValueForKeyPath:... implementation you showed is pretty broken. change will never be equal to @"AVPlayerItemStatusReadyToPlay" because the former is a dictionary and the latter is a string. In addition to checking the context (and calling through to super if the notification is not for your observation), you should check the keyPath or maybe the status property of object.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
1

Ken is right-- your KVO notification handler (-observeValueForKeyPath) is not going to work as is. Take his suggestions there

But also you should store your PlayerItem as a property in your current object, and make sure that you have a matching -removeObserver somewhere (Probably in the dealloc, you can check if playerItem exists, and if so, -removeObserver

And as Ken said, you can then create a new property for your completionBlock to store it off; so it can then be later called in your KVO notification handler.

He also alluded to using copy, and clearing your strong reference to your block. This is because it is common to end up with a retain cycle if say for example the block came from the same class that is retaining it. If the code within the block has a reference to a property, it will retain the object that owns that property. And that same object is also retaining the block, then you will end up with a retain cycle. So just be cautious of your references :)

A O
  • 5,516
  • 3
  • 33
  • 68