Design Patterns: Key-Value Observing


Key-Value Observing (KVO) is heavily used in OS X development in bindings and form a significant portion of UI design. However, with iOS, it takes on somewhat less significance and is mostly used as necessary to simplify program design. KVO “broadcasts” a notification whenever a property is modified, and any class can receive those notifications and handle changes as necessary. This helps to decouple your classes from each other, allowing more flexible and re-usable code, especially as your code grows large and you have many sets of MVC objects.

KVO Layout

KVO Structure

It is primarily used when you have one model object backing multiple controllers, in which case you can use KVO to update the model when any controller changes its data.

Preparing for Key-Value Observing

KVO is set up for you when you use properties. Whenever you use dot-notation or the setter methods to change a property, the corresponding KVO method is called and all receivers will be notified. If you do not use properties or write your own setters, you may want to manually call change notification methods; see the section below to do so.

Key-Value Observing Compliance

To be KVO-compliant, your class must first be KVC compliant. This means that you should have proper, working implementations for valueForKey: and setValue:forKey:. In addition, you should emit the proper notifications when changes are made.

As stated above, if you use standard setters, the notifications are called for you, and you don’t have to worry about them.

Manually Call Change Notifications

You may need to manually call notifications if you don’t use standard setters, if you want to minimize notifications for certain situations, or to package multiple changes into a single notification.

First, you have to override the implementation of automaticallyNotifiesObserversForKey:, which is a class method declared in the NSKeyValueObserving protocol. Given a key, if you want manual notification, return NO; otherwise, return YES. If the key is not recognized, call the super implementation. The default implementation in NSObject simply returns YES.

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey { if ([theKey isEqualToString:@"openingBalance"]) return NO; else return [super automaticallyNotifiesObserversForKey:theKey]; }

Then, to call the actual notifications, you call willChangeValueForKey: before changing the value, and didChangeValueForKey: after the change.

