KVO 簡述
KVO<NSKeyValueObserving>,是一個非正式協(xié)議,它定義了對象之間觀察和通知狀態(tài)改變的通用機制。
我們可以監(jiān)聽一個對象的屬性,包括簡單屬性,一對一的關(guān)系,和一對多的關(guān)系。一對多關(guān)系的監(jiān)聽者會被告知集合變更的類型,以及哪些對象參與了變化。
NSObject提供了一個NSKeyValueObserving協(xié)議的默認(rèn)實現(xiàn),它為所有對象提供了一種自動發(fā)送修改通知的能力。我們可以通過禁用自動發(fā)送通知并使用這個協(xié)議提供的方法來手動實現(xiàn)通知的發(fā)送,以便更精確地去處理通知。
基礎(chǔ)方法的講解
使用KVO必須要滿足的條件和一般使用步驟
1.該對象必須支持KVC(凡是繼承自NSObject的類都支持KVC)
2.作為觀察者的對象必須實現(xiàn) -(void)observeValueForKeyPath:ofObject:change:context: 方法
3.被觀察的對象要用- (void)addObserver:forKeyPath:options:context:方法注冊觀察者
4.用完要移除。附上方法- (void)removeObserver:forKeyPath: 或者- (void)removeObserver:forKeyPath:context:
然后我們對KVO方法進(jìn)行詳細(xì)的講解。
1.- (void)addObserver:forKeyPath:options:context:觀察對象的添加 以及被觀察者的確定
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
解析:
1.此方法為減號方法 調(diào)用者即為被觀察者。
observer 此參數(shù)為觀察者 也就是監(jiān)控者 它可以通過實現(xiàn)
-(void)observeValueForKeyPath:ofObject:change:context:對被觀察者進(jìn)行觀察。forKeyPath 所觀察屬性的名字(宏觀看法,后續(xù)會詳解)
4.options 這個參數(shù)可以理解為所需觀察的設(shè)置選項
可以很清楚的看到,其中包含了四種值,分別為:
NSKeyValueObservingOptionNew:提供更改前的值
NSKeyValueObservingOptionOld:提供更改后的值
NSKeyValueObservingOptionInitial:觀察最初的值(在注冊觀察服務(wù)時會調(diào)用一次觸發(fā)方法)
NSKeyValueObservingOptionPrior:分別在值修改前后觸發(fā)方法(即一次修改有兩次觸發(fā))
稍后會對此進(jìn)行案例的分析。
2.-(void)observeValueForKeyPath:ofObject:change:context: 觀察者觀察方法的實現(xiàn)
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
keyPath 被觀察的屬性 上個方法中已經(jīng)講解 屬于同一個參數(shù)
object被觀察者所屬的對象 也就是本官者本身
change 這是一個字典,它包含了屬性被修改的一些信息。
這個字典中包含的值會根據(jù)我們在添加觀察者時(addObserver方法)設(shè)置的options參數(shù)有所變化。context 添加觀察者時的上下文信息,它可以被用作區(qū)分那些綁定同一個keypath的不同對象的觀察者。
比如說觀察一些繼承自同一個父類的子類,而這些子類都有一個相同的keyPath。
context:(nullable void *)context;
關(guān)于方法講解 我們到此為止。我們接下來進(jìn)行案例分析
3- (void)removeObserver:forKeyPath: 或者- (void)removeObserver:forKeyPath:context:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
注意:
可能導(dǎo)致崩潰或內(nèi)容出錯的情況
1.被觀察的對象銷毀掉了(被觀察的對象是一個局部變量)
2.觀察者被釋放掉了,但是沒有移除監(jiān)聽(如模態(tài)推出,push,pop等)
2.注冊的監(jiān)聽沒有移除掉,又重新注冊了一遍監(jiān)聽
4 案例分析
1. Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,copy)NSString *age;
@end
2. Person.m
#import "Person.h"
@implementation Person
@end
3.調(diào)用 options
3.1 options ---> NSKeyValueObservingOptionNew
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic,strong)Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//1.注冊觀察者,實施監(jiān)聽
self.person = [[Person alloc]init];
[self.person addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionNew
context:nil];
self.person.age = @"18";
}
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context{
NSLog(@"keyPath:%@",keyPath);
NSLog(@"object:%@",object);
NSLog(@"change:%@",change);
}
打印數(shù)據(jù):
2018-05-21 15:32:11.792849+0700 KVODemo[51410:1650569] keyPath:age
2018-05-21 15:32:11.793007+0700 KVODemo[51410:1650569] object:<Person: 0x600000011660>
2018-05-21 15:32:11.793198+0700 KVODemo[51410:1650569] change:{
kind = 1;
new = 18;
}
3.2 options ---> NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
打印數(shù)據(jù):
2018-05-21 15:35:28.030530+0700 KVODemo[51467:1652953] keyPath:age
2018-05-21 15:35:28.030690+0700 KVODemo[51467:1652953] object:<Person: 0x604000017fd0>
2018-05-21 15:35:28.030885+0700 KVODemo[51467:1652953] change:{
kind = 1;
new = 18;
old = "<null>";
}
3.3 options ---> NSKeyValueObservingOptionInitial
2018-05-21 15:42:56.834653+0700 KVODemo[51666:1660465] keyPath:age
2018-05-21 15:42:56.834798+0700 KVODemo[51666:1660465] object:<Person: 0x604000205260>
2018-05-21 15:42:56.834965+0700 KVODemo[51666:1660465] change:{
kind = 1;
}
2018-05-21 15:42:56.835267+0700 KVODemo[51666:1660465] keyPath:age
2018-05-21 15:42:56.835364+0700 KVODemo[51666:1660465] object:<Person: 0x604000205260>
2018-05-21 15:42:56.835492+0700 KVODemo[51666:1660465] change:{
kind = 1;
}
總結(jié):我們發(fā)現(xiàn)當(dāng)options 是 NSKeyValueObservingOptionInitial監(jiān)聽方法只在注冊觀察者者的時候 調(diào)用了一次 所以此方法一般用的不多,可以用作初始值的獲取。
3.4 options ---> NSKeyValueObservingOptionPrior
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic,strong)Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//1.注冊觀察者,實施監(jiān)聽
self.person = [[Person alloc]init];
[self.person addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionPrior
context:nil];
self.person.age = @"18";
self.person.age = @"19";
}
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context{
NSLog(@"keyPath:%@",keyPath);
NSLog(@"object:%@",object);
NSLog(@"change:%@",change);
}
打印數(shù)據(jù):
2018-05-21 15:48:51.106960+0700 KVODemo[51814:1666067] keyPath:age
2018-05-21 15:48:51.107172+0700 KVODemo[51814:1666067] object:<Person: 0x60000000ba00>
2018-05-21 15:48:51.107394+0700 KVODemo[51814:1666067] change:{
kind = 1;
notificationIsPrior = 1;
}
2018-05-21 15:48:51.107520+0700 KVODemo[51814:1666067] keyPath:age
2018-05-21 15:48:51.107615+0700 KVODemo[51814:1666067] object:<Person: 0x60000000ba00>
2018-05-21 15:48:51.107734+0700 KVODemo[51814:1666067] change:{
kind = 1;
}
2018-05-21 15:48:51.107831+0700 KVODemo[51814:1666067] keyPath:age
2018-05-21 15:48:51.107922+0700 KVODemo[51814:1666067] object:<Person: 0x60000000ba00>
2018-05-21 15:48:51.108045+0700 KVODemo[51814:1666067] change:{
kind = 1;
notificationIsPrior = 1;
}
2018-05-21 15:48:51.108123+0700 KVODemo[51814:1666067] keyPath:age
2018-05-21 15:48:51.108284+0700 KVODemo[51814:1666067] object:<Person: 0x60000000ba00>
2018-05-21 15:48:51.108414+0700 KVODemo[51814:1666067] change:{
kind = 1;
}
總結(jié):
我們發(fā)現(xiàn)我們進(jìn)行兩次賦值,但是卻調(diào)用了四次,正如官方文檔解釋一般 它會在 調(diào)用之前調(diào)用之后 分別調(diào)用一次。
未完,待續(xù)