BCLKeyValueObservation 0.1.0

BCLKeyValueObservation 0.1.0

测试已测试
语言语言 Obj-CObjective C
许可 MIT
发布最后发布2014年12月

Benedict Cohen 维护。



  • Benedict Cohen

什么是 BCLKeyValueObservation?

BCLKeyValueObservation 是在 Apple 的 KVO 系统之上的一个薄抽象。BCLKeyValueObservation 的目标是

  • 减少模板代码(再也不用 observeValueForKeyPath:ofObject:change:context:了)
  • 提高功能清晰度(强制方法的命名)
  • 不是用运行时随意编程的借口

BCLKeyValueObservation 通过以下方式实现这些目标

  • 封装了 KVO 使用的接待员模式,并使用受目标动作启发模式
  • 提供具有更强烈动词用法的方法
  • 尊重 KVO 的原始设计目标,并故意不解决附带问题,特别是内存管理

呃,接待员模式是什么?

接待员设计模式解决了一个普遍问题,即将应用程序中一个执行上下文中发生的事件重定向到另一个执行上下文进行处理。

Objective-C 编程中的概念:接待员模式[https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/ReceptionistPattern/ReceptionistPattern.html#//apple_ref/doc/uid/TP40010810-CH13-SW107]

在 KVO 中,接待员模式体现为 observeValueForKeyPath:ofObject:change:context:。与直接调用回调相比,observeValueForKeyPath:ofObject:change:context: 的优点是可以在集中位置处理接收器的并发条件,而不是在每个回调方法中重复。这个特性确实有一些优点,但它带来了显著的代价。实现 observeValueForKeyPath:ofObject:change:context: 需要大量的模板代码,这些代码容易出错。

BCLKeyValueObservation 封装了接待员模式,并提供了目标动作风格模式。BCLKeyValueObservation 提供了与典型的 observeValueForKeyPath:ofObject:change:context: 实现相同的功能。它确保满足并发要求,然后调用特定方法来处理观察到的变化。

为什么使用目标动作风格模式而不是块?

块是一个强大的工具,但它们并不是解决所有问题的正确方案。块最适合处理短暂的、开/关的事件,例如枚举集合或异步任务完成。块不适用于对象之间的开放式通信。这其中的原因是因为块的内存管理行为。默认情况下,块会保留其作用域内的所有对象。如果块仅保留在栈上(从概念上讲),则不太可能引起内存管理问题,但一旦块被附加到对象图(通过存储在 @property 中)时,保留循环的可能性就会大大增加。

KVO是为了开放式通信而设计的。使用块作为KVO的回调机制使意外创建保留循环变得非常容易。当然,有处理由块引起的保留循环的技术,但更好的方法是在一开始就避免这种困境,并使用更合适的模式。

目标-操作模式,就像KVO一样,对内存管理没有影响。目标-操作和KVO都假设内存管理是由其他地方处理的。这是一个合理的假设。如果一个对象正在被观察,那么观察者对对象感兴趣,因此无论如何都应该保留它。

为什么称之为“目标-操作 样式的 模式”?

目标-操作模式旨在用于UI控件(视图)与控制器(如目标 IBAction)进行通信。目标-操作模式利用响应者链让动作根据UI的状态成为最相关的接收者。这种灵活性在模型-控制器通信中并未实现(或需要)。为了避免歧义和混淆,BCLKeyValueObservation使用 observerchangeHandler 而不是 targetaction

如何使用它?

@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