KVO Key-Value Observing 鍵值監(jiān)聽(tīng)
KVO是一個(gè)觀察者模式。觀察一個(gè)對(duì)象的屬性,注冊(cè)一個(gè)指定的路徑,若這個(gè)對(duì)象的的屬性修改,則KVO會(huì)自動(dòng)通知觀察者。
使用步驟
1.注冊(cè)監(jiān)聽(tīng)
/**
* 注冊(cè)一個(gè)監(jiān)聽(tīng)
*
* @param observer 觀察者
* @param keyPath 屬性名字
* @param options 屬性的變化
* @param context void類(lèi)型
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
2.回調(diào)函數(shù)
/**
* 當(dāng)監(jiān)控的某個(gè)屬性的值改變了就會(huì)調(diào)用
*
* @param keyPath 屬性名(哪個(gè)屬性改了?)
* @param object 哪個(gè)對(duì)象的屬性被改了?
* @param change 屬性的修改情況(屬性原來(lái)的值、屬性最新的值)
* @param context void類(lèi)型
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"%@對(duì)象的%@屬性改變了:%@", object, keyPath, change);
}
3.移除觀察,釋放內(nèi)存,在dealloc函數(shù)釋放
/**
* 移除觀察者
*
* @param observer 觀察者
* @param keyPath 觀察的屬性
*/
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
KVO的觀察者是由兩種模式的。一種是自動(dòng)通知,一種是手動(dòng)通知。
自動(dòng)通知自動(dòng)監(jiān)聽(tīng)對(duì)象的屬性,不管這個(gè)屬性的前后屬性變化的值是否一樣,都會(huì)通知觀察者。
手動(dòng)通知重寫(xiě)willChangeValueForKey:和didChangeValueForKey: 方法通知觀察者。
一般都是用自動(dòng)通知,方便快捷。
下面兩種寫(xiě)法,都會(huì)舉例說(shuō)明。
自動(dòng)通知,主要看監(jiān)聽(tīng)回調(diào)的分析
場(chǎng)景:模擬人年齡的變化,看接受通知的次數(shù)
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[Person alloc]init];
self.person.age = 15;
/**
* 注冊(cè)監(jiān)聽(tīng)
*/
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
/**
* 第二次賦值 20
*/
self.person.age = 20;
/**
* 再次賦值 20
*/
self.person.age = 20;
/**
* 詳細(xì)見(jiàn)控制臺(tái)輸出,只比較新舊值
*/
}
/**
* 監(jiān)聽(tīng)回調(diào)
*/
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
NSLog(@"\n change = %@ \n keyPath =%@ object =%@ context=%@",change ,keyPath,object,context);
/** ============================ 控制臺(tái)輸出 ===================================
*
change = {
kind = 1;
new = 20; --------->新的值是 20 注冊(cè)監(jiān)聽(tīng)后,observeValueForKeyPath會(huì)回調(diào)輸出
old = 15; --------->舊的值是 15 注冊(cè)監(jiān)聽(tīng)前,這個(gè)屬性初始化時(shí)候就是15。
}
keyPath =age object =<Person: 0x7feec962a9d0> context=(null)
change = {
kind = 1;
new = 20; --------->新的值是 20 再次傳入20,observeValueForKeyPath這個(gè)還是會(huì)回調(diào)輸出
old = 20; --------->舊的值是 20
}
keyPath =age object =<Person: 0x7feec962a9d0> context=(null)
*/
/**
* 由控制臺(tái)輸出結(jié)果得出結(jié)論,只要是對(duì)屬性進(jìn)行改變,不管屬性的值是否變化,不區(qū)分新舊值的變化,observeValueForKeyPath都是回調(diào),通知觀察者的。
*/
}
-(void)dealloc{
/**
* 移除觀察
*/
[self.person removeObserver:self forKeyPath:@"age"];
}
手動(dòng)通知
場(chǎng)景:模擬人年齡的變化,看接受通知的次數(shù)
需要在Person類(lèi)里面重寫(xiě)方法,具體看實(shí)現(xiàn)代碼
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[Person alloc]init];
self.person.age = 15;
/**
* 注冊(cè)監(jiān)聽(tīng)
*/
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
/**
* 第二次賦值 20
*/
self.person.age = 20;
/**
* 再次賦值 20
*/
self.person.age = 20;
/**
* 詳細(xì)見(jiàn)控制臺(tái)輸出,只比較新舊值
*/
}
/**
* 監(jiān)聽(tīng)回調(diào)
*/
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
NSLog(@"\n change = %@ \n keyPath =%@ object =%@ context=%@",change ,keyPath,object,context);
/** ============================ 控制臺(tái)輸出 ===================================
*
change = {
kind = 1;
new = 20; --------->新的值是 20 注冊(cè)監(jiān)聽(tīng)后,observeValueForKeyPath會(huì)回調(diào)輸出
old = 15; --------->舊的值是 15 注冊(cè)監(jiān)聽(tīng)前,這個(gè)屬性初始化時(shí)候就是15。
}
keyPath =age object =<Person: 0x7feec962a9d0> context=(null)
*/
/**
* 和自動(dòng)通知輸出作對(duì)比,這樣明白理解。主要是在Person類(lèi)里面重寫(xiě)了 automaticallyNotifiesObserversForKey 這個(gè)方法。以及重寫(xiě)age的setter方法
* 由控制臺(tái)輸出結(jié)果得出結(jié)論,只要監(jiān)聽(tīng)對(duì)象的屬性值前后發(fā)生改變,observeValueForKeyPath就回調(diào),通知觀察者的。
*/
}
-(void)dealloc{
/**
* 移除觀察
*/
[self.person removeObserver:self forKeyPath:@"age"];
}
Person類(lèi)的實(shí)現(xiàn)
//
// Person.m
// KVO
//
// Created by XXXXXX on 15/11/9.
// Copyright ? 2015年 Simon. All rights reserved.
//
#import "Person.h"
@implementation Person
-(void)setAge:(float)age{
/**
* 判斷兩值是否一樣,一樣就不復(fù)賦值了
*/
if (_age!= age) {
[self willChangeValueForKey:@"age"];
_age = age;
[self didChangeValueForKey:@"age"];
}
}
/**
* 是否對(duì)這個(gè)key開(kāi)啟自動(dòng)發(fā)送通知
*
* @param theKey 監(jiān)聽(tīng)的屬性key
*
* @return 布爾值
*/
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"age"])
{
automatic = NO;
}
else
{
automatic=[super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
@end
觀察了自動(dòng)通知和手動(dòng)通知,各有所長(zhǎng),看你們喜歡哪個(gè)。記住手動(dòng)通知必須重寫(xiě)方法,只有新舊值前后不一樣才會(huì)通知。自動(dòng)通知就不管新舊值是否一樣,都說(shuō)告訴觀察者。
實(shí)現(xiàn)原理可以參考這個(gè)博客,寫(xiě)的很詳細(xì)。[深入淺出Cocoa]詳解鍵值觀察(KVO)及其實(shí)現(xiàn)機(jī)理][1]
[1]:http://blog.csdn.net/kesalin/article/details/8194240