Reader beware: this post is part of the older "Objective-C era" on Cocoa with Love. I don't keep these articles up-to-date so the code may be broken or superceded by newer APIs. There's some good information but there's also some opinions I no longer endorse – keep a skeptical mind. Read "A new era for Cocoa with Love" for more.
In this post, I look at one of the stranger concepts in Objective-C — the meta-class. Every class in Objective-C has its own associated meta-class but since you rarely ever use a meta-class directly, they can remain enigmatic. I'll start by looking at how to create a class at runtime. By examining the "class pair" that this creates, I'll explain what the meta-class is and also cover the more general topic of what it means for data to be an object or a class in Objective-C.
Creating a class at runtime
The following code creates a new subclass of
NSError at runtime and adds one method to it:
The method added uses the function named
ReportFunction as its implementation, which is defined as follows:
On the surface, this is all pretty simple. Creating a class at runtime is just three easy steps:
- Allocate storage for the "class pair" (using
- Add methods and ivars to the class as needed (I've added one method using
- Register the class so that it can be used (using
However, the immediate question is: what is a "class pair"? The function
objc_allocateClassPair only returns one value: the class. Where is the other half of the pair?
I'm sure you've guessed that the other half of the pair is the meta-class (it's the title of this post) but to explain what that is and why you need it, I'm going to give some background on objects and classes in Objective-C.
What is needed for a data structure to be an object?
Every object has a class. This is a fundamental object-oriented concept but in Objective-C, it is also a fundamental part of the data. Any data structure which has a pointer to a class in the right location can be treated as an object.
In Objective-C, an object's class is determined by its
isa pointer. The
isa pointer points to the object's Class.
In fact, the basic definition of an object in Objective-C looks like this:
What this says is: any structure which starts with a pointer to a
Class structure can be treated as an
The most important feature of objects in Objective-C is that you can send messages to them:
This works because when you send a message to an Objective-C object (like the
NSCFString here), the runtime follows object's
isa pointer to get to the object's
NSCFString class in this case). The
Class then contains a list of the
Methods which apply to all objects of that
Class and a pointer to the
superclass to look up inherited methods. The runtime looks through the list of
Methods on the
Class and superclasses to find one that matches the message selector (in the above case,
NSString). The runtime then invokes the function (
IMP) for that method.
The important point is that the
Class defines the messages that you can send to an object.
What is a meta-class?
Now, as you probably already know, a
Class in Objective-C is also an object. This means that you can send messages to a
In this case,
defaultStringEncoding is sent to the
This works because every
Class in Objective-C is an object itself. This means that the
Class structure must start with an
isa pointer so that it is binary compatible with the
objc_object structure I showed above and the next field in the structure must be a pointer to the
nil for base classes).
As I showed last week, there are a couple different ways that a
Class can be defined, depending on the version of the runtime you are running, but yes, they all start with an
isa field followed by a
However, in order to let us invoke a method on a
isa pointer of the
Class must itself point to a
Class structure and that
Class structure must contain the list of
Methods that we can invoke on the Class.
This leads to the definition of a meta-class: the meta-class is the class for a
- When you send a message to an object, that message is looked up in the method list on the object's class.
- When you send a message to a class, that message is looked up in the method list on the class' meta-class.
The meta-class is essential because it stores the class methods for a
Class. There must be a unique meta-class for every
Class because every
Class has a potentially unique list of class methods.
What is the class of the meta-class?
The meta-class, like the
Class before it, is also an object. This means that you can invoke methods on it too. Naturally, this means that it must also have a class.
All meta-classes use the base class' meta-class (the meta-class of the top
Class in their inheritance hierarchy) as their class. This means that for all classes that descend from
NSObject (most classes), the meta-class has the
NSObject meta-class as its class.
Following the rule that all meta-classes use the base class' meta-class as their class, any base meta-classes will be its own class (their
isa pointer points to themselves). This means that the
isa pointer on the
NSObject meta-class points to itself (it is an instance of itself).
Inheritance for classes and meta-classes
In the same way that the
Class points to the superclass with its
super_class pointer, the meta-class points to the meta-class of the
super_class using its own
As a further quirk, the base class' meta-class sets its
super_class to the base class itself.
The result of this inheritance hierarchy is that all instances, classes and meta-classes in the hierarchy inherit from the hierarchy's base class.
For all instances, classes and meta-classes in the
NSObject hierarchy, this means that all
NSObject instance methods are valid. For the classes and meta-classes, all
NSObject class methods are also valid.
All this is pretty confusing in text. Greg Parker has put together an excellent diagram of instances, classes, meta-classes and their super classes and how they all fit together.
Experimental confirmation of this
To confirm all of this, let's look at the output of the
ReportFunction I gave at the start of this post. The purpose of this function is to follow the
isa pointers and log what it finds.
To run the
ReportFunction, we need to create an instance of the dynamically created class and invoke the
report method on it.
Since there is no declaration of the
report method, I invoke it using
performSelector: so the compiler doesn't give a warning.
ReportFunction will now traverse through the
isa pointers and tell us what objects are used as the class, meta-class and class of the meta-class.
Getting the class of an object: the
object_getClassto follow the
isapointers because the
isapointer is a protected member of the class (you can't directly access other object's
ReportFunctiondoes not use the
classmethod to do this because invoking the
classmethod on a
Classobject does not return the meta-class, it instead returns the
[NSString class]will return the
NSStringclass instead of the
This is the output (minus
NSLog prefixes) when the program runs:
Looking at the addresses reached by following the
isa value repeatedly:
- the object is address
- the class is address
- the meta-class is address
- the meta-class's class (i.e. the
NSObjectmeta-class) is address
NSObjectmeta-class' class is itself.
The value of the addresses is not really important except that it shows the progress from class to meta-class to
NSObject meta-class as discussed.
The meta-class is the class for a
Class object. Every
Class has its own unique meta-class (since every
Class can have its own unique list of methods). This means that all
Class objects are not themselves all of the same class.
The meta-class will always ensure that the
Class object has all the instance and class methods of the base class in the hierarchy, plus all of the class methods in-between. For classes descended from
NSObject, this means that all the
NSObject instance and protocol methods are defined for all
Class (and meta-class) objects.
All meta-classes themselves use the base class' meta-class (
NSObject meta-class for
NSObject hierarchy classes) as their class, including the base level meta-class which is the only self-defining class in the runtime.