Introduction
Objective-C is an amazing language. So many possibilities with dynamic runtime typing and built in polymorphism. But one of my favorite features of mutable runtime objects is the ability to Method Swizzle, which is effectively the ability to add a method or exchange 2 methods. The usefulness of method swizzling is vast, such as adding a method to an older SDK that the newer SDK has or perhaps changing the functionality of an existing method. In this post, I'll just give a quick implementation for method swizzling that can be reused over and over.
Swizzling Methods for the Win!
Method swizzling is something we can accomplish with the Objective-C runtime which has many powerful C functions in the runtime header.
#include <objc/runtime.h>
Now before we start coding, it's important to have at least a high level understanding of how Objective-C methods work in the runtime. When the objective-c runtime loads from a binary being executed, all the objects that are used are templatized - that is to say they have a tier structure defined in memory with things such as what instance variables an instance has and the names of those instances (for KVC) and the instance methods and class methods a class has and the associated selector (SEL
) used to call a particular method, which is effectively just a function that has 2 leading arguments: self
, the pointer to the instance being executed on; and _cmd
the selector that is executing. This association of selectors and functions is maintained in a map for the object template. This means that if we can change the function that a selector is associated with in the map to a new function, we've got the amazing ability to change the behavior of objects at runtime. Insane right!
Things that we will want to be able to do easily:
- If a method exists, we want to be able to exchange it with a new implementation that we can define in a category
- If a method doesn't exist, we want to be able to set our implementation as the method
- We want to do all of this for both instance methods AND for class methods
The biggest reason we want our functions to both be able to add the method if missing and replace the method if it exists is that when it comes to inheritance, it is very possible that you might want to swizzle out a method on a subclass that is implemented in a super class, in which case that class instance would NOT have the method for swizzling, but its parent would. It takes great care from the programmer to ensure that the method implementations are savvy enough to handle either scenario though.
Let's write out the function declarations for what we'd like to see and then tackle them one at a time.
BOOL SwizzleInstanceMethods(Class class, SEL dstSel, SEL srcSel);
BOOL SwizzleClassMethods(Class class, SEL dstSel, SEL srcSel);
So you can see the three necessary arguments for method swizzling, the Class to modify, the destination selector and the source selector. Knowing the Objective-C runtime functions that can accomplish this yields a straightforward solution. I'll address each, starting with instance method swizzling.
BOOL SwizzleInstanceMethods(Class class, SEL dstSel, SEL srcSel)
{
Method dstMethod = class_getInstanceMethod(class, dstSel);
Method srcMethod = class_getInstanceMethod(class, srcSel);
if (!srcMethod)
{
@throw [NSException exceptionWithName:@"InvalidParameter"
reason:[NSString stringWithFormat:@"Missing source
method implementation for swizzling! Class %@, Source: %@,
Destination: %@", NSStringFromClass(class),
NSStringFromSelector(srcSel), NSStringFromSelector(dstSel)]
userInfo:nil];
}
if (class_addMethod(class, dstSel,
method_getImplementation(srcMethod), method_getTypeEncoding(srcMethod)))
{
class_replaceMethod(class, dstSel,
method_getImplementation(dstMethod), method_getTypeEncoding(dstMethod));
}
else
{
method_exchangeImplementations(dstMethod, srcMethod);
}
return (srcMethod == class_getInstanceMethod(class, dstSel));
}
First, we will get the destination and source Method
s. We use the objc
function class_getInstanceMethod
to accomplish this. Method
is simply a pointer to a struct
with all of the necessary method information disconnected from the selector used to call it.
Next, we validate that there is a source method to swizzle. If the user hasn't implemented the source method as a category method, then there's no point in swizzling, so we'll throw an exception.
Then we get into the actual act of Swizzling. The logic here is like this:
- If the destination method doesn't exist, make the destination method be the source method
- AND replace the old source method with the original destination method (which will be
NULL
and effectively removes the old source method)
- If the destination method already existed, exchange the source and destination methods so they are both still in the runtime, but effectively have traded what selector they respond to
Finally, we want to validate success by ensuring the source method we'd set out to swizzle to respond to the destination selector does in fact respond to it with a simple pointer comparison.
Onto class method swizzling:
BOOL SwizzleClassMethods(Class class, SEL dstSel, SEL srcSel)
{
Class metaClass = object_getClass(class);
if (!metaClass || metaClass == class) {
@throw [NSException exceptionWithName:@"InvalidParameter"
reason:[NSString stringWithFormat:
@"%@ does not have a meta class
to swizzle methods on!",
NSStringFromClass(class)]
userInfo:nil];
}
return SwizzleInstanceMethods(metaClass, dstSel, srcSel);
}
Wait, that seems too easy! Well, the nice thing about class methods is that they are merely instance methods on the Class instance of a Class, or the MetaClass. So we just ensure that the class passed in has a metaClass that we can swizzle, and we're good to go!
Now one thing we could do is have a 3rd function that is effectively the same implementation as SwizzleInstanceMethods
but with 1 additional parameter: BOOL isMetaClass
. With that function, call it SwizzleInstanceMethodsInternal
, we could validate that the provided class was either the MetaClass (has a MetaClass equal to itself) or a normal class that has an actual MetaClass. From there, we could have our two functions call it with isMetaClass either YES or NO, but I'll leave that as an exercise for anyone wanted to go to that level.
There we go! We can now easily swizzle one method for another in order to port functionality of a method forward or backward - or to overwrite the functionality of existing methods. A word of warning, with this powerful tool in place, one must take great responsibility as modifying the object behavior can be dangerous if care is not taken.
I will follow this post up with a post to swizzle the description method on the NSData class so that it can behave in a configurable manner.
Find this code on github