通过子类实现KVO,浅析KVO底层原理

通过手动实现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的实现和理解,以后忘记了翻一下也可以快速想起。

 

posted @ 2018-12-04 11:52  空山新雨funny  阅读(408)  评论(0编辑  收藏  举报