Monkey-Patching iOS with Objective-C Categories Part II: Adding Instance Properties

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. In this post we’ll cover how to add new properties to instances.

The Scenario : Adding a New Feature

Why would we want want to patch in a new property to our class hierarchy instead of using subclassing?

Let’s imagine we are creating an app with multiple controllers and wish to add a “touring” feature; the first time a user arrives on a screen, popup tips appear to guide them through its functionality. It would make sense to add a tourSteps property to our controllers, which they each set with their own unique tour.

We could add this property through subclassing UIViewController; introducing a TouringViewController for example that our controllers would then extend. But what if want the functionality of other core iOS controllers like UINavigationController or UITableViewController? You would either have to create custom subclasses for each of them (TouringNavigationController, TouringTableViewController, etc.) which your own controllers would then extend, or abandon using them and reimplement their functionality. Neither solution is appealing.

Instead, use categories to inject the new property into UIViewController and have it available to all descendents, whether our own or
the iOS framework. As when defining properties for regular classes, the @property declarative is used as shorthand to define the getters and setters of a property in the category header file.

UIViewController+TourGuide.h

The usual “next step” after defining a property is to use a @syntesize declarative in the implementation to create the expected getter and setter methods and back them with an appropriate instance variable. We would expect the same to work when adding a property via a category:

However, the above code will fail. Why?

The Problem: No Instance Variables

From the Objective-C Programming Language Guide – Categories and Extensions:

Note that a category can’t declare additional instance variables for the class; it includes only methods.

So the @syntesize cannot create a \_tourSteps instance variable to back the generated getter and setter. Likewise defining the instance variable in the category header file as follows would still not work:

Non-legal Declaration of Instance Variables in Category

What are our options? We clearly have to implement the getter method -tourSteps and setter method -setTourSteps: ourselves, but where will we store the actual values if not an instance variable? A static variable? Doing so at the class-level makes no sense as each instance needs its own value, and we face memory retention headaches if we create them at the method level.

We could maintain a global mapping of objects to their per-instance property values but it would be difficult to correctly manage memory for that collection and properly clean up variables when their associated instance is dealocated.

Luckily the Objective-C runtime already provides such a global mapping for us, handling the memory management issues as long as we use it properly.

The Solution: Associated References

Associated Reference are provided through a collection of Objective-C runtime functions to simulate the behavior of instance variables. Through them you can create and set associations between your class instance and objects that represent their property values. More importantly, those associations are released automatically when your objects are released.

Using associated references, our implementation of -tourSteps and -setTourSteps: is as follows:

Let’s walk through what is happening here.

The “Key” to Creating Associations

The functions for getting and setting associations refer to an object and a key. The object value is the instance that owns the property. In our case, it is self. We then identify the property we will try to retrieve with the key. But what is the key?

Unlike most mapping systems, it is NOT a string; it’s a fixed address in memory, hence the pointer in the method signature. It needs to be fixed to ensure we are always using the same key value when retrieving a specific property. A static variable fits this criteria perfectly. And since the address is all we care about (retrieved with the address (&) operator), what is in that memory address doesn’t matter at all. We make it a char to minimize its footprint. So we define the key OUTSIDE the class as:

static char tourStepsKey;

And later use &tourStepsKeys when we set or get the value.

Respecting Property Attributes When Setting Values

The methods we create an association with is the objc_setAssociatedObject runtime function:

void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)

We make use of it in our property setter implementation, passing in self and the address of tourStepsKey as previously discussed. The third parameter is literally the value we are setting the property to. Note that it is a id reference, meaning we cannot pass primitive values (like NSInteger) but only objects (like NSNumber). Keep this in mind when defining and implementing your own properties.

The final parameter to objc_setAssociatedObject is the policy. It clues the runtime on what to do with the values when their associated object is removed from memory, corresponding to the property attributes strong, weak, copy, and so on. When implementing instance properties, simply pass in the appropriate policy:

  • (weak) / (assign) OBJC_ASSOCIATION_ASSIGN
  • (strong) / (retain) OBJC_ASSOCIATION_RETAIN
  • (copy) OBJC_ASSOCIATION_COPY
  • (nonatomic,strong) OBJC_ASSOCIATION_RETAIN_NONATOMIC
  • (nonatomic,copy) OBJC_ASSOCIATION_COPY_NONATOMIC

You can also remove an association using objc_setAssociatedObject by passing nil as the value. This works perfectly for our purposes of implementing the behavior of properties.

Getting the Associated Values

Retrieving our property is even easier with the objc_getAssociatedObject runtime method:

id objc_getAssociatedObject(id object, void *key)

