Demo地址: https://github.com/hanhuitao/KVO-NSMutableArray.git
iOS 中 KVO (key-value-observing) 的原理,簡單來說就是重寫了被觀察屬性的 set 方法,一般情況下只有通過調(diào)用 set 方法對值進(jìn)行改變才會觸發(fā) KVO,直接訪問實例變量修改值是不會觸發(fā) KVO 的。
先上代碼
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong)NSMutableArray * array;
@end
@implementation ViewController
-(NSMutableArray*)array
{
if (!_array) {
_array=[NSMutableArray new];
}
return _array;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.array addObjectsFromArray:@[@"黑色",@"白色"]];
NSLog(@"初始化地址=%p",self.array);
[self addObserver:self forKeyPath:@"array" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// [self.array addObject:@"蒼白色"];
// [[self mutableArrayValueForKey:@"array"]addObject:@"蒼白色"];
NSLog(@"改變數(shù)據(jù)源之后=%p",self.array);
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
NSLog(@"keyPath=%@,object=%@,change=%@,context=%@",keyPath,object,change,context);
}
@end
我們注意看touchesBegan里面的兩行代碼
1.使用[self.array addObject:@"蒼白色"]添加對象
可以看出:是沒有觸發(fā)kvo的代理方法的,array的前后內(nèi)存地址不變
2.使用 [[self mutableArrayValueForKey:@"array"]addObject:@"蒼白色"]添加對象
可以看出:觸發(fā)了kvo的代理方法的,并且array的前后內(nèi)存地址發(fā)生了變化
為什么會這樣:
NSMutableArray在調(diào)用它的 addObject、removeObject 系列方法時,并不會觸發(fā)它自己的 set 方法。所以,對一個可變數(shù)組進(jìn)行觀察,在它加減元素時不會收到期望的消息。
因為KVO的本質(zhì)是系統(tǒng)監(jiān)測到或常量改變時,會添加上- (void)willChangeValueForKey:(NSString *)key和- (void)didChangeValueForKey:(NSString *)key方法來發(fā)送通知,所以一種解決方法是手動調(diào)用者兩個方法,但是并不推薦,你永遠(yuǎn)無法像系統(tǒng)一樣真正知道這個元素什么時候被改變。另一種便是利用使用mutableArrayValueForKey:了。
那么為什么mutableArrayValueForKey:這個方法可以監(jiān)聽dataSource內(nèi)部變化呢?
mutableArrayValueForKey:默認(rèn)采用的是搜索模式,在NSMutableArray搜索匹配insertObject:atIndex:和removeObjectAtIndex:等能引起容器內(nèi)部Object發(fā)生改變的方法,如果發(fā)現(xiàn)至少一個插入或者刪除方法,就會發(fā)送發(fā)送消息給原始接受者。