Chapter 4: Hold On Loosely: Cocoa Design Patterns

If you’re like most iOS developers, Objective-C is not your first language. You probably have a background in other object-oriented languages such as Java, C++, or C#. You may have done development in C. None of these languages really prepare you for how to think in Objective-C.

In the beginning, there was Simula, and Simula had two children: C++ from Bell Labs and Smalltalk from Xerox PARC. From C++ sprang Java, which tried to make things easier. Microsoft wrote Java.NET and called it C#. Today, most developers are trained in this branch of Simula. Its patterns include generic programming, static typing, customization through subclassing, method calling, and strong object ownership.

Objective-C and Cocoa come from the Smalltalk fork. NeXT developed a framework called NeXTSTEP. It was written in Objective-C and implemented many of Smalltalk’s patterns. When Apple brought NeXTSTEP to the Mac, Apple renamed it Cocoa, although the NS prefix remains to this day. Cocoa has very different patterns, and this is what sometimes gives new developers trouble. Common Cocoa patterns include protocols, dynamic typing, customization through delegation, message passing, and shared object ownership.

I’m not going to give a computer science history lesson here, but it’s important to understand that Objective-C is not Java and it’s not C++. It’s really Smalltalk. Because few developers learn Smalltalk, most need to adjust their thinking to get the most out of Objective-C.

In this chapter, I use the terms Objective-C and Cocoa interchangeably. Technically, Objective-C is a language, and Cocoa is a collection of frameworks implemented in Objective-C. In principle, you could use Objective-C without Cocoa, but in practice, this is never done. In the following sections, you find out about the major Cocoa design patterns and how best to apply them in your programs.

The pattern names used in this chapter come from the book Design Patterns (Addison-Wesley Professional 1994. ISBN: 978-0201633610) by Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides—sometimes called “The Gang of Four.” Apple maps its patterns to the Design Pattern names in the chapter, “Cocoa Design Patterns,” in the Cocoa Fundamentals Guide (see the “Further Reading” section at the end of this chapter).

Understanding Model-View-Controller

The most important pattern in Smalltalk and Cocoa is called model-view-controller (MVC). This is an approach to assigning responsibilities within a program. Model classes are responsible for representing information. View classes are responsible for interfacing with the user. Controller classes are responsible for coordinating between models and views.

There are subtle differences between how Smalltalk and Cocoa implement MVC. This chapter discusses only how Cocoa uses MVC.

Using Model Classes

A good model class encapsulates a piece of data in a presentation-independent way. A classic example of a good model class is Person. A Person might have a name, an address, a birth date, and an image. The Person class, or related model classes, would encapsulate storing and retrieving related information from a data source, but would have no display or editing features. The same Person class should be easily reusable on an iPhone, an iPad, a Mac, or a command-line program. Model classes should reference only other model classes. They should never reference views or controllers. A model class might have a delegate that happens to be a controller, but it should implement this using a protocol so that it does not need to reference the specific controller class.

Model class names are generally simple nouns like Person, Dog, and Record. You often include a two- or three-letter prefix, such as RNPerson, to identify them as your code and prevent collisions.

Model classes can be mutable or immutable. An immutable class cannot change once it’s created. NSString is a good example of this. A mutable class like NSMutableString can change after it’s created. In this context, “change” refers only to changes that are visible outside the object. It doesn’t matter if internal data structures like caches change.

Immutable objects offer many advantages. They can save time and memory. Immutable objects can implement copy by calling retain. Because it’s impossible for the object to change, you don’t have to make a real copy. Immutable objects are inherently thread-safe without locking, which makes them much faster and safer to access in multithreaded code. Because everything is configured at initialization time, it’s much easier to ensure the object is always in a consistent state. You should use immutable model classes unless there is a reason to make them mutable.

Model classes are often the most testable and reusable classes in the system. Designing them well is one of the best ways to improve the overall quality of your code. Historically, Apple sample code has not included well-designed model classes, which confuses new developers who are led to believe that controllers (or worse, views) are supposed to hold data. More recent sample code from Apple has improved, and the example project, The Elements, includes good examples of model classes. Look at AtomicElement and PeriodicElements. (See the “Further Reading” section at the end of this chapter.)

Using View Classes

View classes are responsible for interfacing with the user. They present information and accept user events. (This is the biggest deviation from Smalltalk MVC, where controller classes are responsible for user events.) View classes should not reference controller classes. As with model classes, view classes may have a delegate that happens to be a controller, but they shouldn’t reference the controller class directly. They also shouldn’t reference other views, except their immediate superview and subviews. Views may reference model classes, but generally only the specific model object that they are displaying. For instance, a PersonView object might display a single Person object. It’s easier to reuse view objects that do not reference custom model objects. For instance, a UITableViewCell is highly reusable because it displays only strings and images. There is sometimes a trade-off between reusability and ease-of-use in view objects, and finding the right balance is an important part of your architecture. In my experience, specialized views that handle a specific model class are often very useful for application writers. Framework writers, such as the UIKit team, need to emphasize reusability.

