例子有点少, 内存管理方面不清楚~~,
Item 10: Use Associated Objects to Attach Custom Data to Existing Classes
Sometimes, you want to associate information with an object. Normally, you would do this by subclassing the object’s class and use that instead. However, you can’t always do this, since instances of the class might be created for you by some means and you cannot tell it to create instances of your class instead. That’s where the powerful Objective-C feature called Associated Objects comes in handy.
Objects are associated with other objects, using a key to identify them. They are also designated a storage policy to govern memory-management semantics of the stored value. The storage policies are defined by the enumeration objc_AssociationPolicy
, which contains the values shown in Table 2.1 against the @property
attribute for the equivalent if the association were a property (seeItem 6 for further information on properties).
Management of associations is performed using the following methods:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
Sets up an association of object to value with the given key and policy.
id objc_getAssociatedObject(id object, void *key)
Retrieves the value for the association on object with the given key.
void objc_removeAssociatedObjects(id object)
Removes all associations against object.
The accessing of associated objects is functionally similar to imagining that the object is an NSDictionary
and calling [object setObject:value forKey:key]
and [object objectForKey:key]
. An important difference to note, though, is that key
is treated purely as an opaque pointer. Whereas with a dictionary, keys are regarded equal if they return YES
for isEqual:
, the key for associated objects must be the exact same pointer for them to match. For this reason, it is common to use static global variables for the keys.
An Example of Using Associated Objects
In iOS development, it’s common to use the UIAlertView
class, which provides a standard view for showing an alert to the user. There’s a delegate protocol to handle when the user taps a button to close it; however, using delegation splits up the code of creation of the alert and handling the tap. This makes it slightly awkward to read, as the code is split between two places. Here is an example of what using a UIAlertView
would look like normally:
- (void)askUserAQuestion {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Question"
message:@"What do you want to do?"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Continue", nil];
[alert show];
}
// UIAlertViewDelegate protocol method
- (void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
[self doCancel];
} else {
[self doContinue];
}
}
This pattern gets even more complicated if you ever want to present more than one alert in the same class, since you then have to checkthe alertView
parameter passed into the delegate
method and switch based on that. It would be much simpler if the logic for what to do when each button is tapped could be decided when the alert is created. This is where an associated object can be used. A solution is to set a block against an alert when it is created and then read that block out when the delegate
method is run. Implementing it would look like this:
#import <objc/runtime.h>
static void *EOCMyAlertViewKey = "EOCMyAlertViewKey";
- (void)askUserAQuestion {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Question"
message:@"What do you want to do?"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Continue", nil];
void (^block)(NSInteger) = ^(NSInteger buttonIndex){
if (buttonIndex == 0) {
[self doCancel];
} else {
[self doContinue];
}
};
objc_setAssociatedObject(alert,
EOCMyAlertViewKey,
block,
OBJC_ASSOCIATION_COPY);
[alert show];
}
// UIAlertViewDelegate protocol method
- (void)alertView:(UIAlertView*)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
void (^block)(NSInteger) =
objc_getAssociatedObject(alertView, EOCMyAlertViewKey);
block(buttonIndex);
}
With this approach, the code for creating the alert and handling the result is all in one place, making it more readable than before, as you don’t have to flick between two portions of code to understand why the alert view is being used. You would, however, need to be careful with this approach, as retain cycles could easily be introduced if the block captured. See Item 40 for more information on this problem.
As you can see, this approach is very powerful, but it should be used only when there’s no other way of achieving what you need to do. Widespread use of this approach could get out of hand very quickly and make debugging difficult. Retain cycles become harder to reason, since there is no formal definition of the relationship between the associated objects, as the memory-management semantics are defined at association time rather than in an interface definition. So proceed with caution when using this approach, and do not use it purely because you can. An alternative way of achieving the same with UIAlertView
would be to subclass it and add a property to store the block. I would suggest this approach over associated objects if thealert
view were to be used more than once.
The memory-management semantics of associated objects can be defined to mimic owning or nonowning relationships.
Associated objects should be used only when another approach is not possible, since they can easily introduce hard-to-find bugs.