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

后端 未结 5 1129
鱼传尺愫
鱼传尺愫 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: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 
    #import 
    
    @interface NSObject (LGAssociativeStorage)
    @property (retain) id associatedObject;
    @end
    

    LGAssociativeStorage.mm

    #import 
    #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 
    
    typedef std::map idMap_t;
    typedef std::pair 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
    

提交回复
热议问题