Thursday, July 23, 2015

iOS KVO explained or How Key Value Observations work

There are so many ways to transfer data between objects. delegate, blocks, notifications, etc. Understanding them and how to use them properly is one of the most critical aspects of developing apps. Which ones should I used and why is very important. With that being said, here is one more that I've used many times before but always seemed to avoid really understanding, until now. KVO.

Key-Value Observing (KVO) is another way to learn about other objects without them really offering it up easily. The basis of this technique is if you want to know when a value of a different object changes you can create a KVO to detect and inform you of when this actually happens. Many apple APIs use this like AVPlayer. Personally I am not a big fan but I can appreciate why it is used.

Lets get down to it. Here is the declaration;

 [object addObserver:(NSObject *)anObserver
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context];

-object is the object you want to observe the change on
-anObserver should whom you want to be listening, usually self.
-keyPath is the name of the property you want to listen for. Example is when a label text changes you would put in @"text"
-options is for when it actually changes, what do you want back. There are 4 options of which you can choose multiple;    
    NSKeyValueObservingOptionNew = 0x01, when you want what the new value is
    NSKeyValueObservingOptionOld = 0x02, when you want what the old value was
    NSKeyValueObservingOptionInitial = 0x04, when you want to know before it happens
    NSKeyValueObservingOptionPrior  = 0x08 when you want to know after it happens (can't get the old one then)
-context is a unique identifier that you can reference when the call back method is actually called. I needs to be unique and nothing else. So people like to get creative with this sometimes. Most of the time I see something like this;


static void *AVPlayerDemoPlaybackViewControllerStatusObservationContext = &AVPlayerDemoPlaybackViewControllerStatusObservationContext;

This is just a pointer that references itself. It is small and unique so it fits perfectly. And when you want to find it, you can do so easily. 

Now that you are listening, now what? How do I actually do something? Well you need to run the actually method it plans to run when it needs to of course. It is;

- (void)observeValueForKeyPath:(NSString*) path
                      ofObject:(id)object
                        change:(NSDictionary*)change
                       context:(void*)context

-path is the string you want
-object is the actual object 
-change is a dictionary of the items you requested
-context is that unique context you created above

Since this method is called for all value changes you should isolate what you are looking for each time by referencing path, context and/or object. Once you have done that, grab the change dictionary and see what was actually changed. 

Note: Most of the time you want to compare the new and old one when you get them to make sure it was not actually just updated with the same thing twice.

   if (context == AVPlayerDemoPlaybackViewControllerStatusObservationContext){
        AVPlayerItemStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
        self.streamStatusType = StreamStatusTypeUnknownState;
        switch (status){
            case AVPlayerItemStatusUnknown:{
                self.streamStatusType = StreamStatusTypeUnknownState;
                break;
            }
            case AVPlayerItemStatusReadyToPlay:{
                self.streamStatusType = StreamStatusTypeReadyToPlay;
                break;
            }
            case AVPlayerItemStatusFailed: {
                self.streamStatusType = StreamStatusTypeFailed;
                break;
            }
        }
     }

And that is about it!

Bonus note: removing the observer can actually crash the app randomly is seems so it is good practice to add a @try @catch around it just in case.

        @try {
            [self.playerItem removeObserver:self forKeyPath:@"status"];
        }
        @catch (NSException *exception) {
            
        }
        @finally {
            

        }


References:
Apple KVO intro
Apple KVO example


No comments:

Post a Comment