KVO和上下文可能发生内存泄漏

时间:2013-11-13 10:37:05

标签: ios objective-c memory memory-management memory-leaks

我正在尝试使用KVO在我的UILabel和我的对象数据之间建立一个小的绑定系统。如果我的UI更改,我的数据必须更改,如果我的数据更改,我的UI应该刷新以显示新值。

我遇到的最大问题是我需要使用__bridge_retained void * - 或CFBridgingRetain()将自定义对象强制转换为void *(context) - 但我不知道应该在哪里调用CFBridgingRelease()。如果在observeValueForKeyPath方法中调用它,我会得到一个错误的访问错误(我想因为我对上下文指向的对象的引用计数为0)

// viewDidLoad
// binding my label text with a custom data object
[self bindObject:_myLabel withPath:@"text" toObject:_user path:@"name"];

-(void) bindObject:(id)uiObj withPath:(NSString *)uiPath toObject:(id)dataObj path:(NSString *)dataPath
{
    // custom object storing the object I want to bind and his path
    PLSObjectPath* op = [[PLSObjectPath alloc] init];
    op.theObj = dataObj;
    op.thePath = dataPath;
    PLSObjectPath* ob = [[PLSObjectPath alloc] init];
    ob.theObj = uiObj;
    ob.thePath = uiPath;

    /* possible leak because I don't know where to call CFBridgingRelease */
    [uiObj addObserver:self forKeyPath:uiPath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(op)];
    [dataObj addObserver:self forKeyPath:dataPath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(ob)];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{   

    PLSObjectPath *obj = (__bridge PLSObjectPath*) context;
    PLSObjectPath* pairObj = [[PLSObjectPath alloc] init];
    pairObj.theObj = object;
    pairObj.thePath = keyPath;
    // avoid infinite loop
    [obj.theObj removeObserver:self forKeyPath:obj.thePath];
    [obj.theObj setValue:change[@"new"] forKeyPath:obj.thePath];
    [obj.theObj addObserver:self forKeyPath:obj.thePath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(pairObj)];
}

2 个答案:

答案 0 :(得分:1)

传统上,这个用户使用静态char *作为上下文参数,以便区分不同的observeValueForKeyPath消息。也就是说,应该可以在尝试时做点什么。

我建议从自定义对象切换到Core Foundation,在那里您可以明确地拥有内存管理。因此,我建议将PLSObjectPath更改为CFDictionary。您可以先创建一个NSDictionary,然后使用适当的强制转换将其“传输”到CF域,并将该CFDictionary对象传递给上下文(现在是一个保留的CF对象)。将它observeValueForKeyPath重新转换为CFDictionary,正确地将它转换为NSDictionary,使用它,如果你正确地完成了ARC,它应该被释放。这是一个很好理解的范例 - 将对象移入和移出ARC。

另一种方法是使用静态NSMutableDictionary,并使用上下文指针转到int值,转换为NSNumber时,该值是字典的关键。如果所有KVO都出现在主线程上,则不需要保护字典,但如果没有,则需要将对字典的所有访问权限置于串行调度队列后面,该队列强制在一个线程上进行串行访问。

答案 1 :(得分:0)

内存泄漏来自[obj.theObj removeObserver:self forKeyPath:obj.thePath]。您正在删除观察者,但由于系统不将上下文视为对象,因此不会释放它。此外,您无法从观察对象本身获取上下文。

在某些时候,您需要停止所有观察以允许您的视图被取消分配。这应该发生在viewDid(Will)消失:为了能够在那时释放PLSObjectPath:s,你需要将它们存储在某个地方,可能是NSMutableArray。如果您仍然为此目的存储这些,则不需要使用__bridge_retained。此外,在这种情况下,您的PLSObjectPath:s可能/应该包含指向其他上下文的void *。这样,您可以在observeValueForKeyPath:ofObject:change:context:。

中保存PLSObject的创建。

只是评论,你应该从viewWill(Did)出现KVO:而不是来自viewDidLoad。它为您提供了更好的KVO启动/停止管理,并且当您的视图不在屏幕上时也消除了不必要的观察。