Objective-C Internals: Class Architecture
Objective-C has an unique class architecture where classes are objects. Its elegant design enables dynamic method dispatch for all object types through the use of a compiler generated metaclass.
Objective-C, like many popular languages, uses a class-based style of object-oriented programming. For the purposes of discussing Objective-C’s class architecture, we’ll focus on class behavior (methods and properties) and set aside class state (i.e., instance variables, which are covered in this post).
The following six lines of code are all we need to explore Objective-C’s class architecture:
@interface MyObject: NSObject
+ (void)classMethod;
- (void)instanceMethod;
@end
MyObject *object = [[MyObject alloc] init];
Method Dispatch
Line 6 instantiates the class MyObject and assigns the new object instance to the variable object. The new object instance has its own state and can respond to messages (i.e. method calls) like -instanceMethod and -init.
Objective-C uses dynamic dispatch[1] for each message send (i.e. for each method call). The runtime finds the method implementation by looking up the selector (i.e. method name) in the class object referenced by the instance’s isa variable. (The first instance variable in all Objective-C objects is the isa pointer, which is automatically inserted by the compiler and initialized by the runtime.)
The use of the term class object in the previous paragraph was intentional: in Objective-C, classes are also objects! This ingenious design is the basis for the class methods language feature (e.g. calling [NSObject alloc] or [MyObject classMethod]): it enables class methods to be fully polymorphic (i.e. a subclass can override a class method), and it erases any runtime distinction between class methods and instance methods (both class and instance methods are dispatched via objc_msgSend).
Because classes are objects, they too have an isa pointer, which references the metaclass. The metaclass provides the selector-to-class method implementation map for the class object in the same way the class object provides the selector-to-instance method implementation map for a class instance (i.e. object).
Inheritance
Any class or metaclass only provides selector-to-method implementation mappings for methods implemented by the class. In the code example above, the class object for MyObject has a mapping for instanceMethod and the metaclass for MyObject has a mapping for classMethod.
MyObject also responds to +alloc and -init, which are implemented in NSObject. Each class object (including each metaclass) has a reference to its super class. When resolving a selector, if the class/metaclass object does not define a mapping, the runtime searches the next super class for a definition. (The runtime will throw an exception if no definition is found at the end of the super class chain, though the runtime also provides escape hatches to handle the scenario.)
Architecture Diagram
The following diagram illustrates the Objective-C class design discussed above:
-
The
objectinstance has anisavariable that points to theMyClassclass object. -
The
MyClassclass object has:-
An
isavariable that points to theMyClassmetaclass. -
A
supervariable that points to theNSObjectclass object.
-
-
The
MyClassmetaclass has:-
An
isavariable that points to theNSObject(root object) metaclass. -
A
supervariable that points to theNSObjectmetaclass.
-
The diagram also shows a few points not captured by the discussion above:
-
The
isavariable of each metaclass points to the metaclass of the root object, including the root object metaclass itself. It makes sense that theisaisn’tnil(an object has to have some type), but I’m not sure why all metaclasses are of the type of the root metaclass as opposed to, say, a type provided by the runtime. I suppose, though, it doesn’t actually matter as the metaclass itself never receives messages. -
The super class of the root metaclass is the root class object. I was surprised to learn this when researching for the diagram. It’s not immediately clear to me why this link exists—I thought its super class would be
nil.
If anyone has insight into the rationale for these design artifacts, please reach out and let me know!