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 一般是一对一,而这两个可以一对多。

  • 相关阅读:
    LeetCode 485. Max Consecutive Ones
    LeetCode 367. Valid Perfect Square
    LeetCode 375. Guess Number Higher or Lower II
    LeetCode 374. Guess Number Higher or Lower
    LeetCode Word Pattern II
    LeetCode Arranging Coins
    LeetCode 422. Valid Word Square
    Session 共享
    java NIO
    非阻塞IO
  • 原文地址:https://www.cnblogs.com/baxiu/p/9263010.html
Copyright © 2011-2022 走看看