面试题
1. iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
- 利用
Runtime
动态生成一个子类,并且让instance对象的isa指向这个全新的子类
(即这个子类的superclass指向原来的那个类) - 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
- willChangeValueForKey:
- 父类原来的setter
- didChangeValueForKey:
- 内部会触发监听器(Oberser)的监听方法
observeValueForKeyPath:ofObject:change:context:
2. 如何手动触发KVO?
- 手动调用willChangeValueForKey:和didChangeValueForKey:
代码佐证
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.person1 willChangeValueForKey:@"age"];
[self.person1 didChangeValueForKey:@"age"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
运行结果
3. 直接修改成员变量会触发KVO么?
- 会触发KVO
代码佐证
@interface Person : NSObject {
@public int _age;
}
@property(nonatomic,assign)int age;
@end
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
手动点击触发 - 调用成员变量
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 直接修改成员变量会触发KVO么
self.person1.age = 10;
}
运行结果
手动点击触发 - 调用带下划线的成员变量
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"调用带下划线的成员变量");
self.person1->_age = 10;
}
运行结果
可知,直接调用带下下划线的成员变量不会触发KVO,因为没有调用set方法
序言
KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。
代码佐证
// 创建一个新类
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end
@implementation Person
- (void)setAge:(int)age {
_age = age;
}
@end
// 调用
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
- (void)dealloc {
[self.person1 removeObserver:self forKeyPath:@"age"];
}
// 触摸屏幕改变值
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// NSKVONotifying_MJPerson是使用Runtime动态创建的一个类,是MJPerson的子类
// self.person1.isa == NSKVONotifying_MJPerson
[self.person1 setAge:21];
// self.person2.isa = MJPerson
[self.person2 setAge:22];
NSLog(@"person1 age = %d,person2 age = %d",self.person1.age,self.person2.age);
}
- 新建NSKVONotifying_Person子类,使用伪代码推测其底层实现
#import "Person.h"
@interface NSKVONotifying_Person : Person
@end
#import "NSKVONotifying_Person.h"
@implementation NSKVONotifying_Person
- (void)setAge:(int)age {
_NSSetIntValueAndNotify();
}
// 伪代码
void _NSSetIntValueAndNotify() {
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
// 伪代码
- (void)didChangeValueForKey:(NSString *)key {
// 通知监听器,某某属性值发生了改变
[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
@end
打印结果
底层分析
未使用KVO监听的对象
使用了KVO监听的对象
底层原理解析
先看看添加监听前后实例对象是否发生变化
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
NSLog(@"person1添加KVO监听之前 - %@ %@",object_getClass(self.person1),object_getClass(self.person2));
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
NSLog(@"person1添加KVO监听之后 - %@ %@",object_getClass(self.person1),object_getClass(self.person2));
}
打印结果
通过打印结果,可以发现person1在添加监听之后class类型发生了变化
添加监听前后方法是否发生变化
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
// NSLog(@"person1添加KVO监听之前 - %@ %@",object_getClass(self.person1),object_getClass(self.person2));
NSLog(@"person1添加KVO监听之前 - %p %p",
[self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]);
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
// NSLog(@"person1添加KVO监听之后 - %@ %@",object_getClass(self.person1),object_getClass(self.person2));
NSLog(@"person1添加KVO监听之后 - %p %p",
[self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]);
}
打印结果
通过打印我们知道添加监听之后,调用setAge方法发生了变化
添加监听前后isa是否发生变化
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
NSLog(@"类对象 - %@ %@",
object_getClass(self.person1), // self.person1.isa
object_getClass(self.person2)); // self.person2.isa
NSLog(@"元类对象 - %@ %@",
object_getClass(object_getClass(self.person1)), // self.person1.isa.isa
object_getClass(object_getClass(self.person2))); // self.person2.isa.isa
}
运行结果
可以得出在添加监听之后,实例对象了类对象的isa发生了变化
_NSSet*ValueAndNotify的内部实现
调用方法的顺序
- 调用willChangeValueForKey:
- 调用原来的setter实现
- 调用didChangeValueForKey:
- didChangeValueForKey:内部会调用observer的
- observeValueForKeyPath:ofObject:change:context:方法
代码例子佐证
#import "Person.h"
@implementation Person
- (void)setAge:(int)age {
_age = age;
}
- (void)willChangeValueForKey:(NSString *)key {
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey");
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"didChangeValueForKey - begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey - end");
}
@end
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.person1 setAge:21];
}
运行结果
_NSSet*ValueAndNotify的内部实现 - Class
#import "NSKVONotifying_Person.h"
@implementation NSKVONotifying_Person
- (void)setAge:(int)age {
_NSSetIntValueAndNotify();
}
// 屏幕内部实现,隐藏了NSKVONotifying_MJPerson类的存在
- (Class)class {
return [MJPerson class];
}
- (void)dealloc {
// 收尾工作
}
- (BOOL)_isKVOA {
return YES;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
NSLog(@"类对象 - %@ %@",[self.person1 class],[self.person2 class]);
NSLog(@"类对象 - %@ %@",object_getClass(self.person1),object_getClass(self.person2));
}
运行结果
打印 _NSSet*ValueAndNotify所有方法
// 打印class所有方法列表
- (void)printMethodNamesOfClass:(Class)cls {
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(cls, &count);
// 存储方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有的方法
for (int i = 0; i < count; i++) {
// 获得方法
Method method = methodList[i];
// 获得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 释放
free(methodList);
// 打印方法名
NSLog(@"%@ %@", cls, methodNames);
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
[self printMethodNamesOfClass:object_getClass(self.person1)];
[self printMethodNamesOfClass:object_getClass(self.person2)];
}
运行结果
本文参考借鉴MJ的教程视频,非常感谢.
项目连接地址 - kvo-detail