zoukankan      html  css  js  c++  java
  • iOS中KVO使用理解

    什么是KVO

    KVO<NSKeyValueObserving>,是一个非正式协议,提供了一个途径,使对象(观察者)能够观察其他对象(被观察者)的属性,当被观察者的属性发生变化时,观察者就会被告知该变化。指定一个被观察对象(例如 A 类),当对象某个属性(例如 A 中的字符串 name)发生更改时,对象会获得通知,并作出相应处理;【且不需要给被观察的对象添加任何额外代码,就能使用 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*)change context:(nullable void *)context;

    移除观察者:
     
    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
     
    实现原理
    KVO 的实现依赖于 Objective-C 强大的 Runtime
    当观察某对象 A 时,KVO 机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。
    Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。
     
    自定义KVO
     
    新建NSObject分类
    @interface NSObject (fh)
    - (void)FH_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    @end

    实现方法,新建子类命名为NSNotifying_Class格式,修改对象的类型为新建的子类,添加子类set方法

    #import "NSObject+fh.h"
    #import <objc/runtime.h>
    #import <objc/message.h>
    
    @implementation NSObject (fh)
    - (void)FH_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
        
        //获取当前类名
        NSString * oldName = NSStringFromClass(self.class);
        NSString * newName = [NSString stringWithFormat:@"NSNotifying_%@",oldName];
        
        //创建子类
        Class newClass = objc_allocateClassPair(self.class, newName.UTF8String, 0);
        //注册该类
        objc_registerClassPair(newClass);
        //修改对象的类型
        object_setClass(self, newClass);
        //将观察者的属性保存到当前类里面去
        objc_setAssociatedObject(self, (__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        //给子类添加setName方法
        class_addMethod(newClass, @selector(setName:), (IMP)addMethod, "");
        
    }
    void addMethod(id self,IMP _cmd,NSString * name){
        //获取当前类
        Class myClass = [self class];
        
       //将self的isa指针指向父类
        object_setClass(self, class_getSuperclass([self class]));
       
        //调用父类
        objc_msgSend(self, @selector(setName:),name);
        
        //拿出观察者
    //     objc_getAssociatedObject(self, (__bridge const void *)@"objc");
        
        //通知观察者
        objc_msgSend(objc_getAssociatedObject(self, (__bridge const void *)@"objc"),@selector(observeValueForKeyPath:ofObject:change:context:),self,name,nil,nil);
    
        //改为子类
        object_setClass(self, myClass);
    
    }
    @end

     创建Person类,描述一个name属性

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        Person * p = [[Person alloc]init];
    
        [p FH_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    //    [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
        _p=p;
    
    }
    -(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{
    
        _p.name = @"111";
    
    
    }
    

     观察容器类对象,需要配合KVC完成

    /**
     options参数说明:
     
     NSKeyValueObservingOptionNew   拿到新值
     NSKeyValueObservingOptionOld   拿到旧值
     NSKeyValueObservingOptionInitial   注册就会发一下通知,改变后还会发
     NSKeyValueObservingOptionPrior   改变之前发一次,改变后发一次
     */
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        Person * p = [[Person alloc]init];
    
    //    [p FH_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
        [p addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew context:nil];
        _p=p;
    
    }
    -(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{
    
        //通过KVO观察容器类的,用KVC
        NSMutableArray * tempArr = [_p mutableArrayValueForKey:@"arr"];
        [tempArr addObject:@"1"];
    }
    

    KVO 的使用与Notification非常相似,都能实现类与类之间一对多的通信。KVO是一个对象能够观察另外一个对象的属性的值,并且能够发现值的变化,适合任何类型的对象监听另外一个任意对象的属性的改变。比较常用来在Modal和View之间:View来监听Modal的变化而做出更改。

    优点:1.使用简单,只需三步完成;

                2.当被观察者的对象的属性发生改变时,自动通知相应的观察者了;

    缺点:1.只能用来对对象的属性作出反应,而不会用来对方法或者动作作出反应;

               2.观察的属性必须使用string来定义,编译器不会检测,容易出错;

    拓展

    1.KVC 与 KVO 的不同?

    KVC(键值编码),即 Key-Value Coding,一个非正式的 Protocol,使用字符串(键)访问一个对象实例变量的机制。而不是通过调用 Setter、Getter 方法等显式的存取方式去访问。
    KVO(键值监听),即 Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,对象就会接受到通知,前提是执行了 setter 方法、或者使用了 KVC 赋值。



    2.和 notification(通知)的区别?

    notification 比 KVO 多了发送通知的一步。
    两者都是一对多,但是对象之间直接的交互,notification 明显得多,需要notificationCenter 来做为中间交互。而 KVO 如我们介绍的,设置观察者->处理属性变化,至于中间通知这一环,则隐秘多了,只留一句“交由系统通知”,具体的可参照以上实现过程的剖析。

    notification 的优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,例如键盘、前后台等系统通知的使用也更显灵活方便。



    3.与 delegate 的不同?

    和 delegate 一样,KVO 和 NSNotification 的作用都是类与类之间的通信。但是与 delegate 不同的是:
    这两个都是负责发送接收通知,剩下的事情由系统处理,所以不用返回值;而 delegate 则需要通信的对象通过变量(代理)联系;
    delegate 一般是一对一,而这两个可以一对多。

  • 相关阅读:
    非vue-cli的花括号闪现问题
    vue中实现图片全屏缩放预览,支持移动端
    vue 图片预览插件
    angular.uppercase()
    angular.toJson()
    angular.module()
    对AngularJs的简单了解
    jQuery的属性、遍历和HTML操作
    JQuery函数
    JQuery的选择器
  • 原文地址:https://www.cnblogs.com/baxiu/p/9263010.html
Copyright © 2011-2022 走看看