Home

Objective-C Runtime Associations

 
Posted on Feb 13 2015

Objective-C as a language has been around since the early 1980s and thus is a very mature language compared to the newer languages such as Swift which Apple introduced at WWDC 2014. While Swift is a breath of fresh air in sense of having the Apple development community very excited we cannot ignore the fact that many of Apple's current frameworks are for the majority written in Objective-C. With this in mind this post attempts to discuss runtime object association which is one of the useful features found in the Objective-C runtime.

#import <objc/runtime.h>

Associating Objects

One of the nice features in the Objective-C runtime is the ability to associate objects with other objects living in the runtime. These objects you can associate with can either be your own custom objects or provided objects associated with Apples frameworks (UIView, NSString, etc...). One of the main benefits for associating objects is that you can provide extra functionality to objects that you do not own and hence can add information without sub-classing.

The Objective-C runtime provides the following methods for object association:

Implementations

Adding an extra property through the runtime
Adding an extra property to an existing object is done through the use of Objective-C category extensions. These categories will provide you the ability to change the object on the fly without having to subclass the object. In the scenario below the idea is to add a property to an Area object called confidentialName.
#import <objc/runtime.h>
#import <Area.h>

/**
 * Key for associating the value with an object
 */
static void *kAreaConfidentialName = &kAreaConfidentialName; // 1.

@implementation Area (Confidential)

- (NSString*)confidentialName
{
    return objc_getAssociatedObject(self, kAreaConfidentialName); // 2.
}

- (void)setConfidentialName:(NSString*)confidentialName
{
    objc_setAssociatedObject(self, kAreaConfidentialName, confidentialName, OBJC_ASSOCIATION_RETAIN); // 3.
}

@end
This code presents an implementation to the aforementioned scenario. Here's what's going on in more detail:
  1. Key used to set / get associations from the object
  2. Retrieving the value from the object associated with the key
  3. Setting the value on the object for the key
Some points to note about the above is that the key is a static void pointer and it points to the address of itself. The reasoning behind this is that for you to be able to access the same information at runtime when getting and setting the key needs to be exactly the same i.e. identical otherwise you will never be able to retrieve information that you have set. By declaring the variable static and by utilizing its address we are able to guarantee that the key will always point to the same memory address and thus setting and getting the information will work as expected.

Using UIAlertView in a similar vain to UIAlertController
One of my pet hates before iOS8 came along with UIAlertController was that you had to utilize the delegate UIAlertViewDelegate in order to handle users interacting with a UIAlertView. This was fine when you had one or two UIAlertView's inside your class but if more than that number occurred you had to resort to using the tag option and set a numeric tag on each UIAlertView so you could later identify them in the delegate as shown below.
@interface ViewController<UIAlertViewDelegate>

@end

@implementation ViewController

- (IBAction)displayAlert:(id)sender
{
    UIAlertView *alertView = [[UIAlertView alloc]
                              initWithTitle:NSLocalizedString(@"Display Alert View 10000", nil)
                                    message:NSLocalizedString(@"Displaying an alert view 10000", nil)
                                   delegate:self
                          cancelButtonTitle:NSLocalizedString(@"Cancel", nil)
                          otherButtonTitles:NSLocalizedString(@"Dismiss", nil),
                             nil];
    alertView.tag = 10000;
    [alertView show];
}

- (IBAction)displayAlert:(id)sender
{
    UIAlertView *alertView = [[UIAlertView alloc]
                              initWithTitle:NSLocalizedString(@"Display Alert View 10001", nil)
                                    message:NSLocalizedString(@"Displaying an alert view 10001", nil)
                                   delegate:self
                          cancelButtonTitle:NSLocalizedString(@"Cancel", nil)
                          otherButtonTitles:NSLocalizedString(@"Dismiss", nil),
                             nil];
    alertView.tag = 10001;
    [alertView show];
}

