面试题
问题1:iOS用什么方式实现一个对象的KVO?(KVO的本质是什么?)
答复:
利用runtimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类,当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数。
函数实现内容如下
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:(内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:))
问题2:如何手动触发KVO?
答复:
手动调用willChangeValueForKey:和didChangeValueForKey:
问题3:直接修改成员变量会触发KVO么?
答复:
不会触发KVO
KVO的全称是Key-Value Observing,俗称”键值监听“,可以用于监听某个对象属性值的改变
第一:KVO的基本使用
基本使用详情如下,需要注意的是:在对象销毁前要移除监听,避免野指针访问
#import
@interface MJPerson : NSObject
@property (nonatomic, assign) int age;
@end
#import "MJPerson.h"
@implementation MJPerson
@end
#import "ViewController.h"
#import "MJPerson.h"
@interface ViewController ()
@property (nonatomic, strong, readwrite) MJPerson *person1;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1.0 创建Person对象
self.person1 = [[MJPerson alloc]init];
self.person1.age = 1;
// 2.0 对self.person1的age添加监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 3.0 修改age的属性值(必须使用KVC的方式修改)
self.person1.age = 11;
// 等同于
// [self.person1 setAge:11];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// 4.0 获取到age值发生改变的通知
NSString *new = [change objectForKey:NSKeyValueChangeNewKey];
NSString *old = [change objectForKey:NSKeyValueChangeOldKey];
NSLog(@"监听到%@的%@属性值改变了---新值:%@ 旧值 %@",object,keyPath,new,old);
}
- (void)dealloc {
// 5.0 移除监听
[self.person1 removeObserver:self forKeyPath:@"age"];
}
@end
第二:KVO的实质分析
#import "ViewController.h"
#import "MJPerson.h"
@interface ViewController ()
@property (nonatomic, strong, readwrite) MJPerson *person1;
@property (nonatomic, strong, readwrite) MJPerson *person2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[MJPerson alloc]init];
self.person1.age = 1;
self.person2 = [[MJPerson alloc]init];
self.person2.age = 2;
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 打印方法:p self.person1.isa
// 打印self.person1的isa,结果:NSKVONotifying_MJPerson
// NSKVONotifying_MJPerson 是rumtime动态创建的一个类,并且NSKVONotifying_MJPerson的superclass是MJPerson
self.person1.age = 11;
// self.person2的isa,结果:MJPerson
self.person2.age = 12;
// 等同于
// [self.person1 setAge:11];
// [self.person2 setAge:12];
// 实例对象方法调用原理:首先找到isa指向的类对象——>在类对象中查找对象方法的实现
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSString *new = [change objectForKey:NSKeyValueChangeNewKey];
NSString *old = [change objectForKey:NSKeyValueChangeOldKey];
NSLog(@"监听到%@的%@属性值改变了---新值:%@ 旧值 %@",object,keyPath,new,old);
}
- (void)dealloc {
[self.person1 removeObserver:self forKeyPath:@"age"];
}
@end
推理得出以下rumtime动态创建的NSKVONotifying_MJPerson类的实现,伪代码(重写了setAage:的实现)
@interface NSKVONotifying_MJPerson : MJPerson
@end
#import "NSKVONotifying_MJPerson.h"
@implementation NSKVONotifying_MJPerson
- (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
未添加监听的person1对象
添加监听器的person2对象
扩展:
添加监听后,动态创建一个NSKVONotifying_MJPerson类,在这类中不只是重写了setAge:方法,还重写了以下的方法
-(Class)class {
// 这里重写后返回MJPerson类对象,目的:屏蔽内部实现,隐藏了NSKVONotifying_MJPerson类的存在
return [MJPerson class];
}
- (void)dealloc {
// 收尾工作
}
- (BOOL)_isKVOA {
return YES;
}
验证方法如下
对于person1和person2分别调用如下方法,结果如下
2019-09-10 14:28:58.651949+0800 KVOSession[14066:27762244] NSKVONotifying_MJPerson 方法列表 setAge:,class,dealloc,_isKVOA,
2019-09-10 14:28:58.652042+0800 KVOSession[14066:27762244] MJPerson 方法列表 setAge:,age,
- (void)printMethodNamesOfClass:(Class)cls {
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有的方法
for (int i=0; i
第三:KVO的实质验证
#import "ViewController.h"
#import "MJPerson.h"
#import
@interface ViewController ()
@property (nonatomic, strong, readwrite) MJPerson *person1;
@property (nonatomic, strong, readwrite) MJPerson *person2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[MJPerson alloc]init];
self.person1.age = 1;
self.person2 = [[MJPerson alloc]init];
self.person2.age = 2;
// 验证一:person1实例对象,添加监听前isa的指向
NSLog(@"person1添加监听KVO之前 - %@",
object_getClass(self.person1)
);
NSLog(@"person1添加监听KVO之前 - %p",
// methodForSelector返回的IMP方法实现函数指针
[self.person1 methodForSelector:@selector(setAge:)]
);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"person1添加监听KVO之后 - %@",
object_getClass(self.person1)
);
NSLog(@"person1添加监听KVO之前 - %p",
// methodForSelector返回的IMP方法实现函数指针
[self.person1 methodForSelector:@selector(setAge:)]
);
/*
验证一结果:isa指针在添加监听前后,发生了变化
2019-09-10 11:46:30.515276+0800 KVOSession[10195:27600983] person1添加监听KVO之前 - MJPerson
2019-09-10 11:46:30.515555+0800 KVOSession[10195:27600983] person1添加监听KVO之后 - NSKVONotifying_MJPerson
验证二结果:根据打印结果,得出添加监听后,setAge:的实现函数变成了_NSSetIntValueAndNotify
2019-09-10 13:44:55.163073+0800 KVOSession[12803:27683855] person1添加监听KVO之前 - 0x10bc25820
2019-09-10 13:44:55.163353+0800 KVOSession[12803:27683855] person1添加监听KVO之前 - 0x10c1c6216
(lldb) p (IMP)0x10bc25820
(IMP) $0 = 0x000000010bc25820 (KVOSession`-[MJPerson setAge:] at MJPerson.h:14)
(lldb) p (IMP)0x10c1c6216
(IMP) $1 = 0x000000010c1c6216 (Foundation`_NSSetIntValueAndNotify)
*/
}
@end