美文网首页面试
iOS底层(二)_KVO原理

iOS底层(二)_KVO原理

作者: MR_詹 | 来源:发表于2019-10-08 09:27 被阅读0次

    面试题

    问题1:iOS用什么方式实现一个对象的KVO?(KVO的本质是什么?)

    答复:
    利用runtimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类,当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数。
    函数实现内容如下
    willChangeValueForKey:
    父类原来的setter
    didChangeValueForKey:(内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:))

    问题2:如何手动触发KVO?

    答复:
    手动调用willChangeValueForKey:和didChangeValueForKey:

    问题3:直接修改成员变量会触发KVO么?

    答复:
    不会触发KVO

    KVO的全称是Key-Value Observing,俗称”键值监听“,可以用于监听某个对象属性值的改变

    第一:KVO的基本使用

    基本使用详情如下,需要注意的是:在对象销毁前要移除监听,避免野指针访问

    #import <Foundation/Foundation.h>
    @interface MJPerson : NSObject
    @property (nonatomic, assign) int age;
    @end
    
    #import "MJPerson.h"
    @implementation MJPerson
    @end
    
    #import "ViewController.h"
    #import "MJPerson.h"
    
    @interface ViewController ()
    @property (nonatomic, strong, readwrite) MJPerson *person1;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // 1.0 创建Person对象
        self.person1 = [[MJPerson alloc]init];
        self.person1.age = 1;
        
        // 2.0 对self.person1的age添加监听
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
        [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        // 3.0 修改age的属性值(必须使用KVC的方式修改)
        self.person1.age = 11;
        // 等同于
        // [self.person1 setAge:11];
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        // 4.0 获取到age值发生改变的通知
        NSString *new = [change objectForKey:NSKeyValueChangeNewKey];
        NSString *old = [change objectForKey:NSKeyValueChangeOldKey];
        NSLog(@"监听到%@的%@属性值改变了---新值:%@ 旧值 %@",object,keyPath,new,old);
    }
    
    - (void)dealloc {
        // 5.0 移除监听
        [self.person1 removeObserver:self forKeyPath:@"age"];
    }
    @end
    
    第二:KVO的实质分析
    #import "ViewController.h"
    #import "MJPerson.h"
    
    @interface ViewController ()
    @property (nonatomic, strong, readwrite) MJPerson *person1;
    @property (nonatomic, strong, readwrite) MJPerson *person2;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.person1 = [[MJPerson alloc]init];
        self.person1.age = 1;
        
        self.person2 = [[MJPerson alloc]init];
        self.person2.age = 2;
        
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
        [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        // 打印方法:p self.person1.isa
        // 打印self.person1的isa,结果:NSKVONotifying_MJPerson
        // NSKVONotifying_MJPerson 是rumtime动态创建的一个类,并且NSKVONotifying_MJPerson的superclass是MJPerson
        self.person1.age = 11;
        // self.person2的isa,结果:MJPerson
        self.person2.age = 12;
        
        //    等同于
        //    [self.person1 setAge:11];
        //    [self.person2 setAge:12];
        
        // 实例对象方法调用原理:首先找到isa指向的类对象——>在类对象中查找对象方法的实现
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        NSString *new = [change objectForKey:NSKeyValueChangeNewKey];
        NSString *old = [change objectForKey:NSKeyValueChangeOldKey];
        NSLog(@"监听到%@的%@属性值改变了---新值:%@ 旧值 %@",object,keyPath,new,old);
    }
    
    - (void)dealloc {
        [self.person1 removeObserver:self forKeyPath:@"age"];
    }
    
    @end
    

    推理得出以下rumtime动态创建的NSKVONotifying_MJPerson类的实现,伪代码(重写了setAage:的实现)

    @interface NSKVONotifying_MJPerson : MJPerson
    @end
    
    
    #import "NSKVONotifying_MJPerson.h"
    
    @implementation NSKVONotifying_MJPerson
    
    - (void)setAge:(int)age {
        _NSSetIntValueAndNotify()
    
    }
    
    // 伪代码
    void _NSSetIntValueAndNotify() {
        [self willChangeValueForKey:@"age"];
        [super setAge:age];
        [self didChangeValueForKey:@"age"];
    }
    
    - (void)didChangeValueForKey:(NSString *)key {
        // 通知监听器,某某属性值发生了改变
        [oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
    }
    
    
    @end
    

    未添加监听的person1对象


    image.png

    添加监听器的person2对象


    WeChat82eebd16ebe9003855a579b6e401ca04.png

    扩展:

    添加监听后,动态创建一个NSKVONotifying_MJPerson类,在这类中不只是重写了setAge:方法,还重写了以下的方法
    
    -(Class)class {
        // 这里重写后返回MJPerson类对象,目的:屏蔽内部实现,隐藏了NSKVONotifying_MJPerson类的存在
        return [MJPerson class];
    }
    
    - (void)dealloc {
        // 收尾工作
    }
    
    - (BOOL)_isKVOA {
        return YES;
    }
    
    
    验证方法如下
    对于person1和person2分别调用如下方法,结果如下
    2019-09-10 14:28:58.651949+0800 KVOSession[14066:27762244] NSKVONotifying_MJPerson 方法列表 setAge:,class,dealloc,_isKVOA,
    2019-09-10 14:28:58.652042+0800 KVOSession[14066:27762244] MJPerson 方法列表 setAge:,age,
    
    
    - (void)printMethodNamesOfClass:(Class)cls {
        unsigned int count;
        // 获得方法数组
        Method *methodList = class_copyMethodList(cls, &count);
        
        NSMutableString *methodNames = [NSMutableString string];
        
        // 遍历所有的方法
        for (int i=0; i<count; i++) {
            Method method = methodList[i];
            // 获得方法名
            NSString *methodName = NSStringFromSelector(method_getName(method));
            // 拼接方法名
            [methodNames appendFormat:@"%@,",methodName];
        }
        
        // 释放(OC采用ARC,不需要手动释放 ,而C语言需要自己释放)
        free(methodList);
        
        NSLog(@"%@ 方法列表 %@",NSStringFromClass(cls),methodNames);
    }
    
    
    第三:KVO的实质验证
    #import "ViewController.h"
    #import "MJPerson.h"
    #import <objc/runtime.h>
    
    @interface ViewController ()
    @property (nonatomic, strong, readwrite) MJPerson *person1;
    @property (nonatomic, strong, readwrite) MJPerson *person2;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.person1 = [[MJPerson alloc]init];
        self.person1.age = 1;
        
        self.person2 = [[MJPerson alloc]init];
        self.person2.age = 2;
        
        // 验证一:person1实例对象,添加监听前isa的指向
        NSLog(@"person1添加监听KVO之前 - %@",
              object_getClass(self.person1)
              );
    
        NSLog(@"person1添加监听KVO之前 - %p",
              // methodForSelector返回的IMP方法实现函数指针
              [self.person1 methodForSelector:@selector(setAge:)]
              );
            
    
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
        [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
        
        
        NSLog(@"person1添加监听KVO之后 - %@",
              object_getClass(self.person1)
              );
    
        NSLog(@"person1添加监听KVO之前 - %p",
              // methodForSelector返回的IMP方法实现函数指针
              [self.person1 methodForSelector:@selector(setAge:)]
              );
    
    
        /*
          验证一结果:isa指针在添加监听前后,发生了变化
         2019-09-10 11:46:30.515276+0800 KVOSession[10195:27600983] person1添加监听KVO之前 - MJPerson
         2019-09-10 11:46:30.515555+0800 KVOSession[10195:27600983] person1添加监听KVO之后 - NSKVONotifying_MJPerson
         
         验证二结果:根据打印结果,得出添加监听后,setAge:的实现函数变成了_NSSetIntValueAndNotify
         2019-09-10 13:44:55.163073+0800 KVOSession[12803:27683855] person1添加监听KVO之前 - 0x10bc25820
         2019-09-10 13:44:55.163353+0800 KVOSession[12803:27683855] person1添加监听KVO之前 - 0x10c1c6216
         (lldb) p (IMP)0x10bc25820
         (IMP) $0 = 0x000000010bc25820 (KVOSession`-[MJPerson setAge:] at MJPerson.h:14)
         (lldb) p (IMP)0x10c1c6216
         (IMP) $1 = 0x000000010c1c6216 (Foundation`_NSSetIntValueAndNotify)
         */
       
    }
    @end
    

    相关文章

      网友评论

        本文标题:iOS底层(二)_KVO原理

        本文链接:https://www.haomeiwen.com/subject/nswmyctx.html