
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有很大區別,本人開始時由於沒有想到弱引用問題而無法看到想要的現象,也是學習的一個過程