# pragma mark - UIAlertViewDelegate instance methods (UIALERTVIEWDELEGATE)

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (alertView.tag == 10000) {
        // Do something with alertView 10000
    } else if (alertView.tag == 10001) {
        // Do something with alertView 10001
    }
}

@end
As you can see from above this can get very cumbersome having to remember which UIAlertView is answering its delegate call and more to the point if you have a large view controller with the alerts spread through the code, it is difficult to know exactly where in the view controller the delegate was instantiated and hence know what conditions caused the alert to fire in the first place.
The code below shows you how to utilize the Objective-C runtime to achieve a more elegant look:
#import <objc/runtime.h>

/**
 * Key to hold the block of code to run inside the UIAlertViewDelegate method
 */
static void *kAlertViewDelegateCodeKey = &kAlertViewDelegateCodeKey;

@interface ViewController<UIAlertViewDelegate>

@end

@implementation ViewController

- (IBAction)displayAlert:(id)sender
{
    // 1.
    UIAlertView *alertView = [[UIAlertView alloc]
             initWithTitle:NSLocalizedString(@"Unsaved Changes", nil)
                   message:NSLocalizedString(@"You will lose your unsaved changes. Are you sure?", nil)
                  delegate:self
         cancelButtonTitle:NSLocalizedString(@"No", nil)
         otherButtonTitles:NSLocalizedString(@"Yes", nil), nil];

    // 2.
    objc_setAssociatedObject(alertView, kAlertViewDelegateCodeKey, ^(NSInteger buttonIndex) {
        if (buttonIndex == 1) {
            // Do something if the user decides Yes
        }
    }, OBJC_ASSOCIATION_COPY);
    [alertView show];
}

# pragma mark - UIAlertViewDelegate instance methods (UIALERTVIEWDELEGATE)

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    // 3.
    void (^codeBlock)(NSInteger) = objc_getAssociatedObject(alertView, kAlertViewDelegateCodeKey);
    // 4.
    if (codeBlock) {
        codeBlock(buttonIndex);
    }
}

@end
The code presents an implementation of utilizing the associations to keep your alert firing code and delegate code together in the same method. Here's what's going on in more detail:
  1. Creating the UIAlertView we wish to display in the application
  2. Setting an association on the alertView that shall be run inside the delegate
  3. Retrieving the delegate code block associated with the alertView
  4. Verifying we actually have a valid code block since calling this while nil will result in a crash

Removing Associations

The association with the object can be removed programatically or through the objects natural lifecycle coming to an end and thus once it is deallocated the association will of coure be removed automatically by the system. The code below shows the two ways to remove the association programatically:
#import <objc/runtime.h>

/**
 * Key to hold the associated object
 */
static void *kAssociatedObjectKey = &kAssociatedObjectKey;

@implementation ViewController

- (IBAction)removeSingleAssociatedObject:(id)sender
{
    // 1.
    objc_setAssociatedObject(self, kAssociatedObjectKey, nil, OBJC_ASSOCIATION_COPY);
}

- (IBAction)removeAllAssociatedObjects:(id)sender
{
    // 2.
    objc_removeAssociatedObjects(self);
}

@end
The code shows an implementation of removing associations at runtime from an object. Here's what's going on in more detail:
  1. Setting the object to nil for a specific key removes the association at runtime
  2. Removes all associated objects regardless of who set them. Only use if you know you want a clean object

Objective-C Runtime Association Policies

While going through this post you may have noticed that when setting the associated object we are utilizing the OBJC_ASSOCIATION_COPY as the last parameter in the method. If you look through the runtime.h file and find the declaration you will notice that this is an Association Policy which determines how the value / object shall be associated when calling the method. Possible values are listed below:

Conclusion

The Objective-C runtime is a very powerful beast and this post just touches the tip of the iceberg in terms of what you can achieve by manipulation the runtime. With the release of iOS8 and the UIAlertController class the need for supporting the associated on UIAlertView's has diminished, however if you have to support any devices running a version less than iOS8 it could still become a useful tool in your toolbox for keeping your code cleaner and neater.

UIView Hierarchy