Model-specific view class names often append View to the model class, such as PersonView or RecordView. You should do this only if the view is a subclass of UIView. Some kinds of view classes have special names. Reusable views are generally called cells, such as UITableViewCell on iOS or NSCell on Mac. Lightweight, hardware-optimized view classes are generally called layers, such as CALayer or CGLayer. Whether or not they are subclasses of UIView or NSView, they are still MVC view classes.

Views are responsible for accepting events from users, but not for processing them. When a user touches a view, the view may respond by alerting a delegate that it has been touched, but it should not perform logic or modify other views. For example, pressing a Delete button should simply tell a delegate that the Delete button has been pressed. It should not tell the model classes to delete the data, nor tell the table view to remove the data from the screen. Those functions are the responsibility of a controller.

Using Controller Classes

Between the models and the views lie the controllers, which implement most of the application-specific logic. Most controllers coordinate between model classes and view classes. For example, UITableViewController coordinates between the data model and the UITableView.

Some controllers coordinate between model objects or between view objects. These sometimes have names ending in Manager, such as CALayoutManager and CTFontManager. It’s common for managers to be singletons.

Controllers are often the least-reusable parts of a program, which is why it’s so critical not to allow view and model classes to reference them directly. Even controllers should avoid referencing other controllers directly. In this context, “directly” means referring to specific classes. It’s fine to refer to protocols that are implemented by a controller. For instance, UITableView references <UITableViewDelegate>, but should not reference MYTableViewController.

A common mistake is to allow many objects to reference the application delegate directly. For example, you may want to access a global object. A common, but incorrect, solution is to add this global object as a property on the application delegate and access it as shown here:

// Do not do this

MyAppDelegate *appDelegate =

  (MyAppDelegate*)[[UIApplication sharedApplication] delegate];

Something *something = [appDelegate something];

// Do not do this

It’s very difficult to reuse code that uses this pattern. It relies on MYAppDelegate, which is hard to move to other programs that have their own application delegate. The better way to access global objects is the Singleton pattern, discussed later in this chapter.

The model-view-controller pattern is very effective at improving code reuse. Applying it properly to your programs helps them fit into the Cocoa framework and simplify development.

Understanding Delegates and Data Sources

A delegate is a helper object that manages the behavior of another object. For example, UITableView needs to know how tall each row should be. UITableView has a rowHeight property, but this isn’t sufficient for all problems. What if the first row should be taller than the other rows? Apple might have added a firstRowHeight property for that case. Then it might have added lastRowHeight and evenRowHeight properties. UITableView would become much more complicated, and still would be limited to uses that Apple had specifically designed it for.

Instead, UITableView takes a delegate, which can be any object class that conforms to the <UITableViewDelegate> protocol. Every time UITableView is ready to draw a row, it asks its delegate how tall that row should be, allowing you to implement arbitrary logic for row height. It could be based on the data in that row, or a user configuration option, or any other criterion that is appropriate for your application. Delegation makes customization extremely flexible.

Some objects have a special kind of delegate called a data source. UITableView has a data source protocol called <UITableViewDataSource>. Generally a delegate is responsible for appearance and behavior, whereas a data source is responsible for the data to be displayed. Splitting the responsibilities this way can be useful in some cases, but most of the time the delegate and the data source are the same object. This object is generally the controller. For instance, UITableViewController conforms to both <UITableViewDelegate> and <UITableViewDataSource>.

As a general rule, objects do not retain their delegates. If you create a class with a delegate property, it should almost always be declared weak. In most cases, an object’s delegate is also its controller, and the controller almost always retains the original object. If the object retained its delegate, you would have a retain loop and would leak memory. There are exceptions to this rule. For example, NSURLConnection retains its delegate, but only while the connection is loading. After that, NSURLConnection releases its delegate, avoiding a permanent retain loop.

Delegates are often observers (see “Working with the Observer Pattern” later in this chapter). It’s common for objects to have delegate methods that parallel their notifications. For example, UIApplication sends its delegate applicationWillTerminate:. It also posts the notification UIApplicationWillTerminateNotification.

Configuring your objects using delegation is a form of the Strategy pattern. The Strategy pattern encapsulates an algorithm and allows you to change how an object behaves by attaching different strategy (algorithm) objects. A delegate is a kind of Strategy object that encapsulates the algorithms determining the behavior of another object. For instance, a table view’s delegate implements an algorithm that determines how high the table view’s rows should be. Delegation reduces the need for subclassing by moving customization logic into helper objects, improving reusability and simplifying your code by moving complex customization logic out of the main program flow. Before adding configuration properties to your classes, consider adding a delegate instead.

