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:
- objc_setAssociatedObject - "Sets an associated value for a given object using a given key and association policy"
- objc_getAssociatedObject - "Returns the value associated with a given object for a given key"
- objc_removeAssociatedObjects - "Removes all associations for a given object"
Implementations
- Adding an extra property through the runtime
- Using
UIAlertView
in a similar way toUIAlertController
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. } @endThis code presents an implementation to the aforementioned scenario. Here's what's going on in more detail:
- Key used to set / get associations from the object
- Retrieving the value from the object associated with the key
- Setting the value on the object for the key
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 } } @endAs 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); } } @endThe 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:
- Creating the
UIAlertView
we wish to display in the application - Setting an association on the
alertView
that shall be run inside the delegate - Retrieving the delegate code block associated with the
alertView
- 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); } @endThe code shows an implementation of removing associations at runtime from an object. Here's what's going on in more detail:
- Setting the object to
nil
for a specific key removes the association at runtime - 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 theruntime.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:
OBJC_ASSOCIATION_ASSIGN
- associated object will have a weak referenceOBJC_ASSOCIATION_RETAIN_NONATOMIC
- associated object will have a strong reference made nonatomicallyOBJC_ASSOCIATION_COPY_NONATOMIC
- associated object is copied and the association is made nonatomicallyOBJC_ASSOCIATION_RETAIN
- associated object has a strong reference and it is made atomicallyOBJC_ASSOCIATION_COPY
- associated object is copied and it is made atomically
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 theUIAlertController
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