- iOS 用什么方式實現對一個對象的 KVO ? (KVO 的本質是什么?)
- 首先利用 Runtime API 動態(tài)創(chuàng)建一個中間類(NSKVONotifying_ + 類名), 并讓實例對象的 isa 指針指向這個中間類, 當修改實例對象的屬性時, 會調用 Foundation 的 _NSSetxxxValueAndNotify 函數, 該函數內部主要是做 willChangeValueForKey, super 的 setter, didChangeValueForKey, 然后會觸發(fā) Observer 監(jiān)聽方法
- 如何手動觸發(fā) KVO?
- 實例對象先調用 willChangeValueForKey, 然后再調用 didChangeValueForKey
- KVO 是否可以添加成員變量的觀察?
- 可以的, 為成員變量添加 KVO 是可以的但是必須用 KVC 方式賦值
具體觀察代碼, 重點observeValueForKeyPath:
#import "ViewController.h"
#import "Person.h"
#import <objc/runtime.h>
/**
* 1. iOS 用什么方式實現對一個對象的 KVO ? (KVO 的本質是什么?)
* 首先利用 Runtime API 動態(tài)創(chuàng)建一個中間類(NSKVONotifying_ + 類名), 并讓實例對象的 isa 指針指向這個中間類, 當修改實例對象的屬性時, 會調用 Foundation 的 _NSSetxxxValueAndNotify 函數, 該函數內部主要是做 willChangeValueForKey, super 的 setter, didChangeValueForKey, 然后會觸發(fā) Observer 監(jiān)聽方法
* 2. 如何手動觸發(fā) 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;
// 添加監(jiān)聽
[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;
}
// 當監(jiān)聽對象的屬性值改變時, 就會調用
- (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