Working with the Command Pattern

The Command pattern encapsulates a request as an object. Rather than calling a method directly, you package the method call into an object and dispatch it, possibly at a later time. This can provide significant flexibility and allows requests to be queued, redirected, logged, and serialized. It also supports undoing operations by storing the inverse of the commands. Cocoa implements the Command pattern using target-action and NSInvocation. In this section, you discover how to use NSInvocation to create more complex dispatch objects such as trampolines.

Using Target-Action

The simplest form of the Command pattern in Cocoa is called target-action. This isn’t a full implementation of the Command pattern because it doesn’t encapsulate the request into a separate object, but it allows similar flexibility.

UIControl is an excellent example of target-action. You configure a UIControl by calling addTarget:action:forControlEvents:, which establishes a target, which is the object to send a message, an action, which is the message to send, and a set of events that will trigger the message. The action selector must conform to a particular signature. In the case of UIControl, the signature must be in one of the following forms:

- (void)action;

- (void)action:(id)sender;

- (void)action:(id)sender forEvent:(UIEvent *)event;

UIControl can then dispatch its action like this:

[target performSelector:action

             withObject:self

             withObject:event];

Because of how Objective-C message passing works, this use of performSelector:... works whether action takes one, two, or no parameters. (See Chapter 28 for details of how Objective-C message passing is implemented.)

Target-action is very common in Objective-C. Controls, timers, toolbars, gesture recognizers, IBAction, notifications, and other parts of Cocoa rely on this pattern.

Target-action is similar to delegation. The main difference is that in target-action, the selector is configurable, whereas in delegation, the selector is defined by a protocol. It’s easier for a single object to be the target of several NSTimer objects than it is to be the delegate of several UITableView objects. To listen to multiple NSTimer objects, you only need to configure them with different actions:

[NSTimer scheduledTimerWithTimeInterval:1 target:self

                selector:@selector(firstTimerFired:) ...];

[NSTimer scheduledTimerWithTimeInterval:1 target:self

                selector:@selector(secondTimerFired:) ...];

To listen to multiple table views, you need to check which table view sent the request:

- (NSInteger)numberOfSectionsInTableView:(UITableView*)tv {

  if (tv == self.tableView1) {

    return [self.dataset1 count];

  }

  else if (tv == self.tableView2) {

    return [self.dataset2 count];

  }

  else {

    NSAssert(NO, @”Bad tv: %@”, tv);

    return 0;

  }

}

Each delegate method must include this if logic, which can become very cumbersome. For this reason, multiple instances of a class generally do not share the same delegate.

On the other hand, delegation allows you to verify at compile time that the required methods are implemented. The compiler cannot verify that the target of an NSTimer implements a given action.

While the compiler cannot determine if a target implements a given action, you can check for simple typos by turning on the Undeclared Selector warning (GCC_WARN_UNDECLARED_SELECTOR, -Wundeclared-selector). This generates a warning if an @selector(...) expression references an unknown selector.

Target-action is generally not the best tool for new code. If you have a small number of callbacks, blocks are often a better solution. See Chapter 23 for more information on blocks. If you have a large number of callbacks, delegation is generally best.

Using Method Signatures and Invocations

NSInvocation is a traditional implementation of the Command pattern. It bundles a target, a selector, a method signature, and all the parameters into an object that can be stored and invoked at a later time. When the invocation is invoked, it will send the message, and the Objective-C runtime will find the correct method implementation to execute.

A method implementation (IMP) is a function pointer to a C function with the following signature:

id function(id self, SEL _cmd, ...)

Every method implementation takes two parameters, self and _cmd. The first parameter is the self pointer that you’re familiar with. The second parameter, _cmd, is the selector that was sent to this object. This is a reserved symbol in the language and is accessed exactly like self. For more details on how to work with method implementations, see Chapter 28.

Although the IMP typedef suggests that every Objective-C method returns an id, obviously there are many Objective-C methods that return other types such as integers or floating-point numbers, and many Objective-C methods return nothing at all. The actual return type is defined by the message signature, discussed later in this section, not the IMP typedef.

NSInvocation includes a target and a selector. As discussed in the earlier section “Using Target-Action,” a target is the object to send the message to, and the selector is the message to send. A selector is roughly the name of a method. We say “roughly” because selectors don’t have to map exactly to methods. A selector is just a name, like initWithBytes:length:encoding:. A selector isn’t bound to any particular class or any particular return value or parameter types. It isn’t even specifically a class or instance selector. You can think of a selector as a string. So -[NSString length] and -[NSData length] have the same selector, even though they map to different method’s implementations.

