3种方式实现KVO并进行对比

简介:

KVO

KVO属于设计模式中的观察者模式,在观察者模式中,一个对象任何状态的变更都会通知另外的对改变感兴趣的对象。这些对象之间不需要知道彼此的存在,这其实是一种松耦合的设计。当某个属性变化的时候,我们通常使用这个模式去通知其它对象。

本人用3种方式来讲述KVO的使用,开始前新建一个对象Student类,用以监控Student类中name属性,源码如下

Student.h + Student.m

#import <Foundation/Foundation.h>

@interface Student : NSObject

@property (nonatomic, strong) NSString *name;

@end


#import "Student.h"

@implementation Student

@end

注:所有测试均在ARC环境下

1. 使用系统自带的KVO来测试

延时执行GCD函数

// 系统并发线程池中延时多少ms的block函数
void delayMicroSeconds(int64_t microSeconds, void(^block)())
{
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, microSeconds * USEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        block();
    });
}
实现细节
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 实例化对象
    Student *stu = [[Student alloc] init];
    stu.name = @"Y.X.";
    
    // 添加观察者
    [stu addObserver:self
          forKeyPath:@"name"
             options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
             context:nil];
    
    // 延时1000ms后改变stu的name属性值
    delayMicroSeconds(1000, ^{
       stu.name = @"Jane";
    });
}

监控的方法
// 监听的函数
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%@", change);
}

执行后的打印信息如下

-------------------------------------------------------------------------------------------------------------------------------------------------------------

2014-03-25 17:25:29.316 StudyKVOVer2[4342:60b] {
    kind = 1;
    new = Jane;
    old = "Y.X.";
}
2014-03-25 17:25:29.318 StudyKVOVer2[4342:60b] An instance 0x8c70030 of class Student was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x8c70110> (
<NSKeyValueObservance 0x8c70200: Observer: 0x8c6d0c0, Key path: name, Options: <New: YES, Old: YES, Prior: NO> Context: 0x0, Property: 0x8c700f0>
)

-------------------------------------------------------------------------------------------------------------------------------------------------------------

请注意,因为在ARC环境下,Student默认是weak属性,出了viewDidLoad方法后直接被回收了,如上面红字部分所描述的.

使用就如上面那样,很容易,但不是block实现的

 

2. 使用开源库 THObserversAndBinders 实现KVO

下载源码 https://github.com/th-in-gs/THObserversAndBinders

将文件夹 THObserversAndBinders 拖入到工程文件中,引入相关的头文件

将THObserver转化为强引用(必须的一步)

@interface RootViewController ()

{
    THObserver      *_observer;
}

@end

实现部分
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 实例化对象
    Student *stu = [[Student alloc] init];
    stu.name = @"Y.X.";

    // 创建监听者
    _observer = [THObserver observerForObject:stu keyPath:@"name" oldAndNewBlock:^(id oldValue, id newValue) {
        NSLog(@"stu changed, was %@, is now %@", oldValue, newValue);
    }];
    
    // 延时1000ms后改变stu的name属性
    delayMicroSeconds(1000, ^{
       stu.name = @"Jane";
    });
}

打印信息:

-------------------------------------------------------------------------------------------------------------------------------------------------------------

2014-03-25 17:37:04.269 StudyKVOVer2[4509:60b] stu changed, was Y.X., is now Jane
2014-03-25 17:37:04.272 StudyKVOVer2[4509:60b] An instance 0xa365ee0 of class Student was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0xa366990> (
<NSKeyValueObservance 0xa366a80: Observer: 0xa3668b0, Key path: name, Options: <New: YES, Old: YES, Prior: NO> Context: 0x1, Property: 0xa366970>
)

-------------------------------------------------------------------------------------------------------------------------------------------------------------

Student还是被回收了,与第一个例子是一致的

很明显,这次使用起来简单了不少,该开源库本身有着详细的使用样例,但可能会照成循环引用,引用原文如下

This stuff seems to be making a lot of retain cycles and leaks...

I suspect you're doing something like this:

_observerIvar = [THObserver observerForObject:_objectIvar keyPath:@"propertyToObserve" block:^{
    NSLog(@"propertyToObserve changed, is now %@", _objectIvar.propertyToObserve);
}];

This will create a retain cycle. The reference of _objectIvar inside the block will cause the block - and hence the observer - to strongly retain self. The observer is in turn retained by self when you assign it to _observerIvar, creating the cycle (self retains_observerIvar, which retains the block, which retains self).

You can instead do something like this:

MyObject *blockObject = _objectIvar;
_observerIvar = [THObserver observerForObject:blockObject keyPath:@"propertyToObserve" block:^{
    NSLog(@"propertyToObserve changed, is now %@", blockObject.propertyToObserve);
}];

or:

__weak MySelf *weakSelf = self;
_observerIvar = [THObserver observerForObject:self.objectProperty keyPath:@"propertyToObserve" block:^{
    NSLog(@"propertyToObserve changed, is now %@", weakSelf.objectProperty.propertyToObserve);
}];

And remember to ensure that the observer is not observing by the time that the object in _objectIvar is released (e.g. by calling[_observerIvar stopObserving] in your dealloc).

(Thanks to Peter Steinberger for pointing out that this could use elucidation.)

 

