zoukankan      html  css  js  c++  java
  • KVO原理解析

    KVO在我们项目开发中,经常被用到,但很少会被人关注,但如果面试一些大公司,针对KVO的面试题可能如下:

    • 知道KVO嘛,底层是怎么实现的?
    • 如何动态的生成一个类?

    今天我们围绕上面几个问题,我们先看KVO底层实现原理,以及怎么自己写一个KVO?

    一、KVO

    1. KVO定义

    KVO:可以监听一个对象的某个属性是否发生了改变,或者通知其他对象的指定属性发生了改变。

    2.KVO实现

    2.1 监听某个对象的属性

    - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

    2.2 实现协议

    - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void *)context;

    2.3 移除监听

    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

    下面是一个简单的演示:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
         
        self.person = [[ZJPerson alloc] init];
       
        [self.person setName:@"zhangsan"];
       
        [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
         
    }
     
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
        [self.person setName:@"lisi"];
    }
     
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
        NSLog(@"%@", change);
    }
     
    - (void)dealloc{
        [self.person removeObserver:self forKeyPath:@"name"];
    }

    运行结果

    通过以上demo,我们来思考KVO为什么能监听到属性变化,底层又是怎么样实现的呢?

    3. KVO底层实现

    KVO是通过isa-swizzling技术实现的。运行时根据原类创建一个中间类,这个中间类是原类的子类,并动态修改当前对象的isa指针指向中间类,并且将class 方法重写,返回原类的class。苹果建议通过class 实例方法来获取对象类型。

    在查看KVO底层实现,我们首先用runtime在添加监听之前以及之后的类对象

    1 NSLog(@"%@", object_getClass(self.person));
    2 [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    3 NSLog(@"%@", object_getClass(self.person));

    可以查看结果如下:

    2018-05-19 22:48:18.726028+0800 KVO[33804:3059947] ZJPerson
    2018-05-19 22:48:18.726535+0800 KVO[33804:3059947] NSKVONotifying_ZJPerson

    通过上面发现,添加监听之后,实例对象的类对象发生了改变,系统自动为我们动态添加了一个NSKVONotifying_+类名的类,改变属性的值是通过setter方法进行实现,很明显是系统已经动态生成了NSKVONotifying_ZJPerson类,并重写了setter方法,新创建的NSKVONotifying_ZJPerson是ZJPerson的子类。所以不可以创建NSKVONotifying_ZJPerson类了,如果创建了NSKVONotifying_ZJPerson类,会报以下错误:

    2018-05-19 22:56:32.223288+0800 KVO[33919:3068985] [general] KVO failed to allocate class pair for name NSKVONotifying_ZJPerson, automatic key-value observing will not work for this class

    错误提示的是:创建NSKVONotifying_ZJPerson失败。

    那么问题又来了,重写的setter方法内部又做了什么?我们再次利用runtime打印下面方法的实现。

    通过上面发现,发现内部调用了Foundation框架的_NSSetObjectValueAndNotify方法,我们再次看看_NSSetObjectValueAndNotify内部的实现过程如下:

    1. `-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]:
    2. -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:]:
    3. [ZJPerson setName:];
    4. `NSKeyValueDidChange:
    5. `NSKeyValueNotifyObserver:
    6. - (void)observeValueForKeyPath:ofObject:change:context

    简化成伪代码如下:

    1. 调用willChangeValueForKey:
    2. 调用原来的setter实现
    3. 调用didChangeValueForkey
    4. didChangeValueForkey: 内部会调用observer的observeValueForKeyPath方法
     1 - (void)setName:(NSString *)name{
     2     _NSSetObjectValueAndNotify();
     3 }
     4  
     5 void _NSSetObjectValueAndNotify {
     6     [self willChangeValueForKey:@"name"];
     7     [super setName:name];
     8     [self didChangeValueForKey:@"name"];
     9 }
    10  
    11 - (void)didChangeValueForKey:(NSString *)key{
    12     [observe observeValueForKeyPath:key ofObject:self change:nil context:nil];
    13 }

    拓展1

    1. 子类会重写父类的set、class、dealloc、_isKVOA方法
    2. 当观察对象移除所有的监听后,会将观察对象的isa指向原来的类
    3. 当观察对象的监听全部移除后,动态生成的类不会注销,而是留在下次观察时候再使用,避免反复创建中间子类

    拓展2》〉》NSKVONotifying_ZJPerson内部重写了方法?

    利用runtime打印方法列表

     1 unsigned int count;
     2 Method *methods = class_copyMethodList(object_getClass(self.person), &count);
     3      
     4 for (NSInteger index = 0; index < count; index++) {
     5    Method method = methods[index];
     6    
     7    NSString *methodStr = NSStringFromSelector(method_getName(method));
     8    
     9    NSLog(@"%@
    ", methodStr);
    10 }

    打印结果

    2018-05-20 08:57:07.883400+0800 KVO[35888:3218908] setName:
    2018-05-20 08:57:07.883571+0800 KVO[35888:3218908] class
    2018-05-20 08:57:07.883676+0800 KVO[35888:3218908] dealloc
    2018-05-20 08:57:07.883793+0800 KVO[35888:3218908] _isKVOA

     发现除了重写了setName: 还重写了class dealloc _isKVOA等方法

    调用class方法可能里面实现是:

    - (Class) class {
        return [ZXYPerson class]
    }

    而不是NSKVONotifying_ZJPerson类,为了屏蔽了内部实现,隐藏了该类,如果想查看可以通过runtime的object_getClass()方法获取真实运行时的情况

    二、如何动态生成类

    说到动态生成一个类,也就是利用了苹果的runtime机制,下面我们来动态创建生成类。

    2.1 创建类

    Class customClass = objc_allocateClassPair([NSObject class], "ZJCustomClass"0);

    2.2 添加实例变量

    // 添加实例变量
        class_addIvar(customClass, "age", sizeof(int), 0, "i");

    2.3 添加方法,V@:表示方法的参数和返回值

        class_addMethod(customClass, @selector(hahahha), (IMP)hahahha, "V@:");

    需要实现的方法:

    void hahahha(id self, SEL _cmd)
    {
        NSLog(@"hahahha====");
    }
     
    - (void)hahahha{
    }

    然后注册到运行时环境

    objc_registerClassPair(customClass);

    下面是打印方法列表以及成员变量列表

     1 #pragma mark - Util
     2  
     3 - (NSString *)copyMethodsByClass:(Class)cls{
     4     unsigned int count;
     5     Method *methods = class_copyMethodList(cls, &count);
     6     
     7     NSString *methodStrs = @"";
     8      
     9     for (NSInteger index = 0; index < count; index++) {
    10         Method method = methods[index];
    11         
    12         NSString *methodStr = NSStringFromSelector(method_getName(method));
    13                 
    14         methodStrs = [NSString stringWithFormat:@"%@ ", methodStr];
    15     }
    16     
    17     free(methods);
    18      
    19     return methodStrs;
    20 }
    21  
    22 - (NSString *)copyIvarsByClass:(Class)cls{
    23     unsigned int count;
    24     Ivar *ivars = class_copyIvarList(cls, &count);
    25     
    26     NSMutableString *ivarStrs = [NSMutableString string];
    27      
    28     for (NSInteger index = 0; index < count; index++) {
    29         Ivar ivar = ivars[index];
    30         
    31         NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];  //获取成员变量的名字
    32         
    33         NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; //获取成员变量的数据类型
    34         
    35         [ivarStrs appendString:@"
    "];
    36         [ivarStrs appendString:ivarName];
    37         [ivarStrs appendString:@"-"];
    38         [ivarStrs appendString:ivarType];
    39          
    40     }
    41      
    42     free(ivars);
    43    
    44     return ivarStrs;
    45 }

    如果想要了解更多的KVO,可以关注更新的博客

    https://www.cnblogs.com/guohai-stronger/p/10272146.html

    以上就是KVO的基本内容,希望通过本篇博客,大家对KVO原理以及基本使用有更深的了解!!!

  • 相关阅读:
    让服务器可以下载apk和ipa文件
    MVC第一次访问比较慢的解决方案
    [C#]记录程序耗时的方法【转发】
    uploadify上传带参数及接收参数的方法
    uploadify上传之前判断一个input输入框是否为空
    jgGrid获得的id值是主键的id而不是jqGrid的行号值
    jqGrid删除多行数据问题
    Linux下的微秒级定时器: usleep, nanosleep, select, pselect
    Linux平台延时之sleep、usleep、nanosleep、select比较
    Linux 高精確的時序(sleep, usleep,nanosleep) from:http://blog.sina.com.cn/s/blog_533ab41c0100htae.html
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/9473551.html
Copyright © 2011-2022 走看看