Search code examples
iosobjective-c-blockskey-value-observing

Using KVO in a block function


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?
    }
}

Solution

  • 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.