【问题标题】:Possible memory leak with KVO and contextKVO 和上下文可能存在内存泄漏
【发布时间】:2013-11-25 22:02:56
【问题描述】:

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

我遇到的最大问题是我需要使用 __bridge_retained void* - 或 CFBridgingRetain() 将自定义对象转换为 void*(上下文) - 但我不知道应该在哪里调用 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)];
}

【问题讨论】:

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


    【解决方案1】:

    传统上,此用户使用静态字符 * 作为上下文参数,以区分不同的 observeValueForKeyPath 消息。也就是说,应该可以按照您的尝试做某事。

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

    另一种方法是我们使用静态 NSMutableDictionary,并使用上下文指针转到 int 值,当转换为 NSNumber 时,它就是字典的键。如果所有 KVO 都发生在主线程上,则不需要保护字典,但如果没有,则需要将所有对字典的访问放在串行调度队列后面,以强制在一个线程上进行串行访问。

    【讨论】:

    • xCode 工具分析器仍然显示存储到我的初始 NSDictionary 中的对象可能泄漏的警告
    • 如果字典本身被释放,那么您需要进一步挖掘以找出为什么其中放置的一项没有释放。我在我的 github 网站上写了一个项目,让你跟踪任意对象何时被释放 ObjectTracker github.com/dhoerl/ObjectTracker.git
    【解决方案2】:

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

    在某些时候,您需要停止所有观察以释放您的视图。这应该发生在 viewDid(Will)Disappear: 中。为了能够在此时释放 PLSObjectPath:s,您需要将它们存储在某个地方,可能是 NSMutableArray。如果您出于此目的无论如何都要存储这些,则无需使用 __bridge_retained。此外,在这种情况下,您的 PLSObjectPath:s 可能/应该包含指向其他上下文的 void*。这样,您将 PLSObject 的创建保存在 observeValueForKeyPath:ofObject:change:context: 中。

    只是一个注释,您应该从 viewWill(Did)Appear: 而不是 viewDidLoad 启动 KVO。它为您提供了更好的 KVO 启动/停止管理,并在您的视图不在屏幕上时消除了不必要的观察。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-23
      • 2020-06-12
      • 2011-07-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多