iOS 中監(jiān)聽某個值的改變有哪些方法店溢?
在一個復(fù)雜的叁熔,有狀態(tài)的系統(tǒng)中,當(dāng)一個對象的狀態(tài)發(fā)生改變床牧,如何通知系統(tǒng)荣回,并對狀態(tài)改變做出相應(yīng)的行為是必需考慮的一個問題,在iOS中為這類問題提供了4種解決方法:
NSNotifiactaion 和 NSNotificationCenter:通知中心
Delegates:代理戈咳,
Callback:回調(diào)心软,
KVO(Key-Value Observing):鍵值觀察
Key-Value Observing (簡寫為KVO):當(dāng)指定的對象的屬性被修改了,允許對象接受到通知的機(jī)制著蛙。每次指定的被觀察對象的屬性被修改的時候删铃,KVO都會自動的去通知相應(yīng)的觀察者。
KVO 是什么踏堡?
Objective-C 中的鍵(key)-值(value)觀察(KVO)并不是什么新鮮事物猎唁,它來源于設(shè)計模式中的觀察者模式,其基本思想就是:
一個目標(biāo)對象管理所有依賴于它的觀察者對象顷蟆,并在它自身的狀態(tài)改變時主動通知觀察者對象诫隅。這個主動通知通常是通過調(diào)用各觀察者對象所提供的接口方法來實現(xiàn)的。觀察者模式較完美地將目標(biāo)對象與觀察者對象解耦帐偎。
KVO 的用法
- 如果要監(jiān)聽“對象A”屬性值的改變逐纬,先要為"對象A"的屬性注冊觀察者(假設(shè)觀察者為“對象B”)。
- (void)addObserver:(NSObject * _Nonnull)anObserver
forKeyPath:(NSString * _Nonnull)keyPath
options:(NSKeyValueObservingOptions)options
context:(void * _Nullable)context
anObserver
: 觀察者削樊,注冊 KVO 通知的對象. 其必須實現(xiàn)方法 observeValueForKeyPath:ofObject:change:context:.
keyPath
: 被觀察的屬性豁生,其不能為nil.
options
: 表示要監(jiān)聽那些通知,一般為寫 0
context
: 一些其他的需要傳遞給觀察者的上下文信息,通常設(shè)置為 nil
options
解釋如下:
NSKeyValueObservingOptionNew
: change 字典中包含 key 改變后的新值
NSKeyValueObservingOptionOld
: change 字典中包含 key 改變前的舊值
NSKeyValueObservingOptionInitial
: 在添加觀察者的時候立即發(fā)送一個通知給觀察者,并且是在注冊觀察者方法返回之前
NSKeyValueObservingOptionPrior
: 如果指定甸箱,則在每次修改屬性時眼刃,會在修改通知被發(fā)送之前預(yù)先發(fā)送一條通知給觀察者,這與-willChangeValueForKey:被觸發(fā)的時間是相對應(yīng)的摇肌。這樣擂红,在每次修改屬性時,實際上是會發(fā)送兩條通知围小。
2. 觀察者“對象B”實現(xiàn) observeValueForKeyPath:ofObject:change:context:
. 方法
3. “對象A”移除監(jiān)聽者 - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
;
另外注意:keyPath
即被觀察的屬性只能是 NSString
類型
- 注意點:在哪個線程觸發(fā)監(jiān)聽(修改了值)昵骤,監(jiān)聽方法就會在哪個線程中執(zhí)行
KVO 的實現(xiàn)原理
鍵值編碼和鍵值觀察是根據(jù) isa-swizzling 技術(shù)來實現(xiàn)的,主要依據(jù)runtime的強(qiáng)大動態(tài)能力肯适。
當(dāng)某個類的對象第一次被觀察時变秦,系統(tǒng)就會在運行期動態(tài)地創(chuàng)建該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的 setter 方法框舔。派生類在被重寫的 setter 方法實現(xiàn)真正的通知機(jī)制蹦玫。
同時派生類還重寫了 class 方法以“欺騙”外部調(diào)用者它就是起初的那個類。然后系統(tǒng)將這個對象的 isa 指針指向這個新誕生的派生類刘绣,因此這個對象就成為該派生類的對象了樱溉,因而在該對象上對 setter 的調(diào)用就會調(diào)用重寫的 setter,從而激活鍵值通知機(jī)制纬凤。此外福贞,派生類還重寫了 dealloc 方法來釋放資源。
- 重寫新類的 class 方法
重寫class方法是為了我們調(diào)用它的時候返回跟重寫繼承類之前同樣的內(nèi)容停士。
//打印如下內(nèi)容:
NSLog(@"self->isa:%@",self->isa);
NSLog(@"self class:%@",[self class]);
//在建立KVO監(jiān)聽前挖帘,打印結(jié)果為:
self->isa:Person
self class:Person
//在建立KVO監(jiān)聽之后,打印結(jié)果為:
self->isa:NSKVONotifying_Person
self class:Person
- 重寫新類的 set 方法
新類會重寫對應(yīng)的set方法恋技,是為了在set方法中增加另外兩個方法的調(diào)用:
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
其中拇舀,didChangeValueForKey
:方法負(fù)責(zé)調(diào)用:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
如果沒有 setter 方法,那么 -setValue:forKey 方法會直接調(diào)用:
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
如果在沒有使用鍵值編碼且沒有使用適當(dāng)命名的訪問起方法的時候蜻底,我們只需要顯示調(diào)用上述兩個方法骄崩,同樣可以使用KVO!
例子
//
// ViewController.m
// KVO test
//
// Created by KeSen on 15/9/1.
// Copyright (c) 2015年 KeSen. All rights reserved.
//
#import "ViewController.h"
#import "KSBaby.h"
@interface ViewController ()
{
KSBaby *_baby;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
KSBaby *baby = [[KSBaby alloc] initWithHungry:@"cryed" thirst:NO];
_baby = baby;
// 一般如下使用
// [_baby addObserver:self forKeyPath:@"cry" options: 0 context: nil];
// 在 self 中監(jiān)聽 _baby 的 cry 屬性變化
[_baby addObserver:self forKeyPath:@"cry" options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld| NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionPrior context:@"ssss"];
}
- (void)dealloc {
[_baby removeObserver:self forKeyPath:@"cry"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"keyPath: %@\n change: %@\n context: %@\n", keyPath, change, context);
}
- (IBAction)click:(UIButton *)sender {
_baby.cry = @"crying";
}
@end
//
// ViewController.m
// KVO test
//
// Created by KeSen on 15/9/1.
// Copyright (c) 2015年 KeSen. All rights reserved.
//
#import "ViewController.h"
#import "KSBaby.h"
@interface ViewController ()
{
KSBaby *_baby;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
KSBaby *baby = [[KSBaby alloc] initWithHungry:@"cryed" thirst:NO];
_baby = baby;
// 一般如下使用
// [_baby addObserver:self forKeyPath:@"cry" options: 0 context: nil];
// 在 self 中監(jiān)聽 _baby 的 cry 屬性變化
[_baby addObserver:self forKeyPath:@"cry" options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld| NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionPrior context:@"ssss"];
}
- (void)dealloc {
[_baby removeObserver:self forKeyPath:@"cry"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"keyPath: %@\n change: %@\n context: %@\n", keyPath, change, context);
}
- (IBAction)click:(UIButton *)sender {
_baby.cry = @"crying";
}
@end
那么:程序啟動時輸出:
點擊按鈕后輸出:
KVO與多線程
注意點:在哪個線程觸發(fā)監(jiān)聽(修改了值)朱躺,監(jiān)聽方法就會在哪個線程中執(zhí)行
//
// ViewController.m
// KVO test
//
// Created by KeSen on 15/9/1.
// Copyright (c) 2015年 KeSen. All rights reserved.
//
#import "ViewController.h"
#import "KSBaby.h"
#import "FBKVOController.h"
@interface ViewController ()
{
KSBaby *_baby;
FBKVOController *_observer;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
KSBaby *baby = [[KSBaby alloc] initWithHungry:@"cryed" thirst:NO];
_baby = baby;
// NSKeyValueObservingOptionNew : change 字典中包含 key 改變后的新值
// NSKeyValueObservingOptionOld : change 字典中包含 key 改變前的舊值
// NSKeyValueObservingOptionInitial : 在添加觀察者的時候立即發(fā)送一個通知給觀察者,并且是在注冊觀察者方法返回之前
// NSKeyValueObservingOptionPrior : 如果指定刁赖,則在每次修改屬性時,會在修改通知被發(fā)送之前預(yù)先發(fā)送一條通知給觀察者长搀,這與-willChangeValueForKey:被觸發(fā)的時間是相對應(yīng)的宇弛。這樣,在每次修改屬性時源请,實際上是會發(fā)送兩條通知枪芒。
// 1. 一般用法如下
[_baby addObserver:self forKeyPath:@"cry" options: 0 context: nil];
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// [_baby addObserver:self forKeyPath:@"cry" options: NSKeyValueObservingOptionNew context:@"ssss"];
//
// });
// 2. 第三方庫 FBKVOController 的用法
_observer = [[FBKVOController alloc] initWithObserver:self];
[_observer observe:_baby keyPath:@"cry" options:NSKeyValueObservingOptionNew block:^(id observer, id object, NSDictionary *change) {
NSLog(@"%@, %@", @"FBKVOController", change[NSKeyValueChangeNewKey]);
}];
}
- (void)dealloc {
[_baby removeObserver:self forKeyPath:@"cry"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"keyPath: %@\n change: %@\n context: %@\n", keyPath, change, context);
}
- (IBAction)click:(UIButton *)sender {
// _baby.cry = @"crying";
// 1. 注意點:在哪個線程觸發(fā)監(jiān)聽(修改了值)彻况,監(jiān)聽方法就會在哪個線程中執(zhí)行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_baby.cry = @"cry again";
});
}
@end
KVO 監(jiān)聽 NSMutableArray 的內(nèi)容變化
- 將數(shù)組封裝到一個對象中
- 給這個對象添加 KVO 監(jiān)聽
-
使用 [self.arrayObject mutableArrayValueForKey:@"array”] 獲取數(shù)組對象,對數(shù)組對象今天添加刪除操作舅踪,只有這樣觸發(fā)的數(shù)組才會觸發(fā) kvo
參考:http://www.cppblog.com/kesalin/archive/2012/11/17/kvo.html
http://ningandjiao.iteye.com/blog/2009729
http://www.bkjia.com/IOSjc/993206.html
http://blog.csdn.net/wzzvictory/article/details/9674431