NSInvocation also includes a method signature (NSMethodSignature), which encapsulates the return type and the parameter types of a method. An NSMethodSignature does not include the name of a method, only the return value and the parameters. Here is how you can create one by hand:

NSMethodSignature *sig =

         [NSMethodSignature signatureWithObjCTypes:”@@:*”];

This is the signature for –[NSString initWithUTF8String:]. The first character (@) indicates that the return value is an id. To the message passing system, all Objective-C objects are the same. It can’t tell the difference between an NSString and an NSArray. The next two characters (@:) indicate that this method takes an id and a SEL. As discussed previously, every Objective-C method takes these as its first two parameters. They’re implicitly passed as self and _cmd. Finally, the last character (*) indicates that the first “real” parameter is a character string (char*).

If you do work with type encoding directly, you can use @encode(type) to get the string that represents that type rather than hard-coding the letter. For example, @encode(id) is the string “@”.

You should seldom call signatureWithObjCTypes:. We do it here only to show that it’s possible to build a method signature by hand. The way you generally get a method signature is to ask a class or instance for it. Before you do that, you need to consider whether the method is an instance method or a class method. The method –init is an instance method and is marked with a leading hyphen (-). The method +alloc is a class method and is marked with a leading plus (+). You can request instance method signatures from instances and class method signatures from classes using methodSignatureForSelector:. If you want the instance method signature from a class, you use instanceMethodSignatureForSelector:. The following example demonstrates this for +alloc and –init.

SEL initSEL = @selector(init);

SEL allocSEL = @selector(alloc);

NSMethodSignature *initSig, *allocSig;

// Instance method signature from instance

initSig = [@”String” methodSignatureForSelector:initSEL];

// Instance method signature from class

initSig = [NSString instanceMethodSignatureForSelector:initSEL];

// Class method signature from class

allocSig = [NSString methodSignatureForSelector:allocSEL];

If you compare initSig and allocSig, you will discover that they are the same. They each take no additional parameters (besides self and _cmd) and return an id. This is all that matters to the message signature.

Now that you have a selector and a signature, you can combine them with a target and parameter values to construct an NSInvocation. An NSInvocation bundles everything needed to pass a message. Here is how you create an invocation of the message [set addObject:stuff] and invoke it:

NSMutableSet *set = [NSMutableSet set];

NSString *stuff = @”Stuff”;

SEL selector = @selector(addObject:);

NSMethodSignature *sig = [set methodSignatureForSelector:selector];

NSInvocation *invocation =

  [NSInvocation invocationWithMethodSignature:sig];

[invocation setTarget:set];

[invocation setSelector:selector];

// Place the first argument at index 2.

[invocation setArgument:&stuff atIndex:2];

[invocation invoke];

Note that the first argument is placed at index 2. As discussed previously, index 0 is the target (self), and index 1 is the selector (_cmd). NSInvocation sets these automatically. Also note that you must pass a pointer to the argument, not the argument itself.

Invocations are extremely flexible, but they’re not fast. Creating an invocation is hundreds of times slower than passing a message. Invoking an invocation is efficient, however, and invocations can be reused. They can be dispatched to different targets using invokeWithTarget: or setTarget:. You can also change their parameters between uses. Much of the cost of creating an invocation is in methodSignatureForSelector:, so caching this result can significantly improve performance.

Invocations do not retain their object arguments by default, nor do they make a copy of C string arguments. To store the invocation for later use, you call retainArguments on it. This method retains all object arguments and copies all C string arguments. When the invocation is released, it releases the objects and frees its copies of the C strings. Invocations do not provide any handling for pointers other than Objective-C objects and C strings. If you’re passing raw pointers to an invocation, you’re responsible for managing the memory yourself.

If you use an invocation to create an NSTimer, such as by using timerWithTimeInterval:invocation:repeats:, the timer automatically calls retainArguments on the invocation.

Invocations are a key part of the Objective-C message dispatching system. This integration with the message dispatching system makes them central to creating trampolines and undo management.

Using Trampolines

A trampoline “bounces” a message from one object to another. This technique allows a proxy object to move messages to another thread, cache results, coalesce duplicate messages, or any other intermediary processing you’d like. Trampolines generally use forwardInvocation: to handle arbitrary messages. If an object does not respond to a selector, before Objective-C throws an error, it creates an NSInvocation and passes it to the object’s forwardInvocation:. You can use this to forward the message in any way that you’d like. For full details, see Chapter 28.

In this example, you create a trampoline called RNObserverManager. Any message sent to the trampoline will be forwarded to registered observers that respond to that selector. This provides functionality similar to NSNotification, but is easier to use and faster if there are many observers.

Here is the public interface for RNObserverManager:

RNObserverManager.h (ObserverTrampoline)

