Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

< Previous PageNext Page > Hide TOC

Basic Subclass Design

All subclasses, regardless of ancestor or role, share certain characteristics when they are well designed. A poorly designed subclass is susceptible to error, hard to use, difficult to extend, and a drag on performance. A well-designed subclass is quite the opposite. This article offers suggestions for designing subclasses that are efficient, robust, and both usable and reusable.

Further Reading: Although this document describes some of the mechanics of crafting a subclass, it focuses primarily on basic design. It does not describe how you can use the development applications Xcode and Interface Builder to automate part of class definition. To start learning about these applications, read Xcode Quick Tour Guide for Mac OS X. The design information presented in this section is particularly applicable to model objects. Model Object Implementation Guide discusses the proper design and implementation of model objects at length.

In this section:

The Form of a Subclass Definition
Overriding Superclass Methods
Instance Variables
Entry and Exit Points
Initialize or Decode?
Storing and Accessing Properties
Key-Value Mechanisms
Object Infrastructure
Error Handling
Resource Management and Other Efficiencies
Functions, Constants, and Other C Types
When the Class Is Public


The Form of a Subclass Definition

An Objective-C class consists of an interface and an implementation. By convention, these two parts of a class definition go in separate files. The interface file (as with ANSI C header files) has an extension of .h; the implementation file has an extension of .m. The name of the file up to the extension is typically the name of the class. Thus for a class named Controller, the files would be:

Controller.h
Controller.m

The interface file contains a list of property, method, and function declarations that establish the public interface of the class. It also holds declarations of instance variables, constants, string globals, and other data types. The directive @interface introduces the essential declarations of the interface, and the @end directive terminates the declarations. The @interface directive is particularly important because it identifies the name of the class and the class directly inherited from, using the form:

@interfaceClassName : Superclass

Listing 3-2 gives an example of the interface file for the hypothetical Controller class, before any declarations are made.

Listing 3-2  The basic structure of an interface file

#import <Foundation/Foundation.h>
 
 
@interface Controller : NSObject {
 
}
 
@end

To complete the class interface, you must make the necessary declarations in the appropriate places in this interface structure, as shown in Figure 3-4.


Figure 3-4  Where to put declarations in the interface file

Where to put declarations in the interface file

See “Instance Variables” and “Functions, Constants, and Other C Types” for more information about these types of declarations.

You begin the class interface by importing the appropriate Cocoa frameworks as well as the header files for any types that appear in the interface. The #import preprocessor command is similar to #include in that it incorporates the specified header file. However, #import improves on efficiency by including the file only if it was not previously included, directly or indirectly, for compilation. The angle brackets (<...>) following the command identify the framework the header file is from and, after a slash character, the header file itself. Thus the syntax for an #import line takes the form:

#import <Framework/File.h>

The framework must be in one of the standard system locations for frameworks. In the example in Listing 3-2, the class interface file imports the file Foundation.h from the Foundation framework. As in this case, the Cocoa convention is that the header file having the same name as the framework contains a list of #import commands that include all the public interfaces (and other public headers) of the framework. Incidentally, if the class you’re defining is part of an application, all you need to do is import the Cocoa.h file of the Cocoa umbrella framework.

If you must import a header file that is part of your project, use quotation marks rather than angle brackets as delimiters; for example:

#import "MyDataTypes.h"

The class implementation file has a simpler structure, as shown by Listing 3-3. It is important that the class implementation file begin by importing the class interface file.

Listing 3-3  The basic structure of an implementation file

#import "Controller.h"
 
 
@implementation Controller
 
 
@end

You must write all method and property implementations between the @implementation and @end directives. The implementations of functions related to the class can go anywhere in the file, although by convention they are also put between the two directives. Declarations of private types (functions, structures, and so on) typically go between the #import commands and the @implementation directive.

Overriding Superclass Methods

Custom classes, by definition, modify the behavior of their superclass in some program-specific way. Overriding superclass methods is usually the way to effect this change. When you design a subclass, an essential step is identifying the methods you are going to override and thinking about how you are going to re-implement them.

