TestsTested | ✓ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | Dec 2014 |
Maintained by Benedict Cohen.
BCLKeyValueObservation is a thin abstraction on top of Apple's KVO system. The goals of BCLKeyValueObservation are:
BCLKeyValueObservation achieves these by:
The Receptionist design pattern addresses the general problem of redirecting an event occurring in one execution context of an application to another execution context for handling.
Concepts in Objective-C Programming: Receptionist Pattern[https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/ReceptionistPattern/ReceptionistPattern.html#//apple_ref/doc/uid/TP40010810-CH13-SW107]
In KVO the receptionist pattern is manifested as observeValueForKeyPath:ofObject:change:context:
. The advantage of observeValueForKeyPath:ofObject:change:context:
over invoking callbacks directly is that concurrency preconditions of the receiver can be addressed in a centralized place instead of being repeated in each callback method. This feature does have some merit but it comes at a significant cost. Implementing observeValueForKeyPath:ofObject:change:context:
requires much boiler plate code which is ripe for errors.
BCLKeyValueObservation encapsulates the receptionist pattern and instead provides a target-action style pattern. BCLKeyValueObservation provides the same functionality that a typical implementation of observeValueForKeyPath:ofObject:change:context:
would provide. It ensure that concurrency requirements are meet and then invokes a specific method to handle the observed change.
Blocks are a powerful tool but they are not the right solution to every problem. Blocks are best suited to handling transient, on-off events such as enumerating a collection or asynchronous task completion. Blocks are not well suited for open-ended communication between objects. The reasons for this is because of the memory management behaviour of blocks. By default blocks retain all objects that are within the blocks scope. This is unlikely to cause memory management issues if the block remains exclusively on the stack (in a conceptual sense) but as soon as a block is attached to the object graph (by being stored in a @property
) the possibility of retain cycles becomes much greater.
KVO is intended for open-ended communication. Using blocks as a callback mechanism for KVO makes it very easy to acceidently create a retain cycle. Of course there are techniques for dealing with retain cycles caused by blocks but a better approach is to avoid the quagmire and use a more suitable pattern in the first place.
The target-action pattern, like KVO, has no implications on memory management. Both target-action and KVO assume that memory management is being handled else where. This is fine assumption. If an object is being observed then the observer is interested in the object and should therefore be retaining it anyway.
The target-action pattern is intend for UI controls (views) to communicate with a controller (think target-IBAction
). The target action pattern takes advantage of the responder chain to allow actions to be the most relevant receiver depending on the state of the UI. This flexibility is not implemented (or required) in the case of model-controller communications. To avoid ambiguity and confusion BCLKeyValueObservation uses the terms observer
and changeHandler
instead of target
and action
.
@implementation BECController
#pragma mark - properties
-(void)setModelObject:(BECModelObject *)modelObject
{
//Unregister the existing object
[_modelObject BCL_stopSendingObservationsToObserver:self changeHandler:@selector(modelObject:didChange:keyPath:) forKeyPath:@"value"];
[_modelObject BCL_stopSendingObservationsToObserver:self changeHandler:@selector(modelObject:didChange:keyPath:) forKeyPath:@"anotherValue"];
_modelObject = modelObject;
//start observing the new object
[_modelObject BCL_startSendingObservationsToObserver:self changeHandler:@selector(modelObject:didChange:keyPath:) forKeyPath:@"value"];
[_modelObject BCL_startSendingObservationsToObserver:self changeHandler:@selector(modelObject:didChange:keyPath:) forKeyPath:@"anotherValue"];
//ensure the view correctly represents the new object.
}
#pragma mark - instance life cycle
-(void)dealloc
{
//Unregister the existing object
[_modelObject BCL_stopSendingObservationsToObserver:self changeHandler:@selector(modelObject:didChange:keyPath:) forKeyPath:@"value"];
[_modelObject BCL_stopSendingObservationsToObserver:self changeHandler:@selector(modelObject:didChange:keyPath:) forKeyPath:@"anotherValue"];
}
#pragma mark - KVO handling
-(void)modelObject:(BECModelObject *) didChange:(NSDictionary *)changes keyPath:(NSString *)keyPath
{
[self refreshView];
}
@end