Future Paths
Total Page:16
File Type:pdf, Size:1020Kb
335 Chapter17 Future Paths You’ve now reached the final chapter of Learn Cocoa on the Mac, and hopefully by this time you’ve gotten a good feel for how Cocoa works, and how its various parts can be used to write all sorts of interesting desktop applications. However, the Cocoa frameworks are really huge, and we’ve only scratched the surface on most of the classes and concepts we’ve covered. This book was never meant to be an encyclopedic Cocoa reference (you’ve already got that on your computer, installed with Xcode), but rather a sort of guide to help you find your way. Continuing in that spirit, we’re going to wrap up the book with an overview of additional techniques that will help you take your Cocoa development efforts above and beyond what we’ve covered so far. We’ll start off with a bit of expansion on a few design patterns that we’ve mentioned, but deserve a little more attention. We’ll also take a look at how to use languages other than Objective-C to do Cocoa programming, and finally we’ll look at some ways take your hard-earned Cocoa skills and apply them to other realms than just the Mac OS X desktop. More Cocoa-isms We’ve spent a lot of time in this book dealing with the MVC pattern, which helps you partition your application into logical layers, as well as the delegate pattern, which lets you define the behavior of some GUI objects inside your controller layer, instead of subclassing the GUI objects themselves. These techniques use language features and conventions to define patterns of usage. Along those lines, Cocoa has more tricks up its sleeve. One of them is the concept of notifications (referred to as the observer pattern in some circles), which lets an object notify a collection of other objects when something happens. Another is the use of blocks (new to C and Objective-C in Snow Leopard) to simplify your code in spots where a full-blown delegate, or even a single method, might be overkill. 335 336 CHAPTER 17: Future Paths Notifications Cocoa’s NSNotification and NSNotificationCenter classes provide you with a way to let one object send a message to a bunch of other objects, without any of the objects needing to know anything about the others. All they really need to know is the name of the notification, which can be anything you like. An object that wants to be notified signs up as an observer of a particular notification name in advance, and the object that wants to broadcast a notification uses that name to send its message out to any observers that are listening. For example, let’s say you have several parts of your application that need to be updated whenever a particular event occurs, such as a piece of networking code reading a response from a web server. Using notifications, your networking code doesn’t need to know about every other object that wants the information. Instead, you can define a notification name for both the networking code and the observers to use, preferably in a header file that can be included by all classes involved: #define DATA_RECEIVED @"dataReceived" Observers can sign up to receive notifications at any time, but typically this happens during an object’s initialization: - (id)init { if ((self = [super init])) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNetworkData:) name:DATA_RECEIVED object:nil]; } return self; } That tells the application’s one and only instance of NSNotificationCenter that it should notify the caller by calling its receiveNetworkData: method whenever anyone posts a DATA_RECEIVED notification. Note the final parameter, where we’re passing a nil: if we instead specified an object there, that would limit the notification-observing to only apply to that particular object. Any other object posting the same notification wouldn’t have any effect on us. To make this work, the observer also needs to implement the method it specified when registering. This method always receives the NSNotification itself as a parameter: - (void)receiveNetworkData:(NSNotification *)notification { NSLog(@"received notification: %@", notification); } Finally, any object that sets itself up as observer should usually remove itself from the observer list later on. If you’re using GC as we have in most of this book, it’s not such a big deal; but if you’re not using GC, you really have to do this in order to avoid run-time errors. A common pattern is to do something like the following in the dealloc method of any class that ever registers as an observer: - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; CHAPTER 17: Future Paths 337 [super dealloc]; } Now, what about the notifier itself, in our case the network-reading object that’s going to broadcast its status? That’s taken care of as simply as this: if (some condition is met) { [[NSNotificationCenter defaultCenter] postNotificationName:DATA_RECEIVED object:self]; } The idea is that the notifier can fling out a notification like that, and the notification center takes care of the actual delivery. The notifier can also pass along additional information, in the form of a dictionary which can be retrieved by an observer, like this: // in the notifier NSDictionary *info = [NSDictionary dictionaryWithObject:someData forKey:@"data"]; [[NSNotificationCenter defaultCenter] postNotificationName:DATA_RECEIVED object:self userInfo:info]; // in the observer NSLog(@"received data %@", [[notification userInfo] objectForKey:@"data"]); Blocks In Chapter 16, we introduced you to blocks, an addition to C that Apple has come up with and included in Snow Leopard. We brought up blocks in the context of the concurrency features provided by Grand Central Dispatch, where blocks fit in really well, but there are many more uses for blocks. In Snow Leopard, Apple extended several Cocoa classes, adding dozens of new methods that take blocks as parameters. Let’s take a look at some of them. Enumeration Let’s start with something simple: enumeration. You’re probably familiar with the standard C-based ways of stepping through a list, and perhaps the use of NSEnumerator and even the new fast enumeration (“for – in” loops) that’s been available since the release of Leopard. Now, blocks provide us with yet another way to do the same thing: NSArray *array = [NSArray arrayWithObjects:@"one", "two", @"three"]; // C-style enumeration int i; for (i = 0; i < [array count]; i++) { NSLog(@"C enumeration accessing object: %@", [array objectAtIndex:i]); } // NSEnumerator, the "classic" Cocoa way to enumerate 338 CHAPTER 17: Future Paths NSEnumerator *aEnum = [array objectEnumerator]; id obj1; while ((obj1 = [aEnum nextObject])) { NSLog(@"NSEnumerator accessing object: %@", obj1); } // "Fast enumeration", released as part of Leopard id obj2; for (obj2 in array) { NSLog(@"Fast enumeration accessing object: %@", obj2); } // "Block enumeration", new in Snow Leopard [array enumerateObjectsUsingBlock:^(id obj3, NSUInteger i, BOOL *stop) { NSLog(@"Block enumeration accessing object: %@", obj3); }]; The block we pass in to enumerateObjectsUsingBlock: takes three arguments, and returns nothing. That’s the block signature declared by the method, and that’s what we have to follow. The three arguments sent into our block are an object from the array, an integer containing that object’s index in the array, and a BOOL passed by reference that lets us halt the enumeration by setting its value to YES. Looking at it that way, it may not be obvious at first why the block version is any better than the others, but the fact is that it really combines the best of all the other ways of enumerating. For one thing, it gives you the index of the current object, which is really handy if you want to do something like print out a numbered list of the items in an array. We can’t tell you how many times we’ve dropped down to C-style iteration just for easy access to each object’s index value! Also, there’s a variant of this method that lets you specify options defining how the enumeration runs, such as make it run concurrently: [array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj3, NSUInteger i, BOOL *stop) { NSLog(@"Block enumeration accessing object: %@", obj3); }]; That method actually uses GCD to spread the work around to all available processor cores, which will make your app run more quickly and better utilize system resources. And you get it for free! Similar enumeration methods exist for the NSSet class, but without the index parameter (because the objects in a set are, by definition, unordered). NSDictionary has also gotten some good block action, with new methods such as enumerateKeysAndObjectsUsingBlock: (and its options-taking variant that allows for concurrency), letting you specify a block that gets the key and value together. This is much better than previous ways of enumerating the contents of a dictionary, which typically involved stepping through all the keys, and looking up the value for each key. CHAPTER 17: Future Paths 339 Observing Notifications Using Blocks We realize that we just threw notifications at you a few pages ago, but guess what: Apple’s already taken the block concept and applied it to the NSNotification class as well, which boasts a new method in Snow Leopard that lets you specify a block rather than a selector, like this: [[NSNotificationCenter defaultCenter] addObserverForName:DATA_RECEIVED object:nil queue:nil usingBlock:^(NSNotification *notification){ NSLog(@"received notification: %@", notification); }]; This is cool in a couple of ways. For one thing, it frees you from the burden of creating a method for your notification-handling code, letting you instead put it inline with the code that’s setting it up, which can make your code easier to read. The other cool thing, which is true of all blocks, is that because the block you create picks up its context from the location it’s defined in, it has access to not only instance variables, but also local variables defined earlier in the same method.