先上簡單的背景代碼:
@interface ViewController ()
@property(nonatomic, strong) Dog *dog;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.dog = [Dog new];
self.dog.say = @"wow";
NSLog(@"class name before kvo:%s",object_getClassName(self.dog));
[self.dog addObserver:self forKeyPath:@"say" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"class name after kvo:%s",object_getClassName(self.dog));
self.dog.say = @"yeah";
}
- (void)dealloc{
[self.dog removeObserver:self forKeyPath:@"setSay"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@",change);
}
Log
god:wow
class name before kvo:Dog
class name after kvo:NSKVONotifying_Dog
god:yeah
{
kind = 1;
new = yeah;
}
可以注意到kvo之後對象dog的類型改變了。
可以猜想設置了kvo之後:
1.runtime重新生成了一個類型
2.然後將原對象的指針指向他
3.在新類型中動態添加新的方法來轉接原來的setXX方法
3.1轉發方法給父類屬性set
3.2通知監聽對象屬性發生了改變
#import <Foundation/Foundation.h>
#import "Dog.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation Dog
// 給子類提供的IMP
void kvoMethond(id obj,SEL sel, NSString * str) {
// 創建父類實例-superDog
struct objc_super superDog = {
obj,
class_getSuperclass([obj class])
};
// super.setName
objc_msgSendSuper(&superDog, sel, str);
NSString *selString = [NSStringFromSelector(sel) substringToIndex:NSStringFromSelector(sel).length-1];
NSString *key = [NSStringFromSelector(sel) substringWithRange:NSMakeRange(3, selString.length - 3)];
key = [[key substringToIndex:1].lowercaseString stringByAppendingString:[key substringFromIndex:1]];
// 通知觀察者調用監聽方法
// 獲取observer
id observer = objc_getAssociatedObject(obj, "dog_observer");
[observer observeValueForKeyPath:key ofObject:obj change:@{@"new": str} context:nil];
}
- (void)setSay:(NSString *)say {
_say = say;
NSLog(@"god:%@",say);
}
// 自定義實現的kvo
- (void)dog_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
const char* kvoClassName = [[NSString stringWithFormat:@"KVONotification_%@",NSStringFromClass(self.class)] UTF8String];
// 動態創建子類
Class kvoClass = objc_allocateClassPair(self.class, kvoClassName, 0);
// 將self的isa指向新的子類
object_setClass(self, kvoClass);
NSString *pathName = [NSString stringWithFormat:@"set%@:",keyPath.capitalizedString];
SEL sel = NSSelectorFromString(pathName);
// 給sel(setName)設置新的 IMP
class_addMethod(kvoClass, sel, (IMP)kvoMethond, "v@:@");
// 使用關聯方法保存 observer(IMP方法內要用到這個對象)
objc_setAssociatedObject(self, "dog_observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
vc code
self.dog = [Dog new];
self.dog.say = @"wow";
NSLog(@"class name before kvo:%s",object_getClassName(self.dog));
// [self.dog addObserver:self forKeyPath:@"say" options:NSKeyValueObservingOptionNew context:nil];
[self.dog dog_addObserver:self forKeyPath:@"say" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"class name after kvo:%s",object_getClassName(self.dog));
self.dog.say = @"yeah";
}
- (void)dealloc{
[self.dog removeObserver:self forKeyPath:@"setSay"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@",change);
}