Monkey-Patching iOS with Objective-C Categories Part I: Simple Extensions and Overrides

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.

  1. 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.
  2. 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.

About Rudy Jahchan

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.
This entry was posted in Mobile and tagged , , . Bookmark the permalink.
  • Jared Carroll

    Thanks for the nice intro Rudy. I’m glad to see this is supported in Objective-C.

    In Ruby, I’m a huge fan of the active support gem’s monkey-patches. Extending native classes results in a much more natural interface, as opposed to a “utils” class with a bunch of class methods.

    • http://twitter.com/rudy Rudy Jahchan

      I completely agree. Categories allow you to avoid (for the most part) avoid class bloat and increase readability, when used appropriately. I hope the rest of the post in these series will illustrate this even as we cover more advanced ideas.

  • Pingback: A Smattering of Selenium #79 « Official Selenium Blog

  • David Churchland

    In your example how come the interface is category ‘Formatting’ but the implementation is category ‘Utilities’?

    • http://twitter.com/rudy Rudy Jahchan

      Whoops! Thanks for the catch. Corrected in the original gist.

  • Duncan C

    Another problem with overriding methods is that you can’t call your superclass’s implementation.

    Are you going to write a follow-on that covers simulating instance variables? I assume you’re going to use associative storage through the Objective C runtime? I just learned that trick a couple of months ago.

  • Pingback: Monkey-Patching iOS with Objective-C Categories Part II: Adding Instance Properties | The Carbon Emitter

  • http://www.facebook.com/fuxlud Luda Fux

    Thank you very much. Very nice example.

  • http://alexchaffee.com/ Alex Chaffee

    Something has gone seriously wrong with your gists on this article. Apparently, every time you intend to insert one, you actually get all… the repetition is dazzling!

    • JonahWilliams

      Uh oh, thanks for the heads up. We’ll get that corrected.

      • http://alexchaffee.com/ Alex Chaffee

        Fixed!

  • Pingback: Monkey-Patching iOS with Objective-C Categories Part III: Swizzling | The Carbon Emitter