Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is Associative Reference implemented?

I saw a very good sample here: Subclass UIButton to add a property

What is it? You can't add object to a category. But now with this trick you can.

So what is it? How does it work?

Objective-c object already have some constant number of ivar pointers right?

Now you add another one? How did they figure that out?

A pretty ugly notation I must admit.

like image 548
Anonymous White Avatar asked Oct 28 '12 13:10

Anonymous White


1 Answers

With the Associative References trick, you're not actually adding any instance data to the UIButton object. Instead, you're using a totally separate Cocoa facility to create a new dictionary mapping (or associating) existing UIButton objects with data that's stored elsewhere in the heap.

You could do exactly the same thing without using Cocoa's Associative References; it would just be even uglier and probably less efficient. It would go something like this, in Objective-C++. (I'm not even going to try to write it in Objective-C, because CFMutableDictionary and NSMutableDictionary both have the wrong behavior on a couple of levels, and I'm not going to write the whole thing from scratch. However, C++'s std::map can't be used with __weak references the way I want to use it, so I'm falling back on this inefficient std::vector algorithm. For those unfamiliar with C++: std::vector is roughly equivalent to an NSMutableArray, except that you get to choose whether it retains its contents.)

The point is that the UIButton objects aren't being changed; what's changing is the contents of this additional dictionary. The property getter and setter simply know how to look things up in that dictionary so that it appears as if the UIButton has a new property.

#import "UIButton+Property.h"
#import <algorithm>
#import <vector>

typedef std::pair<__weak id, __strong id> EntryType;
static std::vector<EntryType> myAR;

@implementation UIButton(Property)

-(void) setProperty:(id)property
{
    for (int i=0; i < myAR.size(); ++i) {
        if (myAR[i].first == self) {
            myAR[i].second = property;
            return;
        }
    }
    myAR.push_back(EntryType(self, property));
}

-(id) property
{
    /* To save space, periodically erase the dictionary entries for
     * UIButton objects that have been deallocated. You can skip this
     * part, and even use NSMutableDictionary instead of this C++
     * stuff, if you don't care about leaking memory all over the place.
     */
    size_t n = myAR.size();
    for (size_t i=0; i < n; ++i) {
        if (myAR[i].first == nil)
            myAR[i] = myAR[--n];
    }
    myAR.resize(n);

    /* Look up "self" in our dictionary. */
    for (size_t i=0; i < myAR.size(); ++i) {
        EntryType &entry = myAR[i];
        if (entry.first == self) {
            return entry.second;
        }
    }
    return nil;
}

@end

See also: http://labs.vectorform.com/2011/07/objective-c-associated-objects/

like image 74
Quuxplusone Avatar answered Oct 25 '22 21:10

Quuxplusone