zoukankan      html  css  js  c++  java
  • KVO-基本使用方法-底层原理探究-自定义KVO-对容器类的监听

    书读百变,其义自见!

    将KVO形式以代码实现呈现,通俗易懂,更容易掌握 :GitHub   -链接如果失效请自动搜索:https://github.com/henusjj/KVO_base

    代码中有详细的注释

    一、KVO-常用方法

    //注册
    - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    
    //监听方法
    - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
    
    //移除
    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    
    //监听模式(手动,自动),默认是自动Yes
    +(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
    
    
    //属性的依赖,返回监听属性类的集合
    
    +(NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key;

    二、KVO-基本使用

    KVO监听属性值变化,从而做业务逻辑处理,监听属性变化,我们需要实现三步走

    1.注册监听对象

    2.实现监听方法

    3.移除监听对象,避免crash

    //
    //  ViewController.m
    //  KVO-基本用法
    //
    //  Created by GuoYanjun on 2019/1/9.
    //  Copyright © 2019年 shiyujin. All rights reserved.
    //
    
    #import "ViewController.h"
    #import "Person.h"
    @interface ViewController ()
    @property(nonatomic,strong)Person *p;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        _p=[[Person alloc]init];
    //    注册
        [_p addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:nil];
    
    }
    
    //监听方法
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"%@",change);
    }
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        
    //    自动模式
        static int a =0;
        _p.name = [NSString stringWithFormat:@"%d",a++];
        
    //    手动
    //    [_p willChangeValueForKey:@"name"];
    //    _p.name = [NSString stringWithFormat:@"%d",a++];
    //    [_p didChangeValueForKey:@"name"];
    //
        
    }
    -(void)dealloc{
        [_p removeObserver:self forKeyPath:@"name"];
    }
    @end

    三、底层原理探究

    这里我总结三个地方

    1.创建一个子类,名字是:NSKVONotifying_Person  ,person是本项目中的类

      这里为什么是子类不是分类呢,这里说明以下,如果使用分类 ,他会覆盖set方法,导致原set方法中的逻辑处理失效

    2.重写了set方法

      这里的重写不是重写父类的的set,而是重写子类的

    3.外界改变isa指针

      此处可以在注册方法打一个断点,观察其isa指针的变化

        self->_p->isa:Person
         改变为
         self->_p->isa:
         NSKVONotifying_Person

       

    四、对容器的监听

    对于数组,我们添加元素的时候,都是addObject........

    但是我们知道,KVO是针对set方法从而监听的,因为,

    addObject........是不会响应的,此时,苹果给我提供了

    //    [_p.arry addObject:[NSString stringWithFormat:@"%d",a++]];//这一步不会触发监听方法因为监听是监听set的方法,addObject不是set方法
        
        //解决方法
        NSMutableArray *tenp =[_p mutableArrayValueForKey:@"arry"];
        [tenp addObject:[NSString stringWithFormat:@"%d",a++]];

    五、自定义KVO

    此处需要用到Runtime,KVO文档中,我们会发现,相关方法是在NSobjet的分类,所以

    1.创建一个NSObject的分类,定义注册方法

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface NSObject (JK_KVO)
    - (void)JK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    @end
    
    NS_ASSUME_NONNULL_END

    2.实现该方法

    #import "NSObject+JK_KVO.h"
    #import <objc/message.h>
    
    @implementation NSObject (JK_KVO)
    
    - (void)JK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
        
    //   1.创建一个类 -- self.class 就是Person
        NSString *oldname = NSStringFromClass(self.class);
        NSString *newNem = [@"JKKVO_" stringByAppendingString:oldname];
       Class myclass = objc_allocateClassPair(self.class, newNem.UTF8String, 0);
        // 注册类
        objc_registerClassPair(myclass);
            
    //    2.重写子类set方法 -- 所谓的重写就是给子类添加f这个方法 setName,因为子类没有父类的setName方法!!!
        /* class :给那个类添加方法
         *sel:方法编号
         *imp :方法实现(函数指针)
         *type :返回值类型
         */
        class_addMethod(myclass, @selector(setName:), (IMP)setName, "v@:@");
        
    //    3.修改isa指针
        object_setClass(self, myclass);
        
    //    4.将观察保存到当前对象
        objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_ASSIGN);
        
    }
    
    void setName(id self,SEL _cmd,NSString *newName){
        NSLog(@"来了--%@",newName);
    //    调用父类的setName方法
        Class class =[self class];
        object_setClass(self, class_getSuperclass(class));//改成父类
        
        objc_msgSend(self,@selector(setName:),newName);//发送消息给父类
        
        //    观察者
        id observer = objc_getAssociatedObject(self, @"observer");
        
        if (observer) {
            objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new:":newName,@"kind:":@"1"},nil);
        }
        
    //    改回子类
        object_setClass(self, class);
    }
    @end

    3.调用

     [_p JK_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

    ----!!!!!!!!

    OK,结束。代码已经整理完毕。下班

  • 相关阅读:
    面试题
    学习Javascript闭包(Closure)
    git命令
    css3 3d翻转效果
    meta 如何写
    Java String equals和==的比较
    MySQL Explain详解
    MySQL explain,type分析(转)
    python的内存分析和处理
    python lambda的使用
  • 原文地址:https://www.cnblogs.com/henusyj-1314/p/10245363.html
Copyright © 2011-2022 走看看