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

后端 未结 5 1115
鱼传尺愫
鱼传尺愫 2021-02-09 16:13

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

相关标签:
5条回答
  • 2021-02-09 16:35

    I'll add an answer.

    I found the original blog post, it was from Steve Degutis.

    It basically involves replacing NSObject's methods for valueForUndefinedKey:, setValue:ForUndefinedKey:, and dealloc. Then using a static Dictionary to store any undefined keys.

    Just about as nasty and fun as Louis's solution.

    0 讨论(0)
  • 2021-02-09 16:36

    Notwithstanding concerns for concurrency issues, why not just use global variables ? Even using runtime objc_set/get AssociatedObject() methods aren't you passing a "global" static variable address in which case you still have concurrency issues wouldn't you?

    0 讨论(0)
  • 2021-02-09 16:37

    You could always have them stored in a singleton.

    0 讨论(0)
  • 2021-02-09 16:45

    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
    
    0 讨论(0)
  • 2021-02-09 16:46

    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.

    0 讨论(0)
提交回复
热议问题