参考官方文档(developer.apple.com/library/arc… )html
先来看方法:- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;web
其中observer和keyPath很容易理解,下面来详细讲解options和context数组
options 有以下四中配置安全
1.NSKeyValueObservingOptionNew 观察者回调监听中change字典中包含改变后的值bash
2.NSKeyValueObservingOptionOld 观察者回调监听中change字典中包含改变前的值markdown
3.NSKeyValueObservingOptionInitial 注册后马上触发KVO通知架构
可是须要注意的是 NSKeyValueObservingOptions参数同时指定了NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial,首次触发KVO change字典中并不包含old值app
例子1:框架
#import "ViewController.h" @interface Person : NSObject @property (nonatomic, assign) NSUInteger age; @end @implementation Person @end @interface ViewController () @property (nonatomic, strong) Person * person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _person = [[Person alloc] init]; //添加属性观察 [_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil]; //触发KVO [_person setValue:@300 forKey:@"age"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%s",__func__); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); } - (void)dealloc { NSLog(@"%s",__func__); //移除 [_person removeObserver:self forKeyPath:@"age"]; } @end 复制代码
打印结果以下:oop
2019-12-26 15:26:11.683096+0800 KVC&KVO[3325:172468] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 15:26:11.683215+0800 KVC&KVO[3325:172468] keyPath = age
2019-12-26 15:26:11.683373+0800 KVC&KVO[3325:172468] change = {
kind = 1;
new = 300;
old = 0;
}
复制代码
结果分析: 由于options同时指定了NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld,所以KVO回调接口字典change中同时包含new 和 old,若是只指定了其中一个,那么回调字典中就只有对应的一个
接着咱们将options参数改成: NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld| NSKeyValueObservingOptionInitial
2019-12-26 15:32:44.449559+0800 KVC&KVO[3351:175049] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 15:32:44.449682+0800 KVC&KVO[3351:175049] keyPath = age
2019-12-26 15:32:44.449822+0800 KVC&KVO[3351:175049] change = {
kind = 1;
new = 0;
}
2019-12-26 15:32:44.450094+0800 KVC&KVO[3351:175049] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 15:32:44.450185+0800 KVC&KVO[3351:175049] keyPath = age
2019-12-26 15:32:44.450317+0800 KVC&KVO[3351:175049] change = {
kind = 1;
new = 300;
old = 0;
}
复制代码
结果分析:
1.因为指定了NSKeyValueObservingOptionInitial,因此一旦添加观察,就马上触发KVO(你能够将 [_person setValue:@300 forKey:@"age"]注释掉,它同样会触发KVO,也就是change字典中new =0的那一次).
2.options指定了 NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld| NSKeyValueObservingOptionInitial三个,可是首次马上触发的KVO回调字典并不包含old值
接着在Person类中添加:
//该方法用于修改是否容许自动KVO通知。默认容许返回YES,这里咱们修改成NO + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{ NSLog(@"%s",__func__); return NO; } 复制代码
再次运行:
2019-12-26 15:39:48.684258+0800 KVC&KVO[3389:178028] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 15:39:48.684373+0800 KVC&KVO[3389:178028] keyPath = age
2019-12-26 15:39:48.684523+0800 KVC&KVO[3389:178028] change = {
kind = 1;
new = 0;
}
2019-12-26 15:39:48.684614+0800 KVC&KVO[3389:178028] +[Person automaticallyNotifiesObserversForKey:]
复制代码
结果分析:
接着将options指定 NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld| NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionPrior 同时Person容许自动KVO
运行打印:
2019-12-26 15:49:26.326606+0800 KVC&KVO[3435:181969] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 15:49:26.326725+0800 KVC&KVO[3435:181969] keyPath = age
2019-12-26 15:49:26.326886+0800 KVC&KVO[3435:181969] change = {
kind = 1;
new = 0;
}
2019-12-26 15:49:26.326981+0800 KVC&KVO[3435:181969] +[Person automaticallyNotifiesObserversForKey:]
2019-12-26 15:49:26.327225+0800 KVC&KVO[3435:181969] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 15:49:26.327311+0800 KVC&KVO[3435:181969] keyPath = age
2019-12-26 15:49:26.327438+0800 KVC&KVO[3435:181969] change = {
kind = 1;
notificationIsPrior = 1;
old = 0;
}
2019-12-26 15:49:26.327528+0800 KVC&KVO[3435:181969] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 15:49:26.327608+0800 KVC&KVO[3435:181969] keyPath = age
2019-12-26 15:49:26.327884+0800 KVC&KVO[3435:181969] change = {
kind = 1;
new = 300;
old = 0;
}
复制代码
结果分析:
先来官方文档怎么说的.
使用方法addObserver:forKeyPath:options:context:添加观察时,消息中的上下文指针能够包含任意数据,而且这些数据将在相应的更改通知中传递回观察者. context能够指定NULL并彻底依靠键路径字符串来肯定更改通知的来源,可是这种方法可能会致使对象的父类因为不一样的缘由也观察到相同的键路径而致使问题. context能够提供一种更安全,更可扩展的方法确保观察者收到的通知是发给观察者的,而不是父类对象的. 一个良好的上下文对象能够是类中惟一命名的静态变量的地址,在父类或子类中以相似方式选择的上下文通常不会重复.能够为整个类选择一个上下文,而后依靠通知消息中的关键路径字符串来肯定更改的内容.此外,也能够为每一个观察到的键路径建立一个不一样的上下文,从而彻底不须要进行字符串比较,从而能够更有效地进行通知解析.
首先咱们不使用context,对单个对象简单场景(例如上面的例子),貌似没发现什么不妥~ 可是一旦稍微有点复杂,不使用context那么问题就很明显了!
问题1: 同一个类的不一样对象,须要添加观察它们的age属性,怎么处理? 因为要观察的属性都是age,也就是keyPath相同,可是Object对象不一样,那么你就不能依靠keyPath来区分通知来源了.此时你很容易想到使用Object来区分,这样作也确实能够. 例子以下:
#import "ViewController.h" @interface Person : NSObject @property (nonatomic, assign) NSUInteger age; @end @implementation Person + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{ NSLog(@"%s",__func__); return [super automaticallyNotifiesObserversForKey:key]; } @end @interface ViewController () @property (nonatomic, strong) Person * person,*person2; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _person = [[Person alloc] init]; _person2 = [[Person alloc] init]; //添加属性观察 [_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil]; [_person2 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil]; //触发KVO [_person setValue:@30 forKey:@"age"]; [_person2 setValue:@40 forKey:@"age"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if(object == _person){ NSLog(@"%s",__func__); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); //接着让_person去处理一些事 NSLog(@"已收到_person通知,让_person对象去作些事"); } else if(object == _person2){ NSLog(@"%s",__func__); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); //接着让_person2去处理一些事 NSLog(@"已收到_person2通知,让_person2对象去作些事"); }else{ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)dealloc { NSLog(@"%s",__func__); //移除 [_person removeObserver:self forKeyPath:@"age"]; [_person2 removeObserver:self forKeyPath:@"age"]; } @end 复制代码
2019-12-26 16:22:41.245756+0800 KVC&KVO[3546:194556] +[Person automaticallyNotifiesObserversForKey:]
2019-12-26 16:22:41.246065+0800 KVC&KVO[3546:194556] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 16:22:41.246162+0800 KVC&KVO[3546:194556] keyPath = age
2019-12-26 16:22:41.246317+0800 KVC&KVO[3546:194556] change = {
kind = 1;
new = 30;
old = 0;
}
2019-12-26 16:22:41.246411+0800 KVC&KVO[3546:194556] 已收到_person通知,让_person对象去作些事
2019-12-26 16:22:41.246512+0800 KVC&KVO[3546:194556] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 16:22:41.246601+0800 KVC&KVO[3546:194556] keyPath = age
2019-12-26 16:22:41.246711+0800 KVC&KVO[3546:194556] change = {
kind = 1;
new = 40;
old = 0;
}
2019-12-26 16:22:41.246797+0800 KVC&KVO[3546:194556] 已收到_person2通知,让_person2对象去作些事
复制代码
经过object对象来区分对象通知来源(包括父类子类添加相同的属性观察,也均可以,可是不建议这样作,由于可扩展性差,不够安全),简单场景确实行得通,由于这里也不够复杂.说到这里,那咱们就来点复杂的
问题:2个不一样类的对象,它们的属性都不相同,暂且取其中两个属性来进行观察,怎么处理?那么如今你要作的就是怎么区分通知的来源,若是不使用context,你所想到的就是多重嵌套来判断通知来源,有点相似下面的伪代码:
if(object == 对象1){ //p1,p2 表明对象1属性 if ([keyPath isEqualToString:@"p1"]) { //.... } else if ([keyPath isEqualToString:@"p2"]) { //.... } }else if(object == 对象2){ //pp1,pp2 表明对象2属性 if ([keyPath isEqualToString:@"pp1"]) { //.... } else if ([keyPath isEqualToString:@"pp2"]) { //.... } } 复制代码
看到上面的代码你有没有点抓狂的感受~~~ 这样的代码,容易出错(一旦判断出错,错误的通知观察者,就可能形成让程序crash),并且扩展性不强(好比:我又更改需求了,如今变为3个对象,3个不一样属性须要观察,你如何处理?)
接下来咱们使用context上下文参数就能够解决上述全部问题
#import "ViewController.h" @interface Person : NSObject @property (nonatomic, assign) NSUInteger age; @property (nonatomic, assign) float height; -(void)read; @end @implementation Person -(void)read{ NSLog(@"人会阅读书籍~~~"); } @end @interface Dog : NSObject @property (nonatomic, assign) NSUInteger age; @property (nonatomic, assign) float height; -(void)run; @end @implementation Dog -(void)run{ NSLog(@"狗狗会奔跑~~~"); } @end @interface ViewController () @property (nonatomic, strong) Person * person; @property (nonatomic, strong) Dog * dog; @end @implementation ViewController static void *PersonContext1 = &PersonContext1; static void *PersonContext2 = &PersonContext2; static void *DogContext1 = &DogContext1; static void *DogContext2 = &DogContext2; - (void)viewDidLoad { [super viewDidLoad]; _person = [[Person alloc] init]; _dog = [[Dog alloc] init]; //添加属性观察 [_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:PersonContext1]; [_person addObserver:self forKeyPath:@"height" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:PersonContext2]; [_dog addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:DogContext1]; [_dog addObserver:self forKeyPath:@"height" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:DogContext2]; //触发KVO _person.age = 20; _person.height = 175; _dog.age = 2; _dog.height = 50; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if (context == PersonContext1) { NSLog(@"object = %@",object); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); [_person read]; } else if (context == PersonContext2) { NSLog(@"object = %@",object); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); [_person read]; } else if (context == DogContext1) { NSLog(@"object = %@",object); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); [_dog run]; } else if (context == DogContext2) { NSLog(@"object = %@",object); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); [_dog run]; } else{ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)dealloc { NSLog(@"%s",__func__); //移除 [_person removeObserver:self forKeyPath:@"age" context:PersonContext1]; [_person removeObserver:self forKeyPath:@"height" context:PersonContext2]; [_dog removeObserver:self forKeyPath:@"age" context:DogContext1]; [_dog removeObserver:self forKeyPath:@"height" context:DogContext2]; } @end 复制代码
打印结果以下:
2019-12-26 17:02:02.856237+0800 KVC&KVO[3692:209619] object = <Person: 0x6000000836a0>
2019-12-26 17:02:08.225155+0800 KVC&KVO[3692:209619] keyPath = age
2019-12-26 17:02:08.225185+0800 KVC&KVO[3692:209731] XPC connection interrupted
2019-12-26 17:02:09.256309+0800 KVC&KVO[3692:209619] change = {
kind = 1;
new = 20;
old = 0;
}
2019-12-26 17:02:14.048296+0800 KVC&KVO[3692:209619] 人会阅读书籍~~~
2019-12-26 17:02:18.024598+0800 KVC&KVO[3692:209619] object = <Person: 0x6000000836a0>
2019-12-26 17:02:19.599899+0800 KVC&KVO[3692:209619] keyPath = height
2019-12-26 17:02:20.167343+0800 KVC&KVO[3692:209619] change = {
kind = 1;
new = 175;
old = 0;
}
2019-12-26 17:02:20.927309+0800 KVC&KVO[3692:209619] 人会阅读书籍~~~
2019-12-26 17:02:35.256251+0800 KVC&KVO[3692:209619] object = <Dog: 0x600000083760>
2019-12-26 17:02:36.102929+0800 KVC&KVO[3692:209619] keyPath = age
2019-12-26 17:02:37.063650+0800 KVC&KVO[3692:209619] change = {
kind = 1;
new = 2;
old = 0;
}
2019-12-26 17:02:37.790867+0800 KVC&KVO[3692:209619] 狗狗会奔跑~~~
2019-12-26 17:02:43.469688+0800 KVC&KVO[3692:209619] object = <Dog: 0x600000083760>
2019-12-26 17:02:43.469830+0800 KVC&KVO[3692:209619] keyPath = height
2019-12-26 17:02:43.469978+0800 KVC&KVO[3692:209619] change = {
kind = 1;
new = 50;
old = 0;
}
2019-12-26 17:02:45.048043+0800 KVC&KVO[3692:209619] 狗狗会奔跑~~~
复制代码
结果分析:
注意option参数指定NSKeyValueObservingOptionInitial触发的KVO是没法被automaticallyNotifiesObserversForKey:禁用的,因此咱们能作的就是:只能对除了option指定NSKeyValueObservingOptionInitial以外的触发KVO方式进行禁用
禁用方式就是被观察类重写automaticallyNotifiesObserversForKey:并返回NO便可(这样会禁用全部除了option参数指定NSKeyValueObservingOptionInitial之外的自动KVO)
单独禁用某个key触发的自动KVO能够采用以下两种方式: 单独提供:automaticallyNotifiesObserversOfKey并返回NO便可
+ (BOOL)automaticallyNotifiesObserversOfAge{ return NO; } 复制代码
或者
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{ //这里age是你要禁用的key if([key isEqualToString:@"age"]){ NSLog(@"对%@手动KVO",key); return NO; } return [super automaticallyNotifiesObserversForKey:key]; } 复制代码
1.常规setter方法
2.KVC
3.消息发送,调用setter方法(注意setter不存在状况,须要动态处理)
4.手动KVO
1.手动KVO首先须要注意,option参数不能指定NSKeyValueObservingOptionInitial
2.禁用自动KVO(能够参考以前如何禁用自动KVO)
3.赋值先后分别加入willChangeValueForKey:和didChangeValueForKey:方法便可(比较好的作法是在setter方法中)
[_person willChangeValueForKey:@"age"]; [_person setValue:@300 forKey:@"age"]; [_person didChangeValueForKey:@"age"]; 复制代码
5.依赖触发KVO
有时候一个属性的值依赖于另外一对象中的一个或多个属性,若是这些属性中任一属性的值发生变动,被依赖的属性值也应当为其变动进行标记.最简单的例子就是一我的的姓名fullName是由firstName和lastName组成,当firstName或者lastName发生改变的时候,fullName也会跟着改变.因此若是一个观察者对fullName进行观察,那么当firstName或者lastName改变时,这个观察者也应该被通知.
根据官方文档描述有以下两种解决方案:
方案1 :重写keyPathsForValuesAffectingValueForKey:来指明fullName是依赖lastName和firstName的
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"fullName"]) { NSArray *affectingKeys = @[@"lastName", @"firstName"]; keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys]; } return keyPaths; } 复制代码
方案2: 实现一个遵循命名方式为keyPathsForValuesAffecting的类方法,是依赖于其余值的属性名
+ (NSSet *)keyPathsForValuesAffectingFullName { return [NSSet setWithObjects:@"lastName", @"firstName", nil]; } 复制代码
例子以下:
#import "ViewController.h" @interface Person : NSObject @property (nonatomic, copy) NSString* fullName,*lastName,*firstName; @end @implementation Person - (NSString *)fullName { NSLog(@"%s",__func__); return [NSString stringWithFormat:@"%@ %@",_firstName, _lastName]; } + (NSSet *)keyPathsForValuesAffectingFullName { NSLog(@"%s",__func__); return [NSSet setWithObjects:@"lastName", @"firstName", nil]; } + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSLog(@"%s",__func__); NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"fullName"]) { NSArray *affectingKeys = @[@"lastName", @"firstName"]; keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys]; } return keyPaths; } @end @interface ViewController () @property (nonatomic, strong) Person * person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _person = [[Person alloc] init]; //添加属性观察 [_person addObserver:self forKeyPath:@"fullName" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; _person.firstName = @"Jay"; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%s",__func__); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); } - (void)dealloc { NSLog(@"%s",__func__); //移除 [_person removeObserver:self forKeyPath:@"fullName"]; } @end 复制代码
2019-12-27 17:27:35.457517+0800 KVC&KVO[3492:220907] +[Person keyPathsForValuesAffectingValueForKey:] 2019-12-27 17:27:35.457636+0800 KVC&KVO[3492:220907] +[Person keyPathsForValuesAffectingFullName] 2019-12-27 17:27:35.457752+0800 KVC&KVO[3492:220907] +[Person keyPathsForValuesAffectingValueForKey:] 2019-12-27 17:27:35.457858+0800 KVC&KVO[3492:220907] +[Person keyPathsForValuesAffectingValueForKey:] 2019-12-27 17:27:35.457946+0800 KVC&KVO[3492:220907] +[Person keyPathsForValuesAffectingValueForKey:] 2019-12-27 17:27:35.458045+0800 KVC&KVO[3492:220907] +[Person keyPathsForValuesAffectingValueForKey:] 2019-12-27 17:27:35.458427+0800 KVC&KVO[3492:220907] -[Person fullName] 2019-12-27 17:27:35.458532+0800 KVC&KVO[3492:220907] -[Person fullName] 2019-12-27 17:27:35.458627+0800 KVC&KVO[3492:220907] -[ViewController observeValueForKeyPath:ofObject:change:context:] 2019-12-27 17:27:35.458709+0800 KVC&KVO[3492:220907] keyPath = fullName 2019-12-27 17:27:35.458854+0800 KVC&KVO[3492:220907] change = { kind = 1; new = "Jay (null)"; old = "(null) (null)"; } 复制代码
结果分析:
上面都是一对一简单场景,在一对多关系中(数组属性),上述解决方案就无论用了.好比: 假若有一个部门,里面有不少员工,每一个员工都有各自的薪水,如今要求统计这个部门全部员工的薪水总和. 这种状况不能经过实现keyPathsForValuesAffectingTotalSalary方法并返回employees.salary
有两种解决方法可供参考:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == totalSalaryContext) { [self updateTotalSalary]; } else // deal with other observations and/or invoke super... } - (void)updateTotalSalary { [self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]]; } - (void)setTotalSalary:(NSNumber *)newTotalSalary { if (totalSalary != newTotalSalary) { [self willChangeValueForKey:@"totalSalary"]; _totalSalary = newTotalSalary; [self didChangeValueForKey:@"totalSalary"]; } } - (NSNumber *)totalSalary { return _totalSalary; } 复制代码
看看例子:
#import "ViewController.h" @class Department; @interface Employee : NSObject @property (nonatomic, assign) float salary; @end @implementation Employee @end @interface Department : NSObject @property (nonatomic, strong) NSArray<Employee*> * employees; @property (nonatomic, strong) NSNumber *totalSalary; @end @implementation Department @synthesize totalSalary = _totalSalary; static void *totalSalaryContext = &totalSalaryContext; - (instancetype)init { self = [super init]; if (self) { _employees = @[]; [self addObserver:self forKeyPath:@"employees" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:totalSalaryContext]; } return self; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"%s",__func__); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); if (context == totalSalaryContext) { [self updateTotalSalary]; } else{ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)updateTotalSalary { [self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]]; } - (void)setTotalSalary:(NSNumber *)newTotalSalary { if (_totalSalary != newTotalSalary) { _totalSalary = newTotalSalary; } } - (NSNumber *)totalSalary { return _totalSalary; } - (void)dealloc { NSLog(@"%s",__func__); [self removeObserver:self forKeyPath:@"employees" context:totalSalaryContext]; } @end @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Department *department = [[Department alloc] init]; NSMutableArray* salaries = [department mutableArrayValueForKeyPath:@"employees"]; for (int i =0 ; i< 5; i++) { Employee* emp = [[Employee alloc] init]; emp.salary = 5000+ i*100; [salaries addObject:emp]; } [salaries removeAllObjects]; } @end 复制代码