3. 使用开源库 FBKVOController 实现KVO

下载源码 https://github.com/facebook/KVOController

将 FBKVOController.h FBKVOController.m 拖到工程中引入头文件即可

Key-value observing is a particularly useful technique for communicating between layers in a Model-View-Controller application. KVOController builds on Cocoa's time-tested key-value observing implementation. It offers a simple, modern API, that is also thread safe. Benefits include:

KVO在MVC架构的应用中,在其层级之间的交互上十分有用,KVOController是在Cocoa上KVO实现的,他提供了一个简单地API接口,而且是线程安全的,好处如下所示:

  • Notification using blocks, custom actions, or NSKeyValueObserving callback.
  • No exceptions on observer removal.
  • Implicit observer removal on controller dealloc.
  • Improved performance when using NSKeyValueObservingInitial.
  • Thread-safety with special guards against observer resurrection – rdar://15985376.
  • 监听可以使用blocks,自定义actions或者NSKeyValueObserving回调
  • 在移除监听时不会出现异常
  • 当controller释放时该监听才被移除
  • 提升了一些效果,当在使用NSKeyValueObservingInitial
  • 线程安全

For more information on KVO, see Apple's Introduction to Key-Value Observing.

使用细节:

@interface RootViewController ()

{
    FBKVOController *_KVOController;
}

@property (nonatomic, strong) Student *stu;

@end

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 初始化对象
    _stu = [[Student alloc] init];
    _stu.name = @"Y.X.";

    // 初始化监听者
    _KVOController = [FBKVOController controllerWithObserver:self];
    
    // 开始监听
    [_KVOController observe:_stu
                    keyPath:@"name"
                    options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
                      block:^(id observer, id object, NSDictionary *change) {
                          NSLog(@"%@", change);
                      }];
    
    // 延时1000ms后改变stu的属性
    delayMicroSeconds(1000, ^{
        _stu.name = @"Jane";
    });
}

其打印信息:

-------------------------------------------------------------------------------------------------------------------------------------------------------------

2014-03-25 17:53:42.806 StudyKVOVer2[4737:60b] {
    kind = 1;
    new = Jane;
    old = "Y.X.";
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------

本次将 Student 对象设置成强引用,所以没有出现上面出现的问题,注意,经测试,FBKVOController也必须是强引用

 

心得:

1. KVO系统的没有block实现的方式,还要注意什么时候释放,不怎么好用

2. 使用开源库的KVO可以使用block,方便

3. 推荐使用 FBKVOController 的KVO实现,简单且线程安全

4. ARC和非ARC有很大区别,本人开始时由于没有想到弱引用问题而无法看到想要的现象,也是学习的一个过程


目录
相关文章
KVO的使用(键值监听)
KVO的使用(键值监听)
45 0
|
Android开发 iOS开发
iOS开发:KVC与KVO
KVC 就是键值编码(key-value-coding),可以直接访问对象的属性,或者给对象的属性赋值。黑魔法之一,很多高级的iOS开发技巧都是基于KVC实现的。 KVO 是键值观察者(key-value-observing)。实现方式:通过对某个对象的某个属性添加观察者,当该属性改变,就会调用”observeValueForKeyPath:”方法,为我们提供一个“对象值改变了!”的时机进行一些操作。
204 0
iOS开发:KVC与KVO
|
Java iOS开发
【iOS 开发】Objective - C 面向对象 - 方法 | 成员变量 | 隐藏封装 | KVC | KVO | 初始化 | 多态(二)
【iOS 开发】Objective - C 面向对象 - 方法 | 成员变量 | 隐藏封装 | KVC | KVO | 初始化 | 多态(二)
116 0
|
存储 安全 C语言
【iOS 开发】Objective - C 面向对象 - 方法 | 成员变量 | 隐藏封装 | KVC | KVO | 初始化 | 多态(一)
【iOS 开发】Objective - C 面向对象 - 方法 | 成员变量 | 隐藏封装 | KVC | KVO | 初始化 | 多态(一)
161 0
|
iOS开发 开发者 编译器
iOS开发--通知,代理,KVO的区别,以及通知的多线程问题
1. delegate 当我们第一次编写ios应用时,我们注意到不断的在使用“delegate”,并且贯穿于整个SDK。delegation模式不是IOS特有的模式,而是依赖与你过去拥有的编程背景。针对它的优势以及为什么经常使用到,这种模式可能不是很明显的。
KVO分析
KVO 概述 KVO的全称是NSKeyValueObserving,对象采用的一种非正式协议,当其他对象的指定属性发生变化时,通知对象。由于KVO的机制,只对对象的属性起作用,一般继承自NSObject都支持KVO。
2294 0
KVO 解析
KVO解析(一) —— 基本了解KVO解析(二) —— 一个简单的KVO实现KVO解析(三) —— KVO合规性KVO解析(四) —— Faults and KVO Notifications
819 0
|
API iOS开发 编译器
老调重弹:对kvo的封装思路
关于kvo,kvo能做什么? kvo作为cocoa框架的重要特性之一,在底层框架中被大量使用。在特定的场合使用该特性往往能够带来难以想象的好处,让整个方案变得相当简洁和优雅。
1145 0