0

我正在尝试使用 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)];
}
4

2 回答 2

1

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

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

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

于 2013-11-13T13:35:23.130 回答
0

内存泄漏来自 [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 启动/停止管理,并在您的视图不在屏幕上时消除不必要的观察。

于 2013-11-13T14:31:43.177 回答