#import <objc/runtime.h>

@interface RNObserverManager: NSObject

- (id)initWithProtocol:(Protocol *)protocol

             observers:(NSSet *)observers;

- (void)addObserver:(id)observer;

- (void)removeObserver:(id)observer;

@end

You initialize this trampoline with a protocol and an initial set of observers. You can then add or remove observers. Any method defined in the protocol will be forwarded to all the current observers if they implement it.

Here is the skeleton implementation for RNObserverManager, without the trampoline piece. Everything should be fairly obvious.

RNObserverManager.m (ObserverTrampoline)

@interface RNObserverManager()

@property (nonatomic, readonly, strong)

                                   NSMutableSet *observers;

@property (nonatomic, readonly, strong) Protocol *protocol;

(continued)

@end

@implementation RNObserverManager

- (id)initWithProtocol:(Protocol *)protocol

             observers:(NSSet *)observers {

  if ((self = [super init])) {

    _protocol = protocol;

    _observers = [NSMutableSet setWithSet:observers];

  }

  return self;

}

- (void)addObserver:(id)observer {

   NSAssert([observer conformsToProtocol:self.protocol],

           @”Observer must conform to protocol.”);

  [self.observers addObject:observer];

}

- (void)removeObserver:(id)observer {

  [self.observers removeObject:observer];

}

@end

Now you override methodSignatureForSelector:. The Objective-C message dispatcher uses this method to construct an NSInvocation for unknown selectors. You override it to return method signatures for methods defined in protocol, using protocol_getMethodDescription. You need to get the method signature from the protocol rather than from the observers because the method may be optional, and the observers might not implement it.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel

{

  // Check the trampoline itself

  NSMethodSignature *

  result = [super methodSignatureForSelector:sel];

  if (result) {

    return result;

  }

  

  // Look for a required method

  struct objc_method_description desc =

             protocol_getMethodDescription(self.protocol,

                                           sel, YES, YES);

  if (desc.name == NULL) {

    // Couldn’t find it. Maybe it’s optional

    desc = protocol_getMethodDescription(self.protocol,

                                        sel, NO, YES);

  }

  

  if (desc.name == NULL) {

    // Couldn’t find it. Raise NSInvalidArgumentException

    [self doesNotRecognizeSelector: sel];

    return nil;

  }

  

  return [NSMethodSignature

                        signatureWithObjCTypes:desc.types];

}

Finally, you override forwardInvocation: to forward the invocation to the observers that respond to the selector:

- (void)forwardInvocation:(NSInvocation *)invocation {

  SEL selector = [invocation selector];

  for (id responder in self.observers) {

    if ([responder respondsToSelector:selector]) {

      [invocation setTarget:responder];

      [invocation invoke];

    }

  }

}

To use this trampoline, you create an instance, set the observers, and then send messages to it as the following code shows. Variables that hold a trampoline should generally be of type id so that you can send any message to it without generating a compiler warning.

@protocol MyProtocol <NSObject>

- (void)doSomething;

@end

...

id observerManager = [[RNObserverManager alloc]

                     initWithProtocol:@protocol(MyProtocol)

                            observers:observers];  

[observerManager doSomething];

Passing a message to this trampoline is similar to posting a notification. You can use this technique to solve a variety of problems. For example, you can create a proxy trampoline that forwards all messages to the main thread as shown here:

RNMainThreadTrampoline.h (ObserverTrampoline)

@interface RNMainThreadTrampoline : NSObject

@property (nonatomic, readwrite, strong) id target;

- (id)initWithTarget:(id)aTarget;

@end

RNMainThreadTrampoline.m (ObserverTrampoline)

@implementation RNMainThreadTrampoline

- (id)initWithTarget:(id)aTarget {

(continued)

  if ((self = [super init])) {

    _target = aTarget;

  }

  return self;

}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel

{

  return [self.target methodSignatureForSelector:sel];

}

- (void)forwardInvocation:(NSInvocation *)invocation {

  [invocation setTarget:self.target];

  [invocation retainArguments];

  [invocation performSelectorOnMainThread:@selector(invoke)

                               withObject:nil

                            waitUntilDone:NO];

}

@end

forwardInvocation: can transparently coalesce duplicate messages, add logging, forward messages to other machines, and perform a wide variety of other functions. See Chapter 28 for more discussion, including how to couple with NSProxy.

Using Undo

The Command pattern is central to undo management. By storing Command objects (NSInvocation) in a stack, you can provide arbitrary undo and redo functionality.

Before performing an action that the user should be able to undo, you pass its inverse to NSUndoManager. A convenient way to do this is with prepareWithInvocationTarget:. For example:

- (void)setString:(NSString *)aString {

  // Make sure there is really a change

  if (! [aString isEqualToString:_string]) {

    // Send the undo action to the trampoline

    [[self.undoManager prepareWithInvocationTarget:self]

      setString:_string];

    // Perform the action

    _string = aString;

  }

}

When you call prepareWithInvocationTarget:, the undo manager returns a trampoline that you can send arbitrary messages to. These are converted into NSInvocation objects and stored on a stack. When the user wants to undo an operation, the undo manager just invokes the last command on the stack.

The Command pattern is used throughout Cocoa and is a useful tool for your architectures. It helps separate request dispatching from the requests themselves, improving code reusability and flexibility.

Working with the Observer Pattern

The Observer pattern allows an object to notify many observers of changes in its state, without requiring that the observed object have special knowledge of the observers. The Observer pattern comes in many forms in Cocoa, including NSNotification, delegate observations, and key-value observing (KVO). It encourages weak coupling between objects, which makes components more reusable and robust.

Delegate observations are discussed in “Understanding Delegates and Data Sources” earlier in this chapter. KVO is discussed fully in Chapter 22. The rest of this section focuses on NSNotification.

Most Cocoa developers have encountered NSNotificationCenter, which provides loose coupling by allowing one object to register to be notified of events defined by string names. Notifications can be simpler to implement and understand than KVO. Here are examples of how to use it well.

Poster.h

// Define a string constant for the notification

extern NSString * const PosterDidSomethingNotification;

Poster.m

NSString * const PosterDidSomethingNotification =

                              @”PosterDidSomethingNotification”;

...

// Include the poster as the object in the notification

[[NSNotificationCenter defaultCenter]

  postNotificationName:PosterDidSomethingNotification

                object:self];

Observer.m

// Import Poster.h to get the string constant

#import “Poster.h”

...

  // Register to receive a notification

  [[NSNotificationCenter defaultCenter] addObserver:self

    selector:@selector(posterDidSomething:)

    name:PosterDidSomethingNotification object:nil];

...

- (void) posterDidSomething:(NSNotification *)note {

  // Handle the notification here

}

(continued)

- (void)dealloc {

  // Always remove your observations

  [[NSNotificationCenter defaultCenter]

    removeObserver:self];

  [super dealloc];

}

Notice the name PosterDidSomethingNotification. It begins with the class of the poster, which should always be the class of the object. It then follows a “will” or “did” pattern. This naming convention is very similar to delegate methods and that’s intentional. The ending Notification is traditional for notification names so that they can be distinguished from other string constants like keys or paths.

This example uses a string constant for the notification name, which is critical for avoiding typos. Notification string constants do not traditionally begin with a k as some constants do. I recommend that the value of the string constant match the name of the string constant as shown in this example. This makes obvious which constant is being used when you see the value in debug logs.

The placement of const is important when declaring string constants. This declaration is correct:

extern NSString * const RNFooDidCompleteNotification;

This declaration is incorrect:

extern const NSString * RNFooDidCompleteNotification;

The former is a constant pointer to an immutable string. The latter is a changeable pointer to an immutable string. NSString is always immutable because it is an immutable class. So NSString * const is useful. const NSString * is useless. This rule is easier to remember if you read the declaration from right to left: const pointer to NSString.

As I mentioned earlier, the beginning of the notification name should always be the class of object. In this case, the class is Poster. The object is also almost always self (the object posting the notification). For consistency, the notification should always include an object, even if it is a singleton.

The observer should consider carefully whether to observe a specific object or nil (all notifications with a given name, regardless of the value of object). Observing a specific object can be cleaner and ensures that the observer won’t receive notifications from instances that it’s unaware of. A class that has a single instance today may have additional instances tomorrow.

If you observe a specific instance, it should generally be something you retain in an ivar. Observing something does not retain it, and the object you’re observing could deallocate. Continuing to observe a deallocated object won’t cause a crash; you just won’t receive notifications from that object anymore. But it’s sloppy and likely indicates a flaw in your design. It also uses unneeded slots in the notification table, which is bad for performance.

Although observing an object that deallocates won’t cause a crash, notifying a deallocated observer will, so you should always call removeObserver: in your dealloc if any part of your object calls addObserver:…. Make a habit of this. It’s one of the most common and preventable causes of crashes in code that uses notifications.

Calling addObserver:selector:name:object: multiple times with the same parameters causes you to receive multiple callbacks, which is almost never what you want. Generally, it’s easiest to start observing notifications in init and stop in dealloc. But what if you want to watch notifications from one of your properties, and that property can change? This example shows how to write setPoster: so that it properly adds and removes observations for a poster property:

