通过手动实现KVO,对KVO底层原理有一定认识。
KVO只要是通过监听set方法,从而实现对该对象的监听。
要监听set方法,有两种实现方式,第一就是使用分类,重写set方法,但是这样就会覆盖父类的set方法,所以不可行,pass掉。
第二就是使用子类,把父类的isa指针改为子类。然后调用父类色set方法,最后调用回调方法,该方案可行。
首先是注册监听,在调用监听方法的时候,会动态实现子类,把observer保存到子类的属性中(弱引用weak类型,不能使用strong,会造成循环引用),并且把类型为父类的self 的 isa指针更改为子类。在调用set方法的时候,首先需要调用父类的set方法(通过把isa指针改为父类,调用父类的set方法),然后再调用监听回调方法(把父类色isa指针改回子类,取出observer,通过observer调用监听回调方法)。
废话不多说,直接上代码。
首先是结构目录,其中NSObject+LLKVO是NSObject的子类,作用是动态实现观察对象(比如Person)的子类。
NSObject+LLKVO的代码
#import <Foundation/Foundation.h>
@interface NSObject (LLKVO)
- (void)LL_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
@end
#import "NSObject+LLKVO.h"
#import <objc/message.h>
static NSString *OBSERVER = @"observer";
@implementation NSObject (LLKVO)
- (void)LL_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
//1.创建一个类
NSString *oldClassName = NSStringFromClass(self.class);
NSString *newClassName = [@"LLKVO_" stringByAppendingString:oldClassName];
Class myClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);
//注册类
objc_registerClassPair(myClass);
//2.重写setName方法
/**
*class 给哪个类加方法
*sel 方法编号
*imp 方法实现(函数指针)
*type 返回值类型
*/
class_addMethod(myClass, @selector(setName:), (IMP)setName, "v@:@");
//3.修改isa指针
object_setClass(self, myClass);
//4.将数据观察者保存到当前对象
objc_setAssociatedObject(self, OBSERVER.UTF8String, observer, OBJC_ASSOCIATION_ASSIGN);
}
void setName(id self,SEL _cmd,NSString *newName) {
//改为父类的类型,调用父类的set方法
Class newClass = [self class];
object_setClass(self, class_getSuperclass(newClass));
void (* action1)(id,SEL,NSString *) = (void (*) (id,SEL,NSString *))objc_msgSend;
action1(self,@selector(setName:),newName);
//改为子类
object_setClass(self, newClass);
//取出观察者
id observer = objc_getAssociatedObject(self, OBSERVER.UTF8String);
if (observer) {
void (* action)(id,SEL,NSString *,id,NSDictionary *,id) = (void (*) (id,SEL,NSString *,id,NSDictionary *,id)) objc_msgSend;
action(observer,@selector(LL_observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"kind":@"1",@"new":newName},nil);
}
}
Person的代码,很简单就定义了一个name属性,重写了下set方法
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
#import "Person.h"
@implementation Person
- (void)setName:(NSString *)name {
_name = name;
NSLog(@"我重写了set方法");
}
@end
ViewController中的代码
#import "ViewController.h"
#import "NSObject+LLKVO.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong) Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.person = [Person new];
[self.person LL_addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
}
- (void)LL_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"%@",change);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
static NSInteger a = 0;
self.person.name = [NSString stringWithFormat:@"name -- %tu",++a];
}
@end
Log如下:
2018-12-04 11:43:57.742679+0800 KVO原理浅析[1494:324838] 我重写了set方法
2018-12-04 11:43:57.743071+0800 KVO原理浅析[1494:324838] {
kind = 1;
new = "name -- 1";
}
通过自己实现KVO,明白了KVO的底层原理,苹果底层肯定做的更加详细,功能更加多,但是最基本的思想应该是一致的。
网上肯定有很多大神写的比我详细,底层原理剖析的更加彻底,仅以该博客记录自己对KVO的实现和理解,以后忘记了翻一下也可以快速想起。