Although “When to Override a Method” offers some general guidelines, identifying which superclass methods to override requires you to investigate the class. Peruse the header files and documentation of the class. Also locate the designated initializer of the superclass because, for initialization to succeed, you must invoke the superclass version of this method in your class’s designated initializer. (See “Object Creation” for details.)

Once you have identified the methods, you can start—from a purely practical standpoint—by copying the declarations of the methods to be overridden into your interface file. Then copy those same declarations into your .m file as skeletal method implementations by replacing the terminating semicolon with enclosing braces.

Remember, as discussed in “When to Override a Method,” a Cocoa framework (and not your own code) usually invokes the framework methods that you override. In some situations you need to let the framework know that it should invoke your overridden version of a method and not the original method. Cocoa gives you various ways to accomplish this. The Interface Builder application, for example, allows you to substitute your class for a (compatible) framework class in its Info (inspector) window. If you create a custom NSCell class, you can associate it with a particular control using the NSControl setCell: method.

Instance Variables

The reason for creating a custom class, aside from modifying the behavior of the superclass, is to add properties to it. (The word “properties” here is intended in a general sense, indicating both the attributes of an instance of the subclass and the relationships that instance has to other objects.) Given a hypothetical Circle class, if you are going to make a subclass that adds color to the shape, the subclass must somehow carry the attribute of color. To accomplish this, you might add a color instance variable (probably typed as an NSColor object) to the class interface. Instances of your custom class encapsulate the new attribute, holding it as a persistent, characterizing piece of data.

Instance variables in Objective-C are declarations of objects, structures, and other data types that are part of the class definition. If they are objects, the declaration can either dynamically type (using id) or statically type the object. The following example shows both styles:

id delegate;
NSColor *color;

Generally, you dynamically type an object instance variable when the class membership of the object is indeterminate or unimportant. Objects held as instances variables should be created, copied, or explicitly retained—unless they are already retained by a parent object (as is the case with delegates). If an instance is unarchived, it should retain its object instance variables as it decodes and assigns them in initWithCoder:. (For more on object archiving and unarchiving, see “Entry and Exit Points.”)

The convention for naming instance variables is to make them a lowercase string containing no punctuation marks or special characters. If the name contains multiple words, run them together, but make the first letter of the second and each succeeding word uppercase. For example:

NSString *title;
NSColor *backgroundColor;
NSRange currentSelectedRange;

When the tag IBOutlet precedes the declarations of instance variables, it identifies an outlet with (presumably) a connection archived in a nib file. The tag also enables Interface Builder and Xcode to synchronize their activities. When outlets are unarchived from an Interface Builder nib file, the connections are automatically reestablished.

Instance variables can hold more than an object’s attributes, vendable to clients of the object. Sometimes instance variables can hold private data that is used as the basis for some task performed by the object; a backing store or a cache are examples. (If data is not per-instance, but is to be shared among instances of a class, use global variables instead of instance variables.)

When you add instance variables to a subclass, it’s worthwhile to heed a few guidelines:

Entry and Exit Points

The Cocoa frameworks send an object messages at various points in the object’s life. Almost all objects (including classes, which are really objects themselves) receive certain messages at the beginning of their runtime life and just before their destruction. The methods invoked by these messages (if implemented) are “hooks” that allow the object to perform tasks relevant to the moment. These methods are (in the order of invocation):

  1. initializeThis class method enables a class to initializes itself before it or its instances receive any other messages. Superclasses receive this message before subclasses. Classes that use the old archiving mechanism can implement initialize to set their class version. Other possible uses of initialize are registering a class for some service and initializing some global state that is used by all instances. However, sometimes it’s better to do these kinds of tasks lazily (that is, the first time they’re needed) in an instance method rather than in initialize.

  2. init (or other initializer)—You must implement init or some other primary initializer to initialize the state of the instance unless the work done by the superclass designated initializer is sufficient for your class. See “Object Creation” for information on initializers, including designated initializers.

  3. initWithCoder:If you expect objects of your class to be archived—for example, yours is a model class— you should adopt (if necessary) the NSCoding protocol and implement its two methods, initWithCoder: and encodeWithCoder: . In these methods you must decode and encode, respectively, the instance variables of the object in a form suitable for an archive. You can use the decoding and encoding methods of NSCoder for this purpose or the key-archiving facilities of NSKeyedArchiver and NSKeyedUnarchiver. When the object is being unarchived instead of being explicitly created, the initWithCoder: method is invoked instead of the initializer. In this method, after decoding the instance-variable values, you assign them to the appropriate variables, retaining or copying them if necessary. For more on this subject, see Archives and Serializations Programming Guide for Cocoa.

  4. awakeFromNibWhen an application loads a nib file, an awakeFromNib message is sent to each object loaded from the archive, but only if it can respond to the message, and only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it’s guaranteed to have all its outlet instance variables set. Typically, the object that owns the nib file (File’s Owner) implements awakeFromNib to perform programmatic initializations that require outlet and target-action connections to be set.

  5. encodeWithCoder:—Implement this method if instances of your class should be archived. This method is invoked just prior to the destruction of the object. See the description of initWithCoder: above.

  6. dealloc or finalizeIn memory-managed programs, implement the dealloc method to release instance variables and deallocate any other memory claimed by an instance of your class. The instance is destroyed soon after this method returns. In garbage-collected code, you may need to implement the finalize method instead. See “The dealloc and finalize Methods” for further information.

An application’s global application object (identified as NSApp) also sends messages to an object—if it’s the NSApp delegate and implements the appropriate methods—at the beginning and end of the application’s life. Just after the application launches, NSApp sends its delegate the messages applicationWillFinishLaunching: and applicationDidFinishLaunching: (the former is sent just before any double-clicked document is opened, the latter just afterwards). In either of these methods the delegate can specify global application logic that needs to happen just once early in the application’s runtime existence. Just before it terminates, NSApp sends applicationWillTerminate:. to its delegate, which can implement the method to gracefully handle program termination by saving documents and application state.

The Cocoa frameworks give you numerous other hooks for events ranging from window closing to application activation and deactivation. These are usually implemented as delegation messages and require your object to be the delegate of the framework object and to implement the required method. Sometimes the hooks are notifications. (See “Communicating With Objects” for more on delegation and notification.)

Initialize or Decode?

If you want objects of your class to be archived and unarchived, the class must conform to the NSCoding protocol; it must implement the methods that encode its objects (encodeWithCoder:) and decode them (initWithCoder:). Instead of an initializer method or methods, the initWithCoder: method is invoked to initialize an object being created from an archive.

Because the class's initializer method and initWithCoder: might be doing much of the same work, it makes sense to centralize that common work in a helper method called by both the initializer and initWithCoder:. For example, if as part of its set-up routine, an object specifies drag types and dragging source, it might implement something such as shown in Listing 3-4.

Listing 3-4  Initialization helper method

 (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setTitleColor:[NSColor lightGrayColor]];
        [self registerForDragging];
    }
    return self;
}
- (id)initWithCoder:(NSCoder *)aCoder {
    self = [super initWithCoder:aCoder];
    titleColor = [[aCoder decodeObject] copy];
    [self registerForDragging];
    return self;
}
 
- (void)registerForDragging {
    [theView registerForDraggedTypes:
        [NSArray arrayWithObjects:DragDropSimplePboardType, NSStringPboardType,
        NSFilenamesPboardType, nil]];
    [theView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
}

An exception to the parallel roles of a class initializer and initWithCoder: is when you create a custom subclass of a framework class whose instances appear on an Interface Builder palette. The general procedure is to define the class in your project, drag the object from the Interface Builder palette into your interface, and then associate that object with your custom subclass using the Custom Class pane of the Interface Builder Info window. However, in this case the initWithCoder: method for the subclass is not invoked during unarchiving; an init message is sent instead. You should perform any special set-up tasks for your custom object in awakeFromNib, which is invoked after all objects in a nib file have been unarchived.

Storing and Accessing Properties

The word “property” has both a general and a language-specific meaning in Cocoa. The general notion comes from the design pattern of object modeling. The language-specific meaning relates to the language feature of declared properties.

As described in “Object Modeling,” a property is a defining characteristic of an object (called in the object modeling pattern an entity) and is part of its encapsulated data. A property can be of two sorts: an attribute, such as title and location, or a relationship. Relationships can be to-one or to-many, and can take many forms, such as an outlet, a delegate, or a collection of other objects. Properties are usually, but not necessarily, stored as instance variables.

Part of the overall strategy for a well-designed class is to enforce encapsulation. Client objects should not be able to directly access properties where they are stored, but instead access them through the interface of the class. The methods that grant access to the properties of an object are called accessor methods. Accessor methods (or, simply, “accessors”) get and set the values of an object’s properties. They function as the gates to those properties, regulating access to them and enforcing the encapsulation of the object’s instance data. The accessor method that gets a value is, by convention, called a “getter” method and the method that sets a property’s value is called a “setter” method.

The declared properties feature (introduced in Objective-C 2.0) has brought changes, some subtle, to how you control access to properties. A declared property is a syntactical shorthand for declaring the getter and setter methods of a class’s property. In the implementation block you can then request the compiler to synthesize those methods. As summarized in “Declared Properties,” you declare a property, using the @property directive, in the @interface block of a class definition. The declaration specifies the name and type of the property and may include qualifiers that tell the compiler how to implement the accessor methods.

When you design a class, several factors affect how you might store and access the properties of the class. You may choose to use the declared properties feature and have the compiler synthesize accessor methods for you. Or you may elect to implement the class’s accessor methods yourself; this is an option even if you do use declared properties. Another important factor is whether your code operates in a garbage-collected environment. If the garbage collector is enabled, the implementation of accessor methods is much simpler than it is in memory-managed code. The following sections describe the various alternatives for storing and accessing properties.

Taking Advantage of Declared Properties

Unless you have a compelling reason otherwise, it is recommended that you use the declared properties feature of Objective-C 2.0 in new projects rather than manually writing all of your own accessor methods. First, declared properties relieve you of the tedious burden of accessor-method implementation. Second, by having the compiler synthesize accessor methods for you, you reduce the possibility of programming errors and ensure more consistent behavior in your class implementation. And finally, the declarative style of declared properties makes your intent clear to other developers.

You might declare some properties with @property but still need to implement your own accessor methods for them. The typical reason for doing this is wanting the method to do something more than simply get or set the value of the property. For example, the resource for an attribute has to be loaded from the file system—say, a large image file—and for performance reasons you want your getter method to load the resource lazily (that is, the first time it is requested). If you have to implement an accessor method for such a reason, see “Implementing Accessor Methods” for tips and guidelines. You tell the compiler not to synthesize accessor methods for a property either by designating that property with a @dynamic directive in the class’s @implementation block or simply by not specifying a @synthesize directive for the property (@dynamic is the default); the compiler refrains from synthesizing an accessor method if it sees that such a method already exists in the implementation and there is no @synthesize directive for it.

If garbage collection is disabled for your program, the attribute you use to qualify your property declarations is important. By default (the assign attribute), a synthesized setter method performs simple assignment, which is appropriate for garbage-collected code. But in memory-managed code simple assignment is not appropriate for properties with object values. You must either retain or copy the assigned object in your setter method; you tell the compiler to do this when you qualify a property declaration with a retain or copy attribute. Note that you should always specify the copy attribute for an object property whose class conforms to the NSCopying protocol, even if garbage collection is enabled.

The following examples illustrate a variety of behaviors you can achieve with property declarations (with different sets of qualifying attributes) in the @interface block and @synthesize and @dynamic directives in the @implementation block. The following code instructs the compiler to synthesize accessor methods for the declared name and accountID properties; because the accountID property is read-only, the compiler synthesizes only a getter method. In this case, garbage collection is assumed to be enabled.

@interface MyClass : NSObject {
    NSString *name;
    NSNumber *accountID;
}
@property (copy) NSString *name;
@property (readonly) NSNumber *accountID;
// ...
@end
@implementation MyClass
@synthesize name, accountID;
// ...
@end

Note: As shown in this and subsequent code examples, a property in a 32-bit process must be backed by an instance variable. In a program with a 64-bit address space, properties do not have to be backed by instance variables.

The following code listing illustrates another aspect of declared properties. The assumption is that garbage collection is not enabled, so the declaration for the currentHost property has a retain attribute, which instructs the compiler to retain the new value in its synthesized getter method. In addition, the declaration for the hidden property includes a qualifying attribute that instructs the compiler to synthesize a getter method named isHidden. (Because the value of this property is a non-object value, simple assignment is sufficient in the setter method.)

@interface MyClass : NSObject {
    NSHost *currentHost;
    Boolean *hidden;
}
@property (retain, nonatomic) NSHost *currentHost;
@property (getter=isHidden, nonatomic) Boolean *hidden;
// ...
@end
@implementation MyClass
@synthesize name, hidden;
// ...
@end

Important: Note that this example specifies the nonatomic attribute for the properties. Saying that a property is atomic (the default) simply guarantees that the value returned from the getter method or set through the setter method is always fully set or retrieved; it does not guarantee thread safety for the object. Thread safety cannot be expressed at the level of individual accessor methods. You must always design thread safety into the architecture and implementation of individual classes and the object graph as a whole. Moreover, there is the issue of performance. Because accessor methods are so frequently called, their performance in an application is critical. Imposing the overhead (in a non-garbage-collected environment) of locks in atomic accessors degrades overall performance. For these reasons, it is recommended that for memory-managed code you should specify the nonatomic attribute in property declarations. For more on thread-safety issues, see Threading Programming Guide.

The property declaration in the following example tells the compiler that the previewImage property is read-only so that it won’t expect to find a setter method. By using the @dynamic directive in the @implementation block, it also instructs the compiler not to synthesize the getter method, and then provides an implementation of that method.

@interface MyClass : NSObject {
    NSImage *previewImage;
}
@property (readonly) NSImage *previewImage;
// ...
@end
@implementation MyClass
@dynamic previewImage;
// ...
- (NSImage *)previewImage {
    if (previewImage == nil) {
        // lazily load image and assign to ivar
    }
   return previewImage;
}
// ...
@end

Further Reading: Read “Properties“ in The Objective-C 2.0 Programming Language for the definitive description of declared properties and for guidelines on using them in your code.

Implementing Accessor Methods

For reasons of convention—and because it enables classes to be compliant with key-value coding (see “Key-Value Mechanisms”)—the names of accessor methods must have a specific form. For a method that returns the value of an instance variable (sometimes called the getter), the name of the method is simply the name of the instance variable. For a method that sets the value of an instance variable (the setter), the name begins with “set” and is immediately followed by the name of the instance variable (with the first letter uppercase). For example, if you had an instance variable named “color,” the declarations of the getter and setter accessors would be:

- (NSColor *)color;
- (void)setColor:(NSColor *)aColor;

With Boolean attributes a variation of the form for getter methods is acceptable. The syntax in this case would take the form isAttribute. For example, a Boolean instance variable named hidden would have a isHidden getter method and a setHidden: setter method.

If an instance variable is a scalar C type, such as int or float, the implementations of accessor methods tend to be very simple. Assuming an instance variable named currentRate, of type float, Listing 3-5 shows how to implement the accessor methods.

Listing 3-5  Implementing accessors for a scalar instance variable

- (float)currentRate {
    return currentRate;
}
 
- (void)setCurrentRate:(float)newRate {
    currentRate = newRate;
}

If garbage collection is enabled, implementation of the setter accessor method for properties with object values is also a matter of simple assignment. Listing 3-6 shows how you would implement such methods.

Listing 3-6  Implementing accessors for an object instance variable (garbage collection enabled)

- (NSString *)jobTitle {
    return jobTitle;
}
 
- (void)setJobTitle:(NSString *)newTitle {
    jobTitle = newTitle;
}

When instance variables hold objects and garbage collection is not enabled, the situation becomes more nuanced. Because they are instance variables, these objects must be persistent and so must be created, copied, or retained when they are assigned. When a setter accessor changes the value of an instance variable, it is responsible not only for ensuring persistence but for properly disposing of the old value. The getter accessor vends the value of the instance variable to the object requesting it. The operations of both types of accessors have implications for memory management given two assumptions from the Cocoa object-ownership policy:

With these two assumptions in mind, let’s look at two possible implementations of a getter and setter accessor for an NSString instance variable named title. Listing 3-7 shows the first.

Listing 3-7  Implementing accessors for an object instance variable—good technique

- (NSString *)title {
    return title;
 }
 - (void)setTitle:(NSString *)newTitle {
    if (title != newTitle) {
        [title autorelease];
        title = [newTitle copy];
    }
 }

Note that the getter accessor simply returns a reference to the instance variable. The setter accessor, on the other hand, is busier. After verifying that the passed-in value for the instance variable isn’t the same as the current value, it autoreleases the current value before copying the new value to the instance variable. (Sending autorelease to the object is “more thread-safe” than sending it release.) However, there is still a potential danger with this approach. What if a client is using the object returned by the getter accessor and meanwhile the setter accessor autoreleases the old NSString object and then, soon after, that object is released and destroyed? The client object’s reference to the instance variable would no longer be valid.

Listing 3-8 shows another implementation of the accessor methods that works around this problem by retaining and then autoreleasing the instance variable value in the getter method.

Listing 3-8  Implementing accessors for an object instance variable—better technique

 - (NSString *)title {
    return [[title retain] autorelease];
 }
 - (void)setTitle:(NSString *)newTitle {
    if (title != newTitle) {
        [title release];
        title = [newTitle copy];
    }
}

In both examples of the setter method above (Listing 3-5 and Listing 3-7), the new NSString instance variable is copied rather than retained. Why isn’t it retained? The general rule is this: When the object assigned to an instance variable is a value object—that is, an object that represents an attribute such as a string, date, number, or corporate record—you should copy it. You are interested in preserving the attribute value, and don't want to risk having it mutate from under you. In other words, you want your own copy of the object.

However, if the object to be stored and accessed is an entity object, such as an NSView or an NSWindow object, you should retain it. Entity objects are more aggregate and relational, and copying them can be expensive. One way to determine whether an object is an value object or entity object is to decide whether you’re interested in the value of the object or in the object itself. If you’re interested in the value, it’s probably a value object and you should copy it (assuming, of course, the object conforms to the NSCopying protocol).

Another way to decide whether to retain or copy an instance variable in a setter method is to determine if the instance variable is an attribute or relationship. This is especially true for model objects, which are objects that represent the data of an application. An attribute is essentially the same thing as a value object: an object that is a defining characteristic of the object that encapsulates it, such as a color (NSColor object) or title (NSString object). A relationship on the other hand is just that: a relationship with (or reference to) one or more other objects. Generally, in setter methods you copy the values of attributes and you retain relationships. However, relationships have a cardinality; they can be one-to-one or one-to-many. One-to-many relationships, which are typically represented by collection objects such as NSArray instances or NSSet instances, may require setter methods to do something other than simply retain the instance variable. See Model Object Implementation Guide for details. For more on object properties as either attributes or relationships, see “Key-Value Mechanisms.”

Important: If you are implementing a getter accessor method to load the value of an instance variable value lazily—that is, when it is first requested—do not call the corresponding setter method in your implementation. Instead, set the value of the instance variable directly in the getter method.

If the setter accessors of a class are implemented as shown in either Listing 3-5 or Listing 3-7, to deallocate an instance variable in the class’s dealloc method, all you need to do is invoke the appropriate setter method with an argument of nil.

Key-Value Mechanisms

Several mechanisms with “key-value” in their names are fundamental parts of Cocoa: key-value binding, key-value coding, and key-value observing. They are essential ingredients of Cocoa technologies such as bindings, which automatically communicate and synchronize values between objects. They also provide at least part of the infrastructure for making applications scriptable—that is, responsive to AppleScript commands. Key-value coding and key-value observing are especially important considerations in the design of a custom subclass.

The term “key-value” refers to the technique of using the name of a property as the key to obtain its value. The term is part of the vocabulary of the object modeling pattern, which was briefly discussed in “Storing and Accessing Properties.” Object modeling derives from the entity-relationship modeling used for describing relational databases. In object modeling, objects—particularly the data-bearing model objects of the Model-View-Controller pattern—have properties, which usually (but not always) take the form of instance variables. A property can either be an attribute, such as name or color, or it can be a reference to one or more other objects. These references are known as relationships, and relationships can be one-to-one or one-to-many. The network of objects in a program form an object graph through their relationships with each other. In object modeling you can use key paths—strings of dot-separated keys—to traverse the relationships in an object graph and access the properties of objects.

Note: For a full description of object modeling, see “Object Modeling.”

Key-value binding, key-value coding, and key-value observing are enabling mechanisms for this traversal.

To make each property of a subclass compliant with the requirements of key-value coding, do the following:

To make your object KVO-compliant, simply ensure that your object is KVC-complaint if you are satisfied with automatic observer notification. However, you may chose to implement manual key-value observing, which requires additional work.

Further Reading: Read Key-Value Coding Programming Guide and Key-Value Observing Programming Guide to learn more about these technologies. For a summary of the Cocoa bindings technology, which uses KVC, KVO, and KVB, see “Bindings” in “Communicating With Objects.”

Object Infrastructure

If a subclass is well-designed, instances of that class will behave in ways expected of Cocoa objects. Code using the object can compare it to other instances of the class, discover its contents (for example, in a debugger), and perform similar basic operations with the object.

Custom subclass should implement most, if not all, of the following root-class and basic protocol methods:

If any of the ancestors of your class adopts a formal protocol, you must also ensure that your class properly conforms to the protocol. That is, if the superclass implementation of any of the protocol methods is inadequate for your class, your class should re-implement the methods.

Error Handling

It is axiomatic for any programming discipline that the programmer should properly handle errors. However, what is “proper” often varies by programming language, application environment, and other factors. Cocoa has its own set of conventions and prescriptions for handling errors in subclass code.

For further information about NSError objects, handling errors, and displaying error alerts, see Error Handling Programming Guide For Cocoa.

Resource Management and Other Efficiencies

There are all sorts of things you can do to enhance the performance of your objects and, of course, the application that incorporates and manages those objects. These procedures and disciplines range from multithreading to drawing optimizations and techniques for reducing your code footprint. You can learn more about these things by reading Cocoa Performance Guidelines and other performance documents.

Yet you can, before even adopting a more advanced performance technique, improve the performance of your objects significantly by following three simple, common-sense guidelines:

Functions, Constants, and Other C Types

Because Objective-C is a superset of ANSI C, it permits any C type in your code, including functions, typedef structures, enum constants, and macros. An important design issue is when and how to use these types in the interface and implementation of custom classes.

The following list offers some guidance on using C types in the definition of a custom class:

When the Class Is Public

When you are defining a custom class for your own use, such as a controller class for an application, you have great flexibility because you know the class well and can always redesign it if you have to. However, if your custom class will likely be a superclass for other developers—in other words, if it’s a public class—the expectations others have of your class mean that you have to be more careful of your design.

Here are few guidelines for developers of public Cocoa classes:



< Previous PageNext Page > Hide TOC


Last updated: 2007-10-31




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice