Monkey-Patching iOS with Objective-C Categories Part I: Simple Extensions and Overrides
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 bit differently? In order to do so, you must enter the wild and dangerous world of monkey-patching.
Monkey-patching is extending or modifying the behavior of code at runtime without changing its original source code. You can monkey-patch any code, it doesn’t matter whether it’s your own code or not. This is distinctly different than traditional sub-classing because you are not creating a new class, instead, you are reopening an existing class and changing its behavior.
Monkey-patching is possible in Objective-C by using categories. In fact, the definition of a category practically matches that of monkey-patching:
A category allows you to add methods to an existing class, even to one for which you do not have the source.
In this series of posts, we’ll use 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 ยป
Category Basics
To modify an existing class specify the category in both its interface and implementation definitions:
Interface Definition
Implementation
Adding Simple Methods
The most basic usage of categories is to add a new method to an existing class.
Suppose in our application we want to output dates relative to the current time, e.g., “13 minutes ago”, “4 hours ago”, “just now”, etc. Traditional object-oriented solutions would have us introducing a new class that either extends NSDate
(e.g., creating a RelativeDescriptionDate
subclass with a timeAgoInWords
instance method) or is a standalone helper/utility class (e.g., [NSDateHelper timeAgoInWordsFromDate:myDate]
).
But with categories, we can reopen the NSDate
class and simply add a new instance method:
NSDate+Formatting.h
NSDate+Formatting.m
Now every NSDate
object will have the new method available to it. The following code:
Will print out the following on the console:
The Dangers of Simply Overriding Methods
We can take this a step further and instead of adding new behavior we’ll override existing behavior. Continuing with our example, what if we wanted the default description of a NSDate
object to include the time ago in words? We could simply do the following:
However, this is strongly discouraged for two reasons.
- Other frameworks may rely on the expected behavior of the original method. We now have to go through the trouble of re-implementing that behavior, in addition to the new functionality we wanted to introduce, or risk strange side effects and possibly even crashing out.
- If multiple categories implement the same method, the last one loaded wins! The load order is consistent within an application, but it’s arbitrary, out of our hands, and fragile. For all we know, our implementation could itself be overwritten by an internal framework category!
Because of these reasons, this blunt approach to overriding methods should only be used for the simplest of cases. Later in this series, we’ll explore how swizzling allows us to override a method while preserving all implementations.
Including Your Monkey-Patches
Categories are not automatically “picked up” in a project. Any code that relies on the behavior will need to #import
the necessary header files:
However, including the same set of headers over and over again is redundant. We should first create a single header file that imports all of our most frequently used categories:
We can then import this single header file into a prefix header that is added to all source files. XCode projects often have a .pch
file in the Supporting Files group for this very purpose.
Next Up
While adding and overriding classes is straightforward, there is one very big caveat when using Categories; you cannot add new instance variables to a class. We’ll take a look at working around this limitation in the next post.
tl;dr
- Use Objective-C categories to add functionality to existing classes without subclassing.
- Avoid simple overrides with categories as it can cause problems with other frameworks.
- Use prefix headers to easily import your extensions.