If you’re an Objective-C developer you can get a lot done without understanding the C roots in Objective-C or the object model employed by Objective-C. In fact, trying to learn the object model can be intimidating and confusing because some peculiar complexities.
Even if it’s a little complex, it’s a worthy endeavor to learn the Objective-C object model because you may end up needing to interact with the Objective-C runtime someday, and understanding the object model will be very useful in that situation.
Grab a cup of tea or coffee and follow along with me while we explore the Objective-C object model.
Objects are instances of a class
Sure, this may sound obvious, but it’s worth taking a few seconds to really consider what it means when we say that an object is an instance of a class. Take the following code for example:
NSString *name =
[NSString stringWithFormat:@"%@", @"Peter"];
This is a rather contrived example but it’s important to demonstrate
some of the moving parts in the Objective-C runtime. When the code
above is executed we end up with a variable called name
that is an
instance of the NSString
class. Please consider the following:
-
Objects (instances of a class) are used to hold state.
-
An object’s state is held in its instance variables. For example, the
name
object above owns some memory for storing the bytes that make up the string “Peter”. -
The
name
object also contains a pointer back to its class (NSString
) as part of its state. -
In Objective-C objects don’t contain any behavior, only state.
-
Classes contain behavior and do not contain any state. The behavior they hold is the methods that their instances can respond to.
-
Let’s repeat that once again for clarity. Objects are containers that store state (instance variables) and classes are containers that store behavior (methods).
To make this more concrete take a look at the following diagram:
The blue box represents the name
variable (an instance of the
NSString
class). Using the diagram above let’s look at what happens
if you send the length
message to the name
variable:
NSUInteger len = [name length];
-
The first thing that Objective-C does is ask the
name
variable which class it was instantiated from. Since all the methods are held in the class, that’s where it will start looking for thelength
method. -
Since the class of
name
object isNSString
, Objective-C goes to theNSString
class and searches for thelength
method. -
If Objective-C finds the
length
method in theNSString
class it will stop searching and execute the method that it found. Otherwise it will continue searching the inheritance hierarchy for the method.
I should note that the process I’m describing has been simplified. I’m not considering other places that methods could be stored (e.g. Objective-C categories).
Classes are really objects too
Given that a class holds methods for its instances, you might be
wondering where class methods are stored. Let’s look at the code that
created the name
variable again:
NSString *name =
[NSString stringWithFormat:@"%@", @"Peter"];
In the code above, the stringWithFormat:
message was sent to the
NSString
class. Can classes receive messages, and if so where are
those methods kept? In this case, the Objective-C syntax is covering
something up, it’s hiding the fact that NSString
is also an object.
If NSString
is an object, that means that it must have been
instantiated from a class that is holding its methods. This is true,
but before we dig into it let’s look at some more code:
@interface Person : NSObject {}
+ (void) aClassMethod;
- (void) anInstanceMethod;
@end
You’ve probably seen this many, many times. But what is really going on?
-
A new class is being declared, the
Person
class. -
This class will be used to hold methods for instances.
-
A second class is also being created behind the scenes. Unfortunately this additional class has the same name (
Person
) but is referred to as thePerson
metaclass. -
The
Person
metaclass holds the class methods for thePerson
class.
This can be very confusing because the same name is being used over
and over again. Going back to our name
variable, we can say the
following:
-
The
name
object is an instance of theNSString
class. -
NSString
is an object instantiated from theNSString
metaclass. -
If you send the
length
message to thename
object, Objective-C will go look for that method in theNSString
class. -
If you send the
stringWithFormat:
message to theNSString
object, Objective-C will go look for that method in theNSString
metaclass.
Here’s another diagram:
This time the blue box represents the NSString
object. Here’s the
creation of the name
object again:
NSString *name =
[NSString stringWithFormat:@"%@", @"Peter"];
When the stringWithFormat:
message is sent to the NSString
object
Objective-C will:
-
Ask the
NSString
object for it’s class, which is theNSString
metaclass. -
It will then start searching the
NSString
metaclass for thestringWithFormat:
method. If it doesn’t find the method there it will continue up the inheritance hierarchy.
Naturally, since the NSString
class inherits from the NSObject
class, the NSString
metaclass inherits from the NSObject
metaclass.
Wacky NSObject inheritance
If you’ve made it this far you can rest assured that you know enough
about the Objective-C object model to know a lot more about what’s
going on in your code at run time. There’s just one more thing that
you may want to know: Why does it look like the NSObject
metaclass
inherits from the NSObject
class? Because it does.
Consider the following:
-
The
NSObject
class contains methods for its instantiated objects. -
The
NSObject
metaclass contains methods for theNSObject
class (i.e.NSObject
class methods). -
The
NSObject
metaclass inherits from the (non-meta)NSObject
class. -
Therefore, any instance methods defined for
NSObject
will also be in the search path as class methods for any Objective-C class.
It’s a bit wacky, but if you look at the diagram above it should start to make sense.