0
votes

I'm trying to make a little binding system between my UILabel and my object Data using KVO. If my UI change, my data have to change, and if my data change my UI should refresh to display the new value.

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(). 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)

// 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

2 Answers

1
votes

Traditionally users of this have used a static char * as the context parameter, so as to differentiate the different observeValueForKeyPath messages. 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. Thus I'd suggest changing PLSObjectPath to 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). 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. This is all a well understood paradyme - moving objects in and out of 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. 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.

0
votes

The memory leak is from [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:. To be able to release the PLSObjectPath:s at that point, you will need to store them somewhere, possibly an NSMutableArray. If you will store these anyway for this purpose, you don't need to use __bridge_retained. Also, in this case your PLSObjectPath:s might/should contain a void* pointing to the other context. This way, you save the creation of PLSObject in observeValueForKeyPath:ofObject:change:context:.

Just a comment, you should start the KVO from viewWill(Did)Appear: and not from viewDidLoad. It gives you a better KVO start/stop management and also removes the unnecessary observation while your view is not on the screen.