Dynamic Tips & Tricks With Objective-C
posted on
Over the past couple of years there has been a large influx of Objective-C developers. Some are coming from dynamic languages like Ruby or Python, some from strongly typed languages like Java or C#, and of course there are those who are new to programming altogether. But this means that a large number of Objective-C developers haven't been using it for all that long. When you're new to a language, any language, you focus more on fundamentals like the syntax and core features. But it is often the more advanced, and sometimes less well used parts of a language that really makes it shine.
In this post I'm going to give a bit of a whirlwind tour of the Objective-C runtime, explaining what makes Objective-C so dynamic, and then go into various techniques that this dynamicness enables. Hopefully this will give you a better understanding of how and why Objective-C and Cocoa work the way they do.
The Runtime
Objective-C is a very simple language. It is 95% C. Language wise, it simply adds some extra keywords and syntax. What makes Objective-C truly powerful is its runtime. It is small, yet incredibly flexible. At the core of it is the principle of message sending.
Messages
If you're from a dynamic language like Ruby or Python, you likely know what messages are and can skip over the next paragraph. For those from other languages, read on.
When you think of invoking some code, you think of calling a method. In some languages, this allows for the compiler to perform extra optimisations and error checking as it is a direct and clear relationship between what is being called and what is being invoked. With message sending, this distinction is less clear. You don't need to know if an object will respond to a message in order to send it. You send off the message and it might get handled by the object. Or it could be passed along to another object. A message doesn't need to map to a single method, an object can potentially handle several messages that it funnels through to a single method implementation.
In Objective-C, this messaging is handled by the objc_msgSend() runtime function and its cousins. This function takes a target, a selector and a list of arguments. In fact at a conceptual level, the compiler simply converts all your message sends to calls to objc_msgSend(). For example, the following are functionally equivalent:
[array insertObject:foo atIndex:5];
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
Objects, Classes & Metaclasses
In most OOP languages you have the concepts of classes and of objects. Classes are blueprints from which objects are formed. However, in Objective-C, classes are themselves objects, which can respond to messages, which is why you have the distinction between class and instance methods. In concrete terms, an object in Objective-C is a struct, who's first member is called isa and is a pointer to its class. This is the definition from objc/objc.h
typedef struct objc_object {
Class isa;
} *id;
The object's class is what holds the list of methods it implements, as well as a pointer to the superclass. Now that makes sense for objects, but classes are also objects. This means a class also has an isa variable, so what does it point to? Well this is where the 3rd type comes in: metaclasses. A metaclass is to a class, what a class is to an object, i.e. it holds the list of methods it implements, as well as the super metaclass. To get a more complete understanding of how objects, classes and metaclasses fit together, read this post by Greg Parker which explains them incredibly well.
Methods, Selectors and IMPs
So we know that the runtime sends messages to objects. We also know that an object's class holds a list of its methods. So how do those messages map to methods and how are methods actually implemented?
The answer to the first question is pretty simple. The method list in a class is essentially a dictionary, where selectors are the keys and IMPs are the values. An IMP is simply a pointer to the method's implementation in memory. The really important thing though, is that this connection between the selector and the IMP is determined at runtime, not compile time. This allows us to play around with it, as we'll see later.
The IMP is usually a pointer to a function, where the first argument is of type id and called self, the second argument is of type SEL and is called _cmd and subsequent arguments are the method arguments. This is where the self and _cmd variables are declared when you use them inside a method. Below is an example of a method and a potential IMP for it:
- (id)doSomethingWithInt:(int)aInt {}
id doSomethingWithInt(id self, SEL _cmd, int aInt) {}
Other Runtime Functionality
Now we know about objects, classes, selectors, IMPs and message sending, what is the runtime actually capable of? Well it really serves two purposes:
- Create, modify and introspect classes and objects
- Message sending
We've already covered message sending, but this is only a small part of the functionality. All the runtime functions are prefixed by the item they work on. Below is a prefix and some of the more interesting functions they contain:
class
These are functions for modifying and introspecting classes. Functions like class_addIvar
, class_addMethod
, class_addProperty
and class_addProtocol
allow for building up classes. class_copyIvarList
, class_copyMethodList
, class_copyProtocolList
and class_copyPropertyList
give all the items of each type in a class, whereas class_getClassMethod
, class_getClassVariable
, class_getInstanceMethod
, class_getInstanceVariable
, class_getMethodImplementation
and class_getProperty
return individual items that match the supplied name.
There are also some more general introspection functions which are often wrapped by Cocoa methods, such as class_conformsToProtocol
, class_respondsToSelector
and class_getSuperclass
. Finally you can use class_createInstance
to create an object from a class.
ivar
These functions let you get the name, memory offset and Objective-C type encoding of an ivar.
method
These functions largely allow for introspection, such as method_getName
, method_getImplementation
, method_getReturnType
etc. There are some modification functions though, including method_setImplementation
and method_exchangeImplementations
, which we will cover later.
objc
These are general runtime functions and are in effect the root of the heirarchy. You have the various objc_msgSend
functions for handling the core message sending functionality. There are also objc_getAssociatedObject
, objc_setAssociatedObject
and objc_removeAssociatedObjects
, which obviously handle associated references. As the root of the runtime functions, you can get the classes and protocols in the runtime using objc_copyProtocolList
, objc_getClassList
, objc_getProtocol
, objc_getClass
etc,
Finally we have the methods for creating classes. objc_allocateClassPair
creates the class and metaclass pair, allowing you to add methods, ivars and protocols. objc_registerClassPair
puts these into the runtime, locking them down somewhat so you can only add new Methods.
object
Once you have your objects you can perform some introspection and modification on them directly. You can get and set the ivar values of an object. Using object_copy
and object_dispose
you can perform copies and free the object's memory. Most interesting though is the ability not only to get a class, but to use object_setClass
to change an object's class at runtime. We'll see how this is useful later on.
property
Properties store quite a bit of data with them. On top of getting the name, you can use property_getAttributes
to find out things such as a property's return type, whether it is atomic or non-atomic, the memory management style used, custom getter and setter names, whether the property's implementation is dynamically implemented, the name of the ivar backing the property and whether it is a weak reference.
protocol
Protocols are a bit like classes, but cut down, and the runtime methods are the same. You can get the method, property and protocol lists of a protocol and check whether it conforms to other protocols.
sel
Finally we have some functions for dealing with selectors, such as getting the name, registering a selector name and checking selector equality
Now that we have a grasp of what the Objective-C runtime can do, and how it does some of it, lets look at some of the really interesting dynamic programming techniques these enable.
Classes And Selectors From Strings
One of the most basic dynamic things we can do is to generate classes and selectors from strings. We do this by using the NSClassFromString
and NSSelectorFromString
functions in Cocoa. It's pretty easy to do:
Class stringclass = NSClassFromString(@"NSString")
That give us a class that we can send messages to. So next we could do:
NSString *myString = [stringclass stringWithString:@"Hello World"];
So why do this? Surely it's easier to use the class directly? Well usually it is, but there are some very useful cases where we can use these functions. The first is for testing whether a class exists. NSClassFromString
will return nil if there isn't a class in the runtime that matches the string. For example, you could check whether you are on iOS 4.0 by checking whether NSClassFromString(@"NSRegularExpression")
is nil or not.
The other way they can be used is choosing the class or method to use based on some input. For example, say you are parsing some data. Each data item has a value string to parse and a type it represent (String, Number, Array). You could handle all those in one method, but you could also handle them in multiple methods. One way to do this is to read the type and use an if statment to call the corret method. The other is to use the type to generate a selector and call it that way. Here are the two different approaches:
- (void)parseObject:(id)object {
for (id data in object) {
if ([[data type] isEqualToString:@"String"]) {
[self parseString:[data value]];
} else if ([[data type] isEqualToString:@"Number"]) {
[self parseNumber:[data value]];
} else if ([[data type] isEqualToString:@"Array"]) {
[self parseArray:[data value]];
}
}
}
- (void)parseObjectDynamic:(id)object {
for (id data in object) {
[self performSelector:NSSelectorFromString([NSString stringWithFormat:@"parse%@:", [data type]]) withObject:[data value]];
}
}
- (void)parseString:(NSString *)aString {}
- (void)parseNumber:(NSString *)aNumber {}
- (void)parseArray:(NSString *)aArray {}
As you can see, you can replace 7 line if statement with a single line. And the benefit is that if you need to support a new type in the future, you just add the new method, rather than have to remember to add an extra else if to your main parse method.
Method Swizzling
Earlier on we talked about how methods are made up of two components. The selector, which is an identifier for a method, and the IMP, which is the actual implementation that is run. One of the key things about this separation is that a selector and IMP link can be changed. One IMP can have multiple selectors pointing to it for example.
Another thing you can do is Method Swizzling. This is where you take two methods and swap their IMPs. Again, you may be asking "why do I want to do something like that?". Well lets look at the two obvious ways of extending a class in Objective-C. The first is subclassing. This allows you to override a method and call the original implementation, but it means that you have to use instances of this subclass, which can cause problems if you're subclassing a Cocoa class and Cocoa returns it (eg NSArray). In those cases you want to add a method to NSArray itself, which is where categories come in. For 99% of cases this is great, but you cannot call the original implementation if you override a method.
Method Swizzling lets you have your cake and eat it. You can override a method without subclassing AND call the original implementation. You do this by adding a new method, usually via a category (but it can be in a completely different class). You then exchange the implementations, using the method_exchangeImplementations()
runtime function. So lets look at a concrete example to this, and override the addObject:
method on NSMutableArray to log any objects that are added.
#import
@interface NSMutableArray (LoggingAddObject)
- (void)logAddObject:(id)aObject;
@end
@implementation NSMutableArray (LoggingAddObject)
+ (void)load {
Method addobject = class_getInstanceMethod(self, @selector(addObject:));
Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));
method_exchangeImplementations(addObject, logAddObject);
}
- (void)logAddObject:(id)aobject {
[self logAddObject:aObject];
NSLog(@"Added object %@ to array %@", aObject, self);
}
@end
So the first thing to note is that we're exchanging implementations in the load
method. This method is called on every class and category only once, as it is loaded into the runtime. If you're wanting to exchange implementations for the entire lifetime of a class, this is the best place to put the code. If you only want to do this temporarily, you can put it wherever works best.
The second thing to note is the apparent infinite recursion in logAddObject:
. This is one of the disadvantages of Method Swizzling, in that it can mess with your brain a bit if you forget methods are swizzled. The important thing to remeber is this: everything between the { } is the IMP, everything before is effectively the Selector. Normally the Selector and IMP match up like in code, but if you swizzle, then the Selector actually points to another IMP. This diagram will hopefully clear it up.
Dynamic Subclassing/isa Swizzling
As we covered when looking at the runtime functions, you are able to create new Classes from scratch at runtime. This feature isn't used too often, but it is very powerful when it is used. It can allow you to create a new subclass, with some additional functionality.
But what use would such a subclass be? Well it's important to remember the key thing about an object in Objective-C: it has a variable called isa which is a pointer to its class. This variable can be changed, effectively changing the class of an object without needing to recreate it. Now it isn't quite so simple as you can't really mess around with ivar layout, but you can add new ivars and new methods to an object. To change the class of an object, you just do the following:
object_setClass(myObject, [MySubclass class])
An example of where this is used is Key Value Observing. When you start observing an object, Cocoa creates a subclass of the object's class, and then sets the object's isa pointer to the new subclass. For a more complete explanation, check out this Friday Q&A by Mike Ash.
Dynamic Method Resolution
We've so far looked at swapping things arround and dealing with things that are there. What happens when you send a message to an object that doesn't respond to it? The obvious answer would be "it breaks". While true in most cases, there are actually a series of steps that Cocoa and the runtime go through that allow you to perform some tricks, before it finally gives up.
The first of these steps is Dynamic Method Resolution. Usually, when resolving a method, the runtime looks for a method matching a selector and invokes it. Sometimes, you don't want a method to be created until runtime, as maybe there is some information you need at runtime before it can be made. Either way, the first time this method is needed, you generally want to be told so you can provide an implementation.
To do this you need to override the +resolveInstanceMethod:
and/or the +resolveClassMethod:
methods. These methods get called and the selector for the required method is passed in, allowing you to add the method to the class. If you do add a method you should be sure to return YES, so that the runtime doesn't move on to the next step. A simple implemention would be:
+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
if (aSelector == @selector(myDynamicMethod)) {
class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSelector];
}
So where is this currently used in Cocoa? Well Core Data uses this quite a bit. NSManagedObjects have properties added to them at runtime for getting and setting attributes and relationships. Now these could be generated in code prior to compilation, but then you're hit with the problem of, what if the model is different at runtime? Afterall, the model can be changed.
Message Forwarding
If the resolve method returns NO, the runtime moves onto the next step: Message Forwarding. Whereas Dynamic Method Resolution is about creating methods at runtime, Message Forwarding is about re-routing messages. There are two main uses for this. The first is to pass a message on to another object that can handle it. The second is to route several messages to a single method.
Message Forwarding is a two step process (at least on the latest OS versions). Firstly, the runtime calls -forwardingTargetForSelector:
on the object. If you're only wanting to pass a message to another object as is then you should use this method, as it is much more efficient. If you're wanting to modify the message prior to forwarding however, you want to use -forwardInvocation:
. In this case the runtime packages up the message in an NSInvocation and sends it to you to handle. After you have handled the NSInvocation object, you simply call -invokeWithTarget:
on it, passing in the new target. The runtime will sort out passing the return type back to the calling object.
There are several places Message Forwarding is used in Cocoa, but the two key places are Proxies and the Responder Chain. An NSProxy is a lightweight class, who's intended purpose is to forward messages on to a real object. This is useful for if you want to lazily load part of an object graph, or if you're using something like Distributed Objects where the object you actually want to call is potentially on another computer. It is also used by NSUndoManager, but in this case to intercept messages to invoke later, rather than for forwarding them to something else.
The Responder Chain is how Cocoa deals with sending events and actions to the correct object. Take for example, performing a copy by hitting Cmd-C. This sends a -copy:
message down the Responder Chain. It initially goes to the First Responder, usually the active UI element. If that doesn't handle it the message is forwarded to the -nextResponder
. It continues down the chain until it finds an object that can handle the message, or until it reaches the end of the chain, in which case it causes an error.
Using Blocks As Method IMPs
iOS 4.3 brings a lot of cool new runtime functions. On top of increased powers with properties and protocols comes a new set of functions beginning with the prefix imp. Normally an IMP is a pointer to a function where the first two arguments are an object (self) and a selector (_cmd). iOS 4.0 and Mac OS X 10.6 gave us these rather nifty new things called blocks though. These imp prefixed functions allow us to use a Block as an IMP for a method, specifically using the imp_implementationWithBlock()
function. Here's a quick snippet showing how you can use this to add a method using a block.
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) {
NSLog(@"Hello %@", string);
});
class_addMethod([MYclass class], @selector(sayHello:), myIMP, "v@:@");
For more information on how it works, check out this great post by Bill Bumgarner.
As you can see, there is an awful lot of power in Objective-C. While it seems simple on the surface, it provides a lot of flexibility, which can enabled a lot of cool possibilities. The key advantage a highly dynamic language has is the ability to do a lot of stuff without having to actually extend the core language. A good example is Key Value Observing, which offers an elegant API that works with existing code, without the need for new language features or to heavily modify existing code.
Hopefully this post has given you a deeper understanding of Objective-C and opened your eyes to some of the possibilities it allows when designing your applications.