Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "fake" ivars in an Obj-C category (iPhone)

Update:

iPhone OS 3.1 has associated objects. However, the iPhone simulator does not. If you want to test associated objects code in the simulator, you should file a bug.

See my SO question here.

rdar://7477326


Snow Leopard now has associated objects.

Is there a way to accomplish something similar without associated objects? (Specifically for the iPhone.)

I am pretty sure I saw something like this a while back, but I can't remember where. Something about turning any object into a KVC container.

like image 206
Corey Floyd Avatar asked Sep 10 '09 16:09

Corey Floyd


3 Answers

objc_setAssociatedObject() and friends were added to iPhone OS 3.1, so if you have the option of targetting just 3.1+ devices you can in fact do the exact same thing as on Snow Leopard...

If you can't you can create a static dictionary of associations and monkey patch out NSObjects dealloc method. For various technical reasons this solution cannot be made to work correctly in the presence of GC (which is why apple added the association stuff), but since iPhone does not support GC that is a non-issue.

If you are just starting work on this project I highly recommend using the runtime functions and targeting 3.1 plus, but if that is not an option here is an example of how you do it.

LGAssociativeStorage.h:

#import <pthread.h>
#import <Foundation/Foundation.h>

@interface NSObject (LGAssociativeStorage)
@property (retain) id associatedObject;
@end

LGAssociativeStorage.mm

#import <objc/runtime.h>
#import "LGAssociativeStorage.h"

/* We are using STL containers because:
   1) Using Objective C containers can cause deallocs which cause recursion issues
   2) STL containers are high perf containers that don't introduce external code dependencies
   Ideally one could include a thread safe map implementation, but I don't need one currently
*/

#include <map>

typedef std::map<id,id> idMap_t;
typedef std::pair<id,id> idPair_t;

static NSMutableDictionary * data = nil;
static pthread_mutex_t data_lock = PTHREAD_MUTEX_INITIALIZER;
static IMP gOriginalNSObjectDealloc = nil;
static idMap_t  associatedObjectMap;

static
void removeAssociatedObjectFromMap(id self) {
  idMap_t::iterator iter = associatedObjectMap.find(self);
    if( iter != associatedObjectMap.end() ) {
        [iter->second release];
        associatedObjectMap.erase(iter);
    }
}

static
id newNSObjectDealloc(id self, SEL deallocSelector, ...) {
    pthread_mutex_lock(&data_lock);
    removeAssociatedObjectFromMap(self);
    pthread_mutex_unlock(&data_lock);
    return gOriginalNSObjectDealloc(self, deallocSelector);
}

static void initIfNecessary(void) {
    if (!data) {
        data = [[NSMutableDictionary alloc] init];

        // The below line of code is abusive... in the future the Objective C runtime will use it as evidence
        // that I am an unfit software engineer and take custody of all my code
        gOriginalNSObjectDealloc = class_replaceMethod([NSObject class], @selector(dealloc), newNSObjectDealloc, "v@:");
    }
}



@implementation NSObject (LGAssociativeStorage)

- (id) associatedObject {
    id retval = nil;
    pthread_mutex_lock(&data_lock);
    initIfNecessary();
    idMap_t::iterator iter = associatedObjectMap.find(self);
    if( iter != associatedObjectMap.end() ) {
        retval = iter->second;
    }
    pthread_mutex_unlock(&data_lock);
    return retval;
}

- (void) setAssociatedObject:(id)object_ {
    pthread_mutex_lock(&data_lock);
    initIfNecessary();
    removeAssociatedObjectFromMap(self);
    [object_ retain];
    associatedObjectMap.insert(idPair_t(self, object_));
    pthread_mutex_unlock(&data_lock);   
}

@end
like image 188
Louis Gerbarg Avatar answered Nov 06 '22 03:11

Louis Gerbarg


You could always have them stored in a singleton.

like image 43
Jeff Kelley Avatar answered Nov 06 '22 02:11

Jeff Kelley


There are no good ways to do this in a generic category.

You can easily add data for an object by having a global NSMutableDictionary that maps from any arbitrary NSObject to whatever data you want. The problem is there is no way to know when the object is deallocated, so you cannot tell (in general) when the data goes stale.

The only generic way to solve this is to use method swizzling to replace the NSObject dealloc method to report the deallocation of the object and release your associated data. I'm sure someone has done this, but its such a hideous hack it would be very hard to recommend as a valid appropach.

Now, if your objects in questions have some other way to monitor life cycle (ie, some deallocation hook, like a delegate objectWillClose method of some sort), then you can hook in to that to release your associated data and that would make the technique quite straight forward and legitimate.

like image 2
Peter N Lewis Avatar answered Nov 06 '22 02:11

Peter N Lewis