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

  • 相关阅读:
    55域TLV说明
    iOS开发之指定UIView的某几个角为圆角
    常逛的博客
    猿题库 iOS 客户端架构设计
    NSData
    base64编码
    RSA算法原理
    无法安装64位版本的office因为在您的pc
    mysql导出导入数据
    设置mysql的字符集
  • 原文地址:https://www.cnblogs.com/funny11/p/10063278.html
Copyright © 2020-2023  润新知