Originally posted on Carbon Five's Blog.
Have you ever wanted to introduce new functionality to base classes in the iOS SDK? Or just make them work a little differently? In order to do so, you must enter the wild and dangerous world of monkey-patching.
In this series of posts, we'll show how to monkey-patch in Objective-C through categories to add and change methods, to add new instance variables and properties, and introduce swizzling, a technique that allows us to extend and preserve existing functionality. TL;DR »
In the first post we showed how you can add or override methods with extensions. The second post focused on getting around restrictions on creating instance variables when adding properties to the classes being modified. This third and final post covers the technique of swizzling to override existing methods while preserving their behaviour.
The Problem with Simply Overriding
Back in the original post of this series we discussed that while categories certainly allowed us to bluntly override existing methods of any class in the iOS SDK this was strongly discouraged for two reasons:
- Other frameworks, both in the SDK or third-party, rely on the expected behavior of the original method. We would either have to re-implement that behavior in addition to the new functionality we wanted to introduce, or suffer side effects or more major errors due to its abscence.
- If multiple categories override the same method only the last one loaded wins out as load order is non-deterministic; its possible even the original method will remain unaffected.
Ideally we need a way to have the new method handle calls to the original mathod, but preserve the original method so as to be able to invoke it when needed, much like the Ruby on Rails'
alias_method_chain. Which is exactly what the swizzling technique provides.
The Solution: Swizzling
So far I've been incorrectly referring to the "methods" of a class. But in Objective-C, when you write the following:
[self presentViewController:mailController animated:YES completion:nil];
you are not actually invoking the
presentViewController:animated:completion: method but are instead sending a
presentViewController:animated:completion: message! How an object handles that message is determined at run-time by looking for a method under the message identifier or as it is commonly known as the selector. Normally this is the signature the method was declared under but it can be changed at run-time!
Swizzling is simply exchanging the implementation of two of a class' methods so that when a message is sent using the original selector of one it actually goes to the other. In general, whether for monkey-patching or other scenarios, this is accomplished by using a number of Objective-C Runtime functions:
Walking through the above code:
- First we build selectors (the
SELvariables) to identify the methods we are swizzling; in this case
- References to the methods the selectors point to (represented by the
Methoddata type) are then retrieved.
- We first attempt to add the implementation of the second method under the selector of the first method. We do this in case the first method doesn't truly exist, which is sometimes a possibility.
- If the method was added successfully we need SOMETHING under the selector of the second method, so we simply replace it with the first method's (empty) implementation.
- If we failed to add the method, the first method already exists, so we can simply exchange their implementations.
Now, for the purposes of "monkey-patching", we rarely want to exchange two existing methods. Instead we introduce a new method and then swizzle it with the original. Any calls to the original method will now be directed to the new implementation while the original implementation can be invoked under the name of the new method!
Let's look at…
Going back to the last post's scenario of extending
UIViewController with tour-guide functionality, suppose we want the tour guide information is to appear the first time a view is displayed to a user. The ideal place to have this happen is as part of the
viewWillAppear: call all controllers receive. Remember, we could spend time adding a sub-class for every controller variation we will use, but that could lead to unnecessary code bloat. But since
viewWillAppear: is critical to the UI life-cycle, we can't simply replace it. Hence, we need to swizzle it!
As a best practice when we swizzle a method, it's with a method with the same signature and a similar but unique name. In our case, we'll be creating
Note the call to
tourGuideWillAppear within its own implementation. You may be asking yourself "Isn't that going to result in an infinite recursive loop?"
But at what you have to remember is that at the point the method is invoked the swizzling will have already taken place. That seemingly recursive call will actually go to the original
viewWillAppear:. So remember, to invoke the original method implmentation, call it with the new method's name.
Swizzle on Load
Of course, we still have to at some point perform the swizzle. The first instinct would be to toss it into the
init method of a class, but this is incorrect because:
- We are not creating a class, but a category that will be mixed into a class whose
inityou probably don't want to override and
- Even if that was possible, it's something you only want to do once per class, and not in the per instance constructor!
Luckily, when the Objective-C Runtime loads a category, it invokes a class-level
load method. This is the perfect opportunity to perform the swizzle. We also wrap it with a
dispatch_once block call to ensure it only happens the one time:
And with that our swizzle is complete; when the framework calls
viewWillAppear: on any controller it will pass through our
tourGuideWillAppear:, triggering our custom tour-guide functionality. We can apply this same technique to extend any class method whether called by the framework or us directly, injecting new behavior while preserving any critical functionality.
We have achieved true monkey-patching!
DRYing it Up
Our example has us replacing one method in one class but already it makes for a lot of code. Imagine having to repeat that multiple times across many categories. Let us DRY it up by introducing, in an elegantly meta way, a swizzling category on the base
Now in the
+load of any category we simply call
swizzleInstanceSelector on the category it
self with the selectors of the methods we are swizzling. Here's the final
UIViewController+TourGuide category implementation to illustrate that and all the other monkey-patching techniques we have learned in this series:
- Use swizzling to preserve the original method behavior instead of simply overriding to avoid side-effects.
- Swizzling is simply the exchange of the identifiers of two methods so they point to each other's implementations.
- After swizzling you invoke the original method's implementation but calling the new implementations identifier.
- Swizzle in the
+loadmethod of your category.