如何在Obj-C类别中“伪造”ivars(iPhone)

时间:2009-09-10 16:54:02

标签: iphone objective-c cocoa cocoa-touch

更新

iPhone OS 3.1具有关联对象。但是,iPhone模拟器没有。如果要在模拟器中测试关联对象代码,则应该提交错误。

请参阅我的问题here

rdar:// 7477326


Snow Leopard现在有关联对象。

有没有办法在没有相关对象的情况下完成类似的事情? (特别针对iPhone。)

我很确定我曾经看过这样的事情,但我不记得在哪里。关于将任何对象转换为KVC容器的事情。

5 个答案:

答案 0 :(得分:13)

objc_setAssociatedObject()和朋友被添加到iPhone OS 3.1中,所以如果您可以选择仅定位3.1+设备,那么事实上你可以做与雪豹完全相同的事情......

如果你不能,你可以创建一个静态的关联字典和猴子补丁NSObjects dealloc方法。由于各种技术原因,这种解决方案无法在GC存在的情况下正常工作(这就是为什么苹果添加了关联的东西),但由于iPhone不支持GC,这是一个非问题。

如果你刚开始研究这个项目,我强烈建议你使用运行时功能并定位3.1 plus,但如果这不是一个选项,那么就是你如何做的一个例子。

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

答案 1 :(得分:3)

你总是可以将它们存储在单身中。

答案 2 :(得分:2)

在通用类别中没有好方法可以做到这一点。

通过将全局NSMutableDictionary从任意NSObject映射到您想要的任何数据,您可以轻松地为对象添加数据。问题是无法知道对象何时被释放,因此您无法判断(通常)数据何时过时。

解决此问题的唯一通用方法是使用方法调配来替换NSObject dealloc方法以报告对象的释放并释放关联数据。我确定有人这样做了,但是这样一个可怕的黑客很难推荐作为一个有效的方法。

现在,如果问题中的对象有其他方式来监视生命周期(即一些解除分配钩子,如某种类型的委托objectWillClose方法),那么你可以挂钩来释放你的相关数据,那就是使这项技术非常直接和合法。

答案 3 :(得分:0)

我会添加一个答案。

我找到了原来的blog post,它来自Steve Degutis。

它主要涉及替换valueForUndefinedKey:setValue:ForUndefinedKey:dealloc的NSObject方法。然后使用静态Dictionary来存储任何未定义的键。

就像路易斯的解决方案一样令人讨厌和有趣。

答案 4 :(得分:0)

尽管存在对并发问题的担忧,为什么不使用全局变量?即使使用运行时objc_set / get,也可以使用AssociatedObject()方法传递“全局”静态变量地址,在这种情况下,您仍然会遇到并发问题吗?