As a developer, I love coming across new methods or techniques that help make better, more readable code. Recently I was trying to find a better way of passing information from a method that creates a UIAlertView to the UIAlertView’s delegate method
alertView:didDismissWithButtonIndex:. There is no
userInfo dictionary for an alert view, and Apple specifically says not to subclass UIAlertView. What I’ve done in the past is create a property or class instance variable to temporarily hold the object I want to pass around. I don’t like this technique, it feels sloppy, but it gets the job done.
Associated objects have been around since iOS 3.1 and are a part of the Objective-C runtime. They allow you to associate objects at runtime. Basically, you can attach any object to any other object without subclassing. To begin using associated objects, all you need to do is import <objc/runtime.h> in the class where you want to use them. The relevant methods are the following:
- object is the source object for the association, or in other words, it is the object that will point to the other object.
- *key is the the key for the association, this can be any void pointer, anything that has a constant memory address is all you want.
- value is the object you want to store or associate with the source object.
- policy is a constant defining the type of reference, similar to the types you use when declaring properties. The possible values are:
Typically we don’t want to use
objc_removeAssociatedObjects, but would rather use
objc_setAssociatedObject with a nil value to remove an association. According to Apple,
The main purpose of this function is to make it easy to return an object to a “pristine state”. You should not use this function for general removal of associations from objects, since it also removes associations that other clients may have added to the object. Typically you should use objc_setAssociatedObject with a nil value to clear an association.
In many cases you probably won’t have to worry about removing an association because when the source object is destroyed it will destroy the reference to the associated object.
Sample Use Case
In my case, I want to associate an NSIndexPath to a UIAlertView. Let me explain my use case a little further, you have probably come across a similar problem. I have a table view where I show a confirmation alert when the user tries to delete a row. Usually I wouldn’t put a confirmation on a delete, but sometimes it has serious implications (maybe you’re deleting a folder holding 100 records of something and deleting the folder deletes all those precious records).
The alert is created and displayed in the UITableViewDataSource method
tableView:commitEditingStyle:forRowAtIndexPath:. At this point you have the indexPath you want to delete. Once you call
-show on the alert, your class starts waiting for the UIAlertViewDelegate callback method
alertView:didDismissWithButtonIndex:. Once the user confirms, it enters the delegate method and you no longer know which indexPath you should delete.
Now I’m going to detail a number of solutions to this problem, each better than the last. I’m only going to show the relevant code though instead of the entire class because I’m using a very simple example. I just slightly modified the code that is generated for you when you create a new project with the master-detail template (without core data).
The original solution was to have a class level instance variable that holds the index path we want to delete. We would set the index path to delete in our commitEditingStyle method, and then retrieve it in
This solution works, but why would we want to use this instance variable that is visible to the entire class? Only two methods have interest in this index path, and what if some other method messes with indexPathToDelete and we get some unexpected behavior. It would be better if we could confine this object to only the methods that care about it.
Using the objective-c runtime methods we can associate the index path to the alert view. We will set the association in commitEditingStyle, and retrieve the index path in
As you can see, we no longer need the instance variable, but we use a new static char variable as the association key. The alert view holds a strong reference to the index path, so it persists from one method to the next as long as the alert view is still in memory. When the alert view is destroyed it will also destroy the index path associated with it. This makes the code clearer and confined to just the methods it is used in instead of having an instance variable that is available to the whole class. We can make this code even better though.
Associated Objects Category
You can create a category on NSObject that simplifies the objective-c runtime calls into a nice API you can use in your normal classes. You could expand on this, but a basic category would be as follows:
Your view controller would then look like this…
I like this a little better because it abstracts out the runtime methods and gives you a nice interface you can use on any object. This accomplishes the same thing, but to me it is much more readable and feels better.
According to Apple docs:
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
Also according to Apple docs:
Categories can be used to declare either instance methods or class methods but are not usually suitable for declaring additional properties. It’s valid syntax to include a property declaration in a category interface, but it’s not possible to declare an additional instance variable in a category. This means the compiler won’t synthesize any instance variable, nor will it synthesize any property accessor methods. You can write your own accessor methods in the category implementation, but you won’t be able to keep track of a value for that property unless it’s already stored by the original class.
The only way to add a traditional property-backed by a new instance variable-to an existing class is to use a class extension, as described in ‘Class Extensions Extend the Internal Implementation.’
With our newfound power, we will add a new property to UIAlertView without subclassing it. As you see in the documentation, it is perfectly valid to declare a property in the category interface, you just can’t create a new instance variable. We don’t need a new instance variable, we will just override the getter and setter of our property to store and retrieve the property by associating it to the alert view.
Let’s create a category on UIAlertView called DeleteConfirmation.
Now in UIAlertView+DeleteConfirmation.m
Thanks to Erica Sadun, who then credits Gwynne Raskind, for this bad-assery of using the property selector as the association key. According to them, this is valid because Apple’s selector implementation uses a fixed address.
Using the same example, after importing the new category, our view controller code becomes:
Beautiful. I love it. The index path to delete looks like any other property you would access.
Ok… maybe this is overkill for the example I gave, but I’m sure you will find other uses for it now that you know about it. It is a great weapon to have at your disposal.