- (void)setBalance:(double)newBalance { // Should not compare double directly, // But means the same as if (balance == newBalance) if (fabs(balance - newBalance) < 0.0001) return; [self willChangeValueForKey:@"balance"]; balance = newBalance; [self didChangeValueForKey:@"balance"]; }

Note that to minimize redundant notifications, you can check to see if the value has actually changed.

Finally, you can call willChangeValueForKey: and didChangeValueForKey: multiple times with different keys to send multiple notifications if multiple values get changed.

Registering for Key-Value Observing

To register a class for Key-Value Observing, the observed class must be KVO-compliant for the properties that you want to observe (obviously), you must register as an observer, and implement the observing method.

Note that not all classes are KVO-compliant for all properties. Ensure compatibility as necessary in your classes, but note that properties in Apple’s code are only KVO-compliant of the documentation says so.

Register an Observer

The observed object must be made aware of the observer by sending the addObserver:forKeyPath:options:context: method. In your view controller, you might have the following method, which registers a model object as an observer for the balance property:

- (void)registerObserver { [modelObject addObserver:self forKeyPath:@"balance" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL]; }

The options argument takes one or more of the NSKeyValueObservingOptions values. You will most often use the constants in the example above, which tells the system to return both the original and new values. You specify both using the bitwise OR operator (|), the vertical bar.

The context is a pointer to anything you wish, and is provided as-is to the observer. You can use it as an identifier to determine the change, or to provide any other data. It is your responsibility to retain and/or release it as necessary; it is not retained by the system. The context can be used to identify your notifications. A problem may arise because you can register the same keyPath on multiple objects. You can therefore use a distinguishing context to determine the calling class. See this link for more information on how to solve this issue.

Receiving Notifications

All observers must implement the observeValueForKeyPath:ofObject:change:context: method. The observer is provided the original object and key path that triggered the change, a dictionary containing the details of the actual change, and the user-specified context described above.

The dictionary has a value accessed through the NSKeyValueChangeKindKey, which is a standard NSDictionary change key. It provides information about the change, returning an NSKeyValueChangeSetting. The exact values are defined in the link. You can also access the old or new values (depending on whether neither, one, or both were requested) through the NSKeyValueChangeOldKey and NSKeyValueChangeNewKey keys. If the property is an object, it is returned directly; scalars or C structs are wrapped as an NSValue object.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqual:@"balance"]) { // Handle change } else // Unrecognized keypath [super observeValueForKeyPath:keyPath]; }

Removing an Observer

To clean up after yourself, you should remove yourself as an observer by calling the removeObserver:forKeyPath: method.

- (void)unregisterForKVO { [modelObject removeObserver:self forKeyPath:@"balance"]; }

If you received a context from the notifications, you should only release it after removing the observer.

Design Patterns: Model-View-Controller


As programs get larger, there is a greater need for a paradigm to keep everything in order. At the simplest level, any program is a means to store data and present it in a meaningful way. With only a few views and a good data store, this might be rather easy in a simple app for iPhone. But with Pages-caliber apps, especially on the iPad, a formal solution is needed. Cocoa adopts the Model-View-Controller paradigm (MVC) to better organize projects.

What is MVC?

MVC Overview

MVC Overview

Under the MVC system, your classes are one of three types—a model, a view, or a controller. Models store data in an organized form. For a simple app, the model could just be the actual data store, either in-memory (maybe as an NSArray or NSDictionary), or to-and-from disk. In a more complex app, you may choose to use a SQLite database or Core Data, and your model would be a simple instance or one piece of data. More importantly to MVC though, your model must not describe how to store the data—it must not contain any information about padding in the UI, for example, or the position (frame) of any item. Models are usually specific to your application, so they usually are not reusable, unless you have a “template” method to store data.

Views are what you actually see on-screen. They have functions to draw to screen or to other contexts. The view should not store the data it is displaying though—a label should not keep its text around; a table view should not store a copy of its data. Instead, along with MVC there are the patterns of delegation and the language features of KVC and KVO. Many of the classes from UIKit, including UILabel, UITableView, UITextView, and indeed UIView are views. Obviously, views, because they just display (any) data, can be very easily reused. Conversely, views of should not be bound to specific data. Some views may be suited to certain types of data—table views are more suited to text; the iPhone’s home screen is more suited for icons and short text—but you should not impose other restrictions, such as a maximum text length or that the text should only be of one case (that’s so 1980s…).

The view and the model should never interact. This preserves reusability. If UITableView had an outlet to your MainDataModel, it could it only be used with MainDataModel. If you had a model class called Person, it wouldn’t work with UITableView (and the Address Book app would never exist). The controller serves as a median. As shown in the image above, the controller controls interaction. If the model changes, the controller is notified and the view is changed accordingly (for example, you could call [tableView reloadData]). If user interaction changes the model (for example, deleting a row from a table view), the controller is also notified, and from there the change gets propagated to the data store. Therefore, the view can just tell the controller that some data at this location got deleted; the view does not have to worry about what to do or how to handle the deletion so it actually gets deleted. This goes back to the concept of abstraction, one of the fundamentals of object-oriented programming. Similarly, the model does not have to be concerned with exactly how the data got deleted, and just delete the data. In this way, the classes are kept clean and perform one function—the point of a class in the first place. Because the controller is the median and has to deal with specific views and data models, it is typically the least reusable. UIViewController implements so little functionality (from a functionality perspective—it actually does a lot behind the scenes) because controllers are very specific to each application. Most of your app logic goes into the controller.

Another compelling reason to use a controller is to make decisions. Obvious, but there are situations where changes to the model or view should not or need not propagate to the other. Without a controller, a change in the data at row 586 would affect the view even if the view was only showing rows 5–10, an unnecessary operation (which may even cause the UI to slow down for a moment). Other times, data should not be deleted, or deleted at a later time—if a file is being written to from another thread, a delete command from the UI should not be executed immediately. The write should be stopped or allowed to finish before the delete occurs.

Mediation

Hand-in-hand with MVC comes the concept of delegation (and data sources). Data sources are obvious—protocols such as UITableViewDataSource make it so a table view can get data from an id type, making the table view very reusable. The controller implements the delegate, and asks its data model for data as necessary (or as the data source methods are called). More interestingly though, the controller can return data that does not correspond to the data model; in fact, the controller could calculate values and not have a data model at all. The table view does not have to know about this though, and does not need to do any extra handling in these cases.

The data source protocol “pushes” data to the view; the delegate goes in the other direction and informs the controller of changes to the view. There is a naming convention involved, which will be the topic of a future post. Again, the controller can notify the model, or not, depending on the situation.

KVC and KVO are not really used in simple applications. However, with multiple controllers that need to interact with each other, KVC and KVO can be used to great benefit. KVO, which stands for Key-Value Observing, registers a class for notifications when a key-value is changed in any other class. This is a more advanced topic, and will form the basis of another post. It is an effective way to allow multiple controllers to communicate without resorting to a tangle of protocols.

Resources

WWDC 2010 had a great lecture on MVC and the “10 Best MVC Tips Ever.” It is highly recommended. Note that you will need to be a registered developer, including free developers.
Video Link | iTunes Link | Slides (9.7MB)

Extension 7: Loop Aids


In a loop, you can use the keywords continue; and break; to control the execution of the loop.

The continue Keyword

In a loop, continue tells the program to begin the next iteration, ignoring any code that comes after it. For example, if we had a simple program that reads in input and checks if the input is 5, we could use the following code:

for (int times = 1; times <= 10; times++) {
	int input;
	scanf("%d", &input);
	if (input != 5)
		continue;
	NSLog(@"Five!");
} 

In this loop, if the input is not 5, then the word “Five” is not printed. The program moves on to the next iteration of the loop.

The break Keyword

The break statement is used in the same context as continue, except that the break statement immediately ends the execution of the loop and moves on to the code after the loop. For example:

for (int times = 1; times <= 10; times++) {
	int input;
	scanf("%d", &input);
	if (input != 5)
		break;
}
NSLog(@"Not Five!");

“Not Five” will be printed as soon as the user enters something that is not five.

Pitfalls

Keep in mind that continue and break only apply to the “closest” loop—that is, in the case of nested loops, they will only apply to the “deepest” loop.

Comments are Important


Say hello to your friend, the comment.

// Comments will make your life a lot easier.

/*
Don’t be afraid to use a lot of comments.
They can only help.
*/

I know writing comments is extra work. For those of use who don’t have perfect typewriting skills (and with the QWERTY keyboard, who does?). But they are extremely valuable.

(Zemanta tells me that I should add this link: 7 Reasons to Switch to the Dvorak Keyboard Layout. Personally, I’d prefer the Programmer’s Dvorak, if I can ever set up Ukelele to work properly. Who knows what I’m talking about???)

I write convoluted code. I have a hard time following MVC. It’s a fact of life. I easy this problem significantly by using comments. Trust me—I was skeptical about comments. Why should I write comments? I know what my code does! Uh…no I don’t. It’s a simple fact of a programmer’s life that we start to forget about what our code does. Or why that code works the way it does. By adding comments at the beginning of every few lines, or even a few tabs away from the end of every other line, it makes my job so much easier.

Trust me. Comments are worth the extra effort.

Don’t Write Confusing Code


Here is a very important lesson that I’ve just begun to learn:

Don’t write confusing code.

Make sure you know exactly what you plan to do. Think it out, make a solid plan, and organize it mentally. As Wil Shipley of Pimp My Code (See my Links page) says, you have to know exactly what you’re going to do, before you do it. To paraphrase, your new project is a clean slate. Every line of code is a filthy black streak. I guess every line of ugly code is a scratch…?

As I work on an app for a friend, I realized that I haven’t planned enough. As a result, a lot of my code is very convoluted, to the point where I can’t figure out the logic behind it at all. There’s too much for me to want to re-write—it’s just a huge mess that it ends up being. Had I known what I wanted every piece of code to do, I wouldn’t be in this mess.

So how would I recommend doing this? Simple—plan out all the steps as if you were manually doing them. Then, convert to code. To paraphrase Mr. Shipley again, instead of writing if (something is true) then (doing this, something, and that), write if (something is not true)…apparently it is more natural that way. I’ll let him explain:

Don’t indent and indent and indent for the main flow of the method. This is huge. Most people learn the exact opposite way from what’s really proper — they test for a correct condition, and if it’s true, they continue with the real code inside the ‘if’.

What you should really do is write ‘if’ statements that check for improper conditions, and if you find them, bail. This cleans your code immensely, in two important ways: (a) the main, normal execution path is all at the top level, so if the programmer is just trying to get a feel for the routine, all she needs to read is the top level statements, instead of trying to trace through indention levels figuring out what the “normal” case is, and (b) it puts the ‘bail’ code right next to the correctness check, which is good because the ‘bail’ code is usually very short and belongs with the correctness check.

When you plan out a method in your head, you’re thinking, ‘I should do blank, and if blank fails I bail, but if not I go on to do foo, and if foo fails I should bail, but if not i should do bar, and if that fails I should bail, otherwise I succeed,’ but the way most people write it is, ‘I should do blank, and if that’s good I should do foo, and if that’s good I should do do bar, but if blank was bad I should bail, and if foo was bad I should bail, and if bar was bad I should bail, otherwise I succeed.’ You’ve spread your thinking out: why are we mentioning blank again after we went on to foo and bar? We’re SO DONE with blank. It’s SO two statements ago.

Thank you, Mr. Shipley. Don’t write confusing code.

Edit: Etresoft below has made an important point. I also think I didn’t quite make myself perfectly clear. As I’ve said about my project for my friend, I jumped in, without proper planning. That, coupled with poor logic, is resulting in a mess that I’m fighting to pull myself out of right now. I believe that any app needs a good dose of UI design and even class diagrams—plus a good amount of reasonable logic. Don’t make code more convoluted than it needs to be.

Also, I tend to re-write a lot of code in if-else statements…that’s just more room for error, and makes code harder to manage. It was easier to do that the first time around, rather than figuring out exactly what I needed to put in the if-else.

Don’t take the easy way out, and trade that off for convoluted code.

With more experience, I may end up doing a reprise of this post. Etresoft’s right—I do learn through mistakes.

Thanks!

  • Welcome

    My goal is to make CupsOfCocoa into a beautiful source for beginners to the iPhone platform to get started. Subscribe below for more, and stay tuned!

  • Contact Me

    If you need to contact me for any reason, feel free to send me an email.
  • The Giving Spirit

    If you've found this site helpful, would you consider donating a little sum? Any amount is appreciated...Thanks so much!

  • Roadmap

  • Enter your email address to follow this blog and receive notifications of new posts by email.

    Join 220 other followers

  • Back to the Past

    May 2020
    S M T W T F S
     12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31  
  • Time Machine

  • You count!

    • 622,650 views
  • Worldwide Stats

    free counters
%d bloggers like this: