Part 1 of this series examined the history and philosophy of Objective-C. This article starts investigating some of the concrete syntax. As you might expect, this involves defining and using classes.
Classes Aren't Special
In Smalltalk, classes are just objects with a few special features. The same is true in Objective-C. A class is an object. It responds to messages just as an object does. Both Objective-C and C++ split object allocation and initialization:
- In C++, object allocation is done via the new operator. In Objective-C, it's done by sending the class an alloc message—which, in turn, calls malloc() or an equivalent.
- Initialization in C++ is done by calling a function with the same name as the class. Objective-C doesn't distinguish between initialization methods and other methods, but by convention the default initialization method is init.
When you declare a method to which instances respond, the declaration starts with -, and + is used for class methods. It's common to use these prefixes for messages in documentation, so you would say +alloc and -init, to indicate that alloc is sent to a class and then init is sent to an instance.
Classes in Objective-C, as in other object-oriented languages, are object factories. Most classes don't implement +alloc themselves; instead, they inherit it from the superclass. In NSObject, the root class in most Objective-C programs, the +alloc method calls +allocWithZone:. This takes an NSZone as an argument, a C structure containing some policy for object allocations. Back in the 1980s, when Objective-C was used in NeXTSTEP to implement device drivers and pretty much all of the GUI on machines with 8MB of RAM and 25 MHz CPUs, NSZone was very important for optimization. At the moment, it's more or less completely ignored by Objective-C programmers. (It has potential to become more relevant as NUMA architectures become more common, however.)
One of the nice features of the fact that object creation semantics are defined by the library and not the language is the idea of a class cluster. When you send an -init message to an object, it returns an initialized object. This may be the object to which you sent the message (and usually is), but it doesn't have to be. The same is true of other initializers. It's possible to have specialized subclasses of a public class that are more efficient on different data.
One common trick for implementing this feature is called isa-swizzling. As I said earlier, Objective-C objects are C structures whose first element is a pointer to the class. This element is accessible, just as any other instance variables are; you can change the class of an object at runtime simply by assigning a new value. Of course, if you set the class of an object to something that has a different layout in memory, things will go horribly wrong. However, you can have a superclass that defines the layout and then a set of subclasses that define the behavior; for example, this technique is used by the standard string class (NSString), which has various instances for different width text encodings, for static strings, and so on.
Because classes are objects, you can do pretty much anything with them that you would with objects. For example, you can put them in collections. I use this format fairly often when I have a set of incoming events that need handling by instances of different classes. You could create a dictionary mapping the event names to the classes, and then instantiate a new object for each incoming event. If you do this in a library, it allows users of the code to register their own handlers easily.