- iOS 用什么方式实现对一个对象的 KVO ? (KVO 的本质是什么?)
- 首先利用 Runtime API 动态创建一个中间类(NSKVONotifying_ + 类名), 并让实例对象的 isa 指针指向这个中间类, 当修改实例对象的属性时, 会调用 Foundation 的 _NSSetxxxValueAndNotify 函数, 该函数内部主要是做 willChangeValueForKey, super 的 setter, didChangeValueForKey, 然后会触发 Observer 监听方法
- 如何手动触发 KVO?
- 实例对象先调用 willChangeValueForKey, 然后再调用 didChangeValueForKey
- KVO 是否可以添加成员变量的观察?
- 可以的, 为成员变量添加 KVO 是可以的但是必须用 KVC 方式赋值
具体观察代码, 重点observeValueForKeyPath:
#import "ViewController.h"
#import "Person.h"
#import <objc/runtime.h>
/**
* 1. iOS 用什么方式实现对一个对象的 KVO ? (KVO 的本质是什么?)
* 首先利用 Runtime API 动态创建一个中间类(NSKVONotifying_ + 类名), 并让实例对象的 isa 指针指向这个中间类, 当修改实例对象的属性时, 会调用 Foundation 的 _NSSetxxxValueAndNotify 函数, 该函数内部主要是做 willChangeValueForKey, super 的 setter, didChangeValueForKey, 然后会触发 Observer 监听方法
* 2. 如何手动触发 KVO?
* 实例对象先调用 willChangeValueForKey, 然后再调用 didChangeValueForKey
*/
@interface ViewController ()
@property (nonatomic, strong) Person *person1;
@property (nonatomic, strong) Person *person2;
@end
@implementation ViewController
- (void)dealloc {
[self.person1 removeObserver:self forKeyPath:@"age"];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.person1 = Person.new;
self.person1.age = 10;
self.person2 = Person.new;
self.person2.age = 20;
// 添加监听
[self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.person1.age = arc4random() % 100 + 1;
self.person2.age = arc4random() % 100 + 1;
}
// 当监听对象的属性值改变时, 就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
/**
* 打印 person1 的类对象
* (lldb) p self.person1.isa
* (Class) $2 = NSKVONotifying_Person
* Fix-it applied, fixed expression was:
* self.person1->isa
*
* 打印 person2 的类对象
* (lldb) p self.person2.isa
* (Class) $3 = Person
* Fix-it applied, fixed expression was:
* self.person2->isa
*
* 打印所有方法, 可以 llbd 也可以自己实现一个 _printMethodNamesOfClass
* (lldb) po [$2 _shortMethodDescription]
* <NSKVONotifying_Person: 0x600000d40870>:
* in NSKVONotifying_Person:
* Instance Methods:
* - (void) setAge:(long)arg1; (0x7fff258e518d) // ----- 实际会先调用 _NSSetIntValueAndNotify
* - (Class) class; (0x7fff258e2fd5) // ----- 重写 class 实现, class 方法 底层可能就是 object_getClass, 重写后让读者认为还是 Person 类
* - (void) dealloc; (0x7fff258e2d3a) // ---- 收尾工作
* - (BOOL) _isKVOA; (0x7fff258e2d32)
* in Person:
* Properties:
* @property (nonatomic) long age; (@synthesize age = _age;)
* Instance Methods:
* - (long) age; (0x10e795f90)
* - (void) setAge:(long)arg1; (0x10e795fb0)
* (NSObject ...)
*/
[self _printMethodNamesOfClass:object_getClass(object)];
NSLog(@"%@", change);
}
// 打印所有方法
- (void)_printMethodNamesOfClass:(Class)cls {
// 定义
unsigned int outCount = 0;
Method *methodList = NULL;
Method tempMethod = NULL;
NSMutableString *strResult = [NSMutableString string];
NSString *strTemp = [NSString string];
// copy 出方法列表
methodList = class_copyMethodList(cls, &outCount);
// 遍历
for (int i = 0; i < outCount; ++i) {
tempMethod = methodList[i];
strTemp = NSStringFromSelector(method_getName(tempMethod));
[strResult appendFormat:@"\n%@", strTemp];
}
NSLog(@"%@", strResult);
}
@end
网友评论