- (void)setPoster:(Poster *)aPoster {

  NSNotificationCenter *nc =

                     [NSNotificationCenter defaultCenter];

  if (_poster != nil) {

    // Remove all observations for the old value

    [nc removeObserver:self name:nil object:_poster];

  }

  _poster = aPoster;

  if (_poster != nil) {

    // Add the new observation

    [nc addObserver:self

           selector:@selector(anEventDidHappen:)

               name:PosterDidSomethingNotification

             object:_poster];

  }

}

The checks for nil are very important here. Passing nil as the object or the name means “any object” or “any notification.”

While observing specific instances is cleaner and protects you against surprises when new objects are added to the system, there are reasons to avoid it. First, you may not really care which object is posting the notification. The object may not actually exist when you want to start observing notifications it might post, or the object instance may change over time.

There are also performance considerations when observing notifications. Every time a notification is posted, NSNotificationCenter has to search through the list of all registered observers to determine which observers to notify. The time required to search this list is proportional to the total number of observations registered in the NSNotificationCenter. When the total number of observations in the program reaches a few hundred, the time to search this list can become noticeable on an iPhone, particularly older models. The time required to call removeObserver: is similarly proportional to the total number of observations, which can cause serious performance problems if you have a large number of observations and post many notifications or remove observers often.

What if you want to observe a notification from a large number of objects, but not necessarily every object that might post that notification? For instance, you might be interested in changes to music tracks, but only the tracks in your current playlist. You could observe each track individually, but that can be very expensive. A better technique is to observe nil and check in the callback whether you were actually interested, as shown here:

// Observe all objects, whether in your tracklist or not

[[NSNotificationCenter defaultCenter]

  addObserver:self selector:@selector(trackDidChange:)

  name:TrackDidChangeNotification object:nil];

...

- (void)trackDidChange:(NSNotification *)note {

  // Verify that you cared about this track

  if ([self.tracks containsObject:[note object]]) {

    ...

  }

}

This technique reduces the number of observations, but adds an extra check during the callback. Whether this tradeoff is faster or slower depends on the situation, but it’s generally better than creating hundreds of observations.

Posting notifications is synchronous, which trips up many developers who expect the notification to execute on another thread or otherwise run asynchronously. When you call postNotification:, observers are notified one at a time before returning. The order of notification is not guaranteed.

Notifications are a critical part of many Cocoa programs. You just need to keep the preceding issues in mind, and they’ll be a very useful part of your architecture.

Working with the Singleton Pattern

The Singleton pattern is in many ways just a global variable. It provides a global way to access a specific object. The Singleton pattern is common throughout Cocoa. In most cases, you can identify it by a class method that begins with shared, such as +sharedAccelerometer, +sharedApplication, and +sharedURLCache. Some singleton access methods have other prefixes, such as +[NSNotificationCenter defaultCenter] and +[NSUserDefaults standardUserDefaults]. These are generally older classes inherited from NeXTSTEP. Most new frameworks use the shared prefix followed by their class name (without its namespace prefix).

The Singleton pattern is one of the most misused patterns in Cocoa because of some unfortunate sample code published by Apple. In the Cocoa Fundamentals Guide, Apple includes an implementation of the Singleton pattern that overrides the major memory management methods, allocWithZone:, copyWithZone:, retain, retainCount, release, and autorelease. Using Apple’s example, multiple calls to [[Singleton alloc] init] return the same object, which is almost never needed or appropriate. Apple’s explanation to this code indicates that it’s useful only in cases where it’s mandatory that there be only one instance of the class, which is seldom the case. Most of the time, it’s merely convenient to have one instance of the class that is easily accessible. Many classes, such as NSNotificationCenter, work perfectly well if multiple instances exist. Unfortunately, many developers do not carefully read the explanation and incorrectly copy this example.

Sometimes a strict singleton is appropriate. For example, if a class manages a unique shared resource, it may be impossible to have more than one instance. In this case, it’s often better to treat the creation of multiple instances as a programming error with NSAssert rather than transparently returning a shared instance. You will see how to implement this kind of assertion later in this section.

If you are creating a transparently strict singleton, make sure that it’s an implementation detail and not something the caller must know. For instance, the class should be immutable. If the caller has requested distinct instances using +alloc, it’s very confusing if changes to one modify the other.

In the vast majority of cases, use a shared singleton rather than a strict singleton. A shared singleton is just a specific instance that is easy to fetch with a class method, and is generally stored in a static variable. There are many ways to implement a shared singleton, but my recommendation is to use Grand Central Dispatch (GCD):

+ (MYSingleton *)sharedSingleton {

  static dispatch_once_t pred;

  static MYSingleton *instance = nil;

  dispatch_once(&pred, ^{instance = [[self alloc] init];});

  return instance;

}

This approach is easy to write, it’s fast, and it’s thread-safe. Other approaches achieve thread safety by adding an @synchronize in +sharedSingleton, but this approach adds a significant performance penalty every time +sharedSingleton is called. It is also possible to use +initialize, but the GCD approach is simpler.

