简体   繁体   English

KVO和上下文可能会导致内存泄漏

[英]Possible memory leak with KVO and context

I'm trying to make a little binding system between my UILabel and my object Data using KVO. 我正在尝试使用KVO在UILabel和对象数据之间建立一个小的绑定系统。 If my UI change, my data have to change, and if my data change my UI should refresh to display the new value. 如果我的UI更改,则我的数据也必须更改,如果我的数据更改,我的UI应该刷新以显示新值。

The biggest issue I have is that I need to cast a custom object to a void* (context) with __bridge_retained void* - or CFBridgingRetain() - but I don't know where I should call CFBridgingRelease(). 我遇到的最大问题是,我需要使用__bridge_retained void *(或CFBridgingRetain())将自定义对象转换为void *(上下文),但我不知道应该在哪里调用CFBridgingRelease()。 If call it in observeValueForKeyPath method I get a bad access error (I guess because my Reference Count to the object pointed by context is 0) 如果在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)];
}

Traditionally users of this have used a static char * as the context parameter, so as to differentiate the different observeValueForKeyPath messages. 传统上,此用户使用静态char *作为上下文参数,以区分不同的observeValueForKeyPath消息。 That said, it should be possible to do something as you are attempting. 就是说,应该可以在尝试时做一些事情。

What I would suggest is to switch from a custom object to a Core Foundation one, where you can do you own memory management explicitly. 我建议从自定义对象切换到Core Foundation,从中可以显式地拥有内存管理。 Thus I'd suggest changing PLSObjectPath to CFDictionary. 因此,我建议将PLSObjectPath更改为CFDictionary。 You can first create a NSDictionary, then "transfer" it to the CF domain with the appropriate cast, and pass that CFDictionary object for context (which is now a retained CF object). 您可以首先创建一个NSDictionary,然后使用适当的强制转换将其“转移”到CF域,然后将该CFDictionary对象传递给上下文(现在是保留的CF对象)。 Recast it in observeValueForKeyPath to a CFDictionary, properly ARC cast it to a NSDictionary, use that, then it should get released if you've done the ARC correctly. 将它在observeValueForKeyPath重铸到CFDictionary,将ARC正确地投射到NSDictionary,然后使用它,如果正确完成了ARC,则应该释放它。 This is all a well understood paradyme - moving objects in and out of ARC. 这都是一个很好理解的天堂-将对象移入和移出ARC。

Another way you could do it is us a static NSMutableDictionary, and use the context pointer to go to a int value, which when converted to a NSNumber is the key to the dictionary. 您可以执行此操作的另一种方法是使用静态NSMutableDictionary,然后使用上下文指针转到一个int值,将其转换为NSNumber时将其作为字典的键。 If all KVO occurs on the main thread, you don't need to protect the dictionary, but if not then you will need to put all access to the dictionary behind a serial dispatch queue that in forces serial access on one thread. 如果所有KVO都发生在主线程上,则不需要保护字典,但是如果没有,则需要将对字典的所有访问权限放在串行分派队列后面,从而强制对一个线程进行串行访问。

The memory leak is from [obj.theObj removeObserver:self forKeyPath:obj.thePath]. 内存泄漏来自[obj.theObj removeObserver:self forKeyPath:obj.thePath]。 You are removing the observer but as the system doesn't treat the context as an object, it will not release it. 您正在删除观察者,但是由于系统不会将上下文视为对象,因此不会释放它。 Also, there is no way for you to get the context from the observed object itself. 同样,您也无法从被观察对象本身获取上下文。

At some point you will need to stop all observation to allow your view to be deallocated. 在某个时候,您将需要停止所有观察以允许重新分配视图。 This should happen from viewDid(Will)Disappear:. 这应该从viewDid(Will)Disappear:发生。 To be able to release the PLSObjectPath:s at that point, you will need to store them somewhere, possibly an NSMutableArray. 为了能够在那时释放PLSObjectPath:s,您将需要将它们存储在某个地方,可能是NSMutableArray。 If you will store these anyway for this purpose, you don't need to use __bridge_retained. 如果您仍然为此存储这些,则无需使用__bridge_retained。 Also, in this case your PLSObjectPath:s might/should contain a void* pointing to the other context. 同样,在这种情况下,您的PLSObjectPath:s可能/应该包含指向另一个上下文的void *。 This way, you save the creation of PLSObject in observeValueForKeyPath:ofObject:change:context:. 这样,您可以将PLSObject的创建保存在observeValueForKeyPath:ofObject:change:context:中。

Just a comment, you should start the KVO from viewWill(Did)Appear: and not from viewDidLoad. 只是注释,您应该从viewWill(Did)Appear:而不是从viewDidLoad启动KVO。 It gives you a better KVO start/stop management and also removes the unnecessary observation while your view is not on the screen. 它为您提供了更好的KVO启动/停止管理,并且还消除了视图不在屏幕上时的不必要观察。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM