Faking instance variables in Objective-C categories with Associative References

http://oleb.net/blog/2011/05/faking-ivars-in-objc-categories-with-associative-references/

 

In OS X 10.6 and iOS 3.1, Apple added Associative References to the Objective-C runtime. Essentially, this means that each and every object has an optional dictionary you can add arbitrary key/value pairs to.

This is a great feature, especially considering that Objective-C has forever had a feature to add methods to existing classes: categories. Categories, however, do not permit you to add instance variables. Using associative references, it’s easy to fake ivars.

In the C API of the Objective-C runtime, you add a key/value pair to an object and read it again with these two functions:

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

If we wrap these calls in a property’s custom getter and setter, we can make the implementation of our fake “ivar” totally opaque to the user of our API.

Using objects to tag UIViews

As an example, say we want to add the ability to add an arbitrary object as a tag to a UIView (UIView’s existing tag property only takes integers, which can be limiting at times). The interface of our “object tag” category could look like this:

@interface UIView (ObjectTagAdditions)

@property (nonatomic, retain) id objectTag;
- (UIView *)viewWithObjectTag:(id)object;

@end

Using associative references, the implementation of the property is straightforward:

static const char *ObjectTagKey = "ObjectTag";

@implementation UIView (ObjectTagAdditions)
@dynamic objectTag;

- (id)objectTag {
    return objc_getAssociatedObject(self, ObjectTagKey);
}

- (void)setObjectTag:(id)newObjectTag {
    objc_setAssociatedObject(self, ObjectTagKey, newObjectTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

...

By specifying OBJC_ASSOCIATION_RETAIN_NONATOMIC, we tell the runtime to retain the value for us. Other possible values are OBJC_ASSOCIATION_ASSIGN, OBJC_ASSOCIATION_COPY_NONATOMIC, OBJC_ASSOCIATION_RETAIN, OBJC_ASSOCIATION_COPY, corresponding to the familiar property declaration attributes.

Finally, here is the code for the recursive -viewWithObjectTag: method:

- (UIView *)viewWithObjectTag:(id)object {
    // Raise an exception if object is nil
    if (object == nil) {
        [NSException raise:NSInternalInconsistencyException format:@"Argument to -viewWithObjectTag: must not be nil"];
    }

    // Recursively search the view hierarchy for the specified objectTag
    if ([self.objectTag isEqual:object]) {
        return self;
    }
    for (UIView *subview in self.subviews) {
        UIView *resultView = [subview viewWithObjectTag:object];
        if (resultView != nil) {
            return resultView;
        }
    }
    return nil;
}

Update May 16, 2011: Vadim Shpakovski asked on Twitter why, when the argument to viewWithObjectTag: is nil, I chose to generate an exception over returning nil to the caller. The reason is that returning nil would be ambiguous since it means that no view with the specified objectTag could be found. Since nil is the default value of objectTag, this is highly unlikely.

A good alternative to raising an exception is to return the first view whose objectTag actually is nil, just like viewWithTag: does. I chose not to do that because I imagine it’s more likely that calling -viewWithObjectTag: with a nil argument is a programmer error than intended usage. It’s just a matter of preference, though.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值