Like when the value was set, we pass in self and the address of our property identifying key variable to the method. Note we return the value right away instead of casting it; again, as it is an id reference, no type will be enforced by the compiler.

Next

So there you have it; while you can’t technically have instance variables backing category defined properties, through the use of Associated References you can implement their functionality rather easily.

In the next and final post in the series, we will see how to use the technique of swizzling to accomplish something we were warned off last time; truly overriding and extending existing methods, even those core to the operation of iOS.

TL;DR

  • You can add properties through categories but NOT instance variables; @syntesize will fail.
  • Use Associated References to replicate the behavior of instance variables.
  • The property is identified using a fixed memory address; define a static char variable whose address is the identifier.
  • Use the objc_setAssociatedObject to implement the setter, passing in self as the object, the address of your key variable, the value of the property and the appropriate policy.
  • Use objc_getAssociatedObject to implement the getter.
  • Check out a related bonus trick below!

BONUS: The Exception to the Rule

There is ONE exception to the rule of categories being unable to define instance variables using @synthesize for properties defined in the category; when it’s a [class extension][categores]. For example, the code below defines a property foo in an extension, which appears as an “anonymous” category with empty () paratheses:

But why is this useful? Well when the extension is declared not in an header file but with immediately with the implementation it effectively provides for “private” properties. The property will not be available to other components that reference the header, but you can within the implementation and without the hassle of using associated references! We can also use this technique to have a property defined with one set of attributes in its header file (for example readonly) and then redefine it with a different set of attributes in extension only available to the implementation!

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. Bookmark the permalink.
  • Vince

    thx can’t wait for the swizzling

  • nupark

    And you Ruby people never stop to wonder why it’s so hard to update multiple gems and rails all at once? It might have something to do with this. Don’t bring monkey patching to ObjC. Your category methods should be the extend of what you add, and they should be name-prefixed to avoid conflicts. You should essentially never override or swizzle an existing method in application code.

    • http://rudyjahchan.com/ Rudy Jahchan

      I am afraid I will have to disagree on this. First, “monkey-patching”, or the ability to reopen an existing class to add or modify behavior without extending it, exists in more languages than Ruby. Yes, Rubyists are probably more likely to take advantage of this, but the fact that Catgeories, Extensions, and the ability to swizzle exist in the Objective-C runtime should be an indication that they are acceptable.

      My next post will cover how to safely override existing methods, and in fact explicitly states you should always maintain previous behavior. Which is part of another message I have departed in these posts; that you should only use this functionality where warranted. It is not a hammer to be used for every “feature” nail.

    • http://rudyjahchan.com Rudy Jahchan

      I am afraid I will have to disagree on this. First, “monkey-patching”, or the ability to reopen an existing class to add or modify behavior without extending it, exists in more languages than Ruby. Yes, Rubyists are probably more likely to take advantage of this, but the fact that Catgeories, Extensions, and the ability to swizzle exist in the Objective-C runtime should be an indication that they are acceptable.

      My next post will cover how to safely override existing methods, and in fact explicitly states you should always maintain previous behavior. Which is part of another message I have departed in these posts; that you should only use this functionality where warranted. It is not a hammer to be used for every “feature” nail.

      • nupark

        It’s impossible to maintain previous behavior without calling the previous method. It’s also impossible to be sure that your override will affect the correct class in a world in which class clusters exist.

        The Objective-C runtime supports this functionality because it provides a general API with which one can assemble and operate on classes at runtime. It doesn’t provide this functionality for the purpose of breaking API contracts and monkey-patching classes you do not control. It is very difficult to use correctly, and absolutely wrong to treat it as a general-purpose tool.

        Or, to quote bbum, runtime wrangler at Apple, while he was bemoaning the apparent discovery of monkey patching by new iOS developers: “swizzling lies! Just try and swizzle a method in a class cluster class when the subclass changes across a software update or arch”

        https://twitter.com/bbum/status/294822376284246016
        https://twitter.com/bbum/status/294833400425091073

        You should not monkey-patch code. To do so without breaking existing invariants is very difficult. If you fail in this, then it becomes increasingly difficult to trust that your code will continue to work across releases of iOS or that it will continue to interoperate with other code that monkey-patches shared classes.

        Please don’t bring this lack of engineering care and discipline to ObjC.

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

  • emice

    Thanks for this! I am creating a container view controller like UITabBarController and UINavigationController, called FlipViewController. I wanted any UIViewController instance added as a child view of a FlipViewController to see it’s parent though self.flipController, just like when using self.navigationController. I could have used self.parentViewController but this can come up nil when the view is not the side being displayed, leading to potential bugs.

  • emice

    I needed to include – #import – to get this to compile.

  • Pingback: Giuseppe Basile | WP Stacker link collection: January with 111 links