Although a shared singleton is usually the best approach, sometimes you do require a strict singleton. For example, you may have a singleton that manages the connection to the server, and the server protocol may forbid multiple simultaneous connections from the same device. As a general rule, you first try to redesign the protocol so that it doesn’t have this restriction, but there are cases where doing so is impractical and a strictly enforced singleton is the best approach.

In most cases, the best way to implement a strict singleton is with the same pattern as a shared singleton, but treat calls to init as a programming error with NSAssert, as shown here:

+ (MYSingleton *)sharedSingleton {

  static dispatch_once_t pred;

  static MYSingleton *instance = nil;

  dispatch_once(&pred, ^{instance = [[self alloc] initSingleton];});

  return instance;

}

- (id)init {

  // Forbid calls to –init or +new

  NSAssert(NO, @”Cannot create instance of Singleton”);

  // You can return nil or [self initSingleton] here,

  // depending on how you prefer to fail.

  return nil;

(continued)

}

// Real (private) init method

- (id)initSingleton {

  self = [super init];

  if ((self = [super init])) {

    // Init code

  }

  return self;

}

This approach’s advantage is that it prevents callers from believing they’re creating multiple instances when that is forbidden. Frameworks should avoid silently fixing programming errors. Doing so just makes it harder to track down bugs.

As discussed earlier in the section “Understanding Model-View-Controller,” developers often use the application delegate to store global variables like this:

// Do not do this

MYAppDelegate *appDelegate =

      (MYAppDelegate*)[[UIApplication sharedApplication] delegate];

Something *something = [appDelegate something];

// Do not do this

In almost all cases, globals like this are better implemented with a Something singleton like the following:

Something *something = [Something sharedSomething];

This way, when you copy the Something class to another project, it’s self-contained. You don’t have to extract bits of the application delegate along with it. If the application delegate is storing configuration information, it’s best to move that into NSUserDefaults or a singleton Configuration object.

The Singleton pattern is one of the most common patterns in well-designed Cocoa applications. Don’t overuse it. If an object is used in only a few places, just pass the object where it is needed. But for objects that have application-wide scope, a Singleton is a very good way to maintain loose coupling and improve code reusability.

Summary

This chapter explored the most pervasive patterns in Cocoa, particularly Strategy, Observer, Command, and Singleton. You discovered how several patterns combine to facilitate Cocoa’s central architecture: model-view-controller. Cocoa uses design patterns focused on loose coupling and code reusability. Understanding these patterns will help you anticipate how Apple frameworks are structured and improve your code’s integration with iOS. The patterns Apple uses in iOS are well-established and have been studied and refined for years throughout industry and academia. Correctly applying these patterns will improve the quality and reusability of your own programs.

Further Reading

Apple Documentation

The following documents are available in the iOS Developer Library at developer.apple.com or through the Xcode Documentation and API Reference.

“Cocoa Design Patterns,” Cocoa Fundamentals Guide. This entire document is valuable to understanding Cocoa, but the section, “Cocoa Design Patterns,” focuses on how Cocoa applies the well-established software patterns.

The Elements (Sample Code). Historically, Apple sample code has not demonstrated good design or coding practices. The focus has typically been to show how a specific feature works, and the sample code typically ignores Apple’s recommendations and common best practices. Apple appears to have changed its approach to sample code, and some recent examples are well-designed and written. The Elements is a good example that developers can use to model their own projects.

Notification Programming Topics. Explains the Observer pattern implemented with NSNotification.

Undo Architecture. Explains how to use NSUndoManager using the Command pattern.

Other Resources

Gamma, Erich et al. Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley Professional, 1994. ISBN: 978-0201633610). This book is a collection of well-known design patterns, explained in practical terms with code examples in C++ and Smalltalk. It should be part of every developer’s library. Erich Gamma and his coauthors did not invent these patterns, and Design Patterns is not an exhaustive list of all patterns. This book attempts to catalog patterns that the authors found in common use among developers and to provide a framework by which developers can apply known solutions to their unique problems.

AgentM, “Elegant Delegation,” Borkware Rants. AgentM provides a somewhat different MDelegateManager class than my RNObserverManager. It was designed for Objective-C 1.0, so it does not rely on @protocol, but it’s still worth studying.borkware.com/rants/agentm/elegant-delegation

Burbeck, Steve. “Applications Programming in Smalltalk-80™: How to use Model-View-Controller (MVC).” (1987, 1992). This is the definitive paper defining the MVC pattern in Smalltalk. NeXTSTEP (and later Cocoa) modified the pattern somewhat, but the Smalltalk approach is still the foundation of MVC.st-www.cs.illinois.edu/users/smarch/st-docs/mvc.html

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.31.206