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.
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:
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.
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:
SELvariables) to identify the methods we are swizzling; in this case
Methoddata type) are then retrieved.
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.
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:
inityou probably don’t want to override and
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!
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:
+loadmethod of your category.
Rudy’s fascination with computer programming began at age 10 when he mistakenly picked up the Micro-Adventure book Space Attack; he thought it was going to be about Star Wars. That happy accident led him to graduate from McGill University in Computer Science and start a 12 year career in software development playing with a wide range of technology; everything from web applications to cryptology to NoSQL.