面试题答案
一键面试KVO工作原理
- 注册观察:在观察者对象中,通过
addObserver:forKeyPath:options:context:
方法为被观察对象的特定属性注册观察。此方法会告知系统,当被观察对象指定属性值发生改变时,要通知观察者。 - 底层实现:当被观察对象的属性值改变时,系统会自动调用观察者的
observeValueForKeyPath:ofObject:change:context:
方法。实际上,系统在注册观察时,会动态创建被观察对象的子类,并在子类中重写被观察属性的setter方法。当属性值改变时,就会触发通知。 - 通知处理:观察者在
observeValueForKeyPath:ofObject:change:context:
方法中,根据传入的参数来判断是哪个对象的哪个属性发生了变化,并执行相应的处理逻辑。
举例监听视图控制器属性变化
假设在一个视图控制器ViewController
中有一个name
属性,需要监听它的变化。
- 定义视图控制器及属性
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (nonatomic, strong) NSString *name;
@end
- 注册观察
在视图控制器的
viewDidLoad
方法中注册观察:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
self.name = @"初始值";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSString *newName = change[NSKeyValueChangeNewKey];
NSLog(@"名字变为: %@", newName);
}
}
- (void)dealloc {
[self removeObserver:self forKeyPath:@"name"];
}
@end
- 改变属性值触发监听
在其他地方改变
name
属性值时,observeValueForKeyPath:ofObject:change:context:
方法就会被调用。
KVO内存问题及解决方案
- 内存问题:如果观察者对象先于被观察对象释放,而没有及时移除观察,就会导致野指针错误,程序崩溃。例如,视图控制器A观察视图控制器B的某个属性,当A被释放时,如果没有移除对B属性的观察,B再改变属性值时,就会向已释放的A发送通知,造成崩溃。
- 解决方案:在观察者对象的
dealloc
方法中,调用removeObserver:forKeyPath:
方法移除对被观察对象属性的观察。如上述代码中在ViewController
的dealloc
方法里移除对name
属性的观察。同时,要确保在整个生命周期内,观察的注册和移除是平衡的,避免重复注册或遗漏移除。