Using Observers on iOS

[In response to the popularity of this particular page, I have written a primer on the subject, which you can see here. I have also built a sample iOS project for XCode 4.2 and iOS 5 showing observers in action]

There is a very powerful feature in iOS called observers that allows objects to monitor the value of an instance variable in another object. An object can register an observer, asking to be notified if the value of an instance variable changes. When the value is changed, and there are observers attached, all the observers are notified of the new value. This feature is a key part of the Cocoa programming on MacOS, and Interface Builder allows you to easily attach variables to elements of a control (titles, values, state). However, this was not carried over into iOS thought Interface Builder, but it is still available programmatically.

Why use observers? It makes it easier for different views to update their visual state when that state is driven by underlying data. Consider a situation where you have a UITableView that displays a list of objects by name (I’ll call this Table A). Tapping on an entry opens a new view (View B), which contains (among other things), a feature that allows the user to edit the name of the selected object. One way to make sure that Table A contains an up-to-date name for the object is to have View B tell Table A when the name of the object has been changed. This may not be a big deal for simpler apps, but if there are other views that also reference the object and display that object’s name, then View B would also have to know about those views and tell them when the name of an object changes. Every time you add a view that wants to display this same data, and keep it up to date, you have to go back to View B to update it accordingly. If all of these views also decided to display other editable data on the object, it would be necessary to update View B (or any other view that edits the data) to make sure every view that cares about the data is informed of any changes. In more complicated apps, it can be easy to miss something, and now you have a view that is displaying incorrect or out-of-date information.

Enter the observer. Rather than putting the onus on View B to tell other views about changes to an object, a view (or any NSObject for that matter) can register an observer on the data object in question. Going back to the original example, Table A would register an observer on an object’s name. Whenever the name is changed, Table A will be notified. Adding new views that edit the data is simpler, because they don’t need to know or care that Table A, View B or any other view object needs to know about changes to the name.

How do you do this? Using the addObserver method that part of an NSObject. If you want to be able to observe an instance variable, you have to have set it up as a property (or manually specified the set and get methods for the variable). For example, consider the following object:

@interface SimpleObject : NSObject  {
NSString *name;
}

 

@property (nonatomic, retain) NSString *name;

If you @synthesize the name variable, you automatically get the appropriate getters and setters for the variable. If another object wants to add itself as an observer and react to changes in the name variable on an instance of SimpleObject, it needs to do 2 things. First it has to add itself using the addObserver method on an instance of SimpleObject. To add it to an instance stored in a variable name obj, the code would look like this.

[obj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld context:nil];

There are different options available that specify what data accompanies the notification, and you can read about those in the iOS documentation. The context provides a way for you to provide additional data on the notification. Note that the string provided in forKeyPath must match the name of the instance variable.

The second thing that is necessary is to actually implement the notification method, so that your object can be told when the value changes. The method you need to implement is observeValueForKeyPath.

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Do whatever you need to do here
}

The keyPath is the string you originally specified as the keyPath when the observer was added. The object is the one which is notifying you of the change. The change variable provides information about what changed and how (in some cases, it can include the original value of the variable). The context is the pointer you passed originally when adding the observer.

One important thing to remember is to remove observers when they are no longer needed. Otherwise, they will “pile up” if a view is deallocated and reallocated, and observers registered each time. To remove an object as an observer, you would do the following.

[obj removeObserver:self forKeyPath:@"name"];

That is all there is to it. This is a nice, simple way for views to be able to monitor attributes of objects, and keep their interfaces up to date, without having to make the views that changes these values aware of who needs to know what. There are more details available in the iOS developer documentation related to the NSObject class.

Advertisements

One thought on “Using Observers on iOS

Comments are closed.