zoukankan      html  css  js  c++  java
  • KVO

    官方文档地址:

    https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html

    基本用法:

    1. [self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL]

    ** context作用,用来打标签在回调方法中做区分,应用场景是在如果父类和子类有同一个属性名,回调判断会复杂,如果是用context来区分会很方便,在内存中只能惟一的写一个,相当于一个静态值。而且查找更优越,如果判断类和属性列表的话需要查找他们的缓存列表,所以context的优点是嵌套少,性能好。

    2. 改变值 self.person.name = @"person";

    3. 响应回调
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{

    NSLog(@"%@",change);
    NSLog(@"downloadProgress == %@",self.person.downloadProgress);
    }

    4. 析构:

    - (void)dealloc{

    [self.student removeObserver:self forKeyPath:@"name"];

    }

    ** 如果没有在dealloc中移除观察者,并且被观察对象是一个单例的换,再次打开界面重新给name复制时会闪退或者调用两次等异常。(所以一定要移除,至于是什么导致的崩溃,闭源源码无法看到,猜测是不断添加注册观察者,当系统需要给观察者复制是,原页面已经销毁,找不到原地址,导致野指针崩溃。),下面是官方文档:

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {

        if([key isEqualToString:@"name"]){

            return YES;

        }

        return NO;

    }

    返回NO,则之前添加的观察者,不再回调,也可以选择观察一些特定的key.

    如果这里返回NO,也可以使用在set方法里面添加手动观察方法的方式实现添加观察者:

    - (void)setName:(NSString *)name{

        [self willChangeValueForKey:@"name"];

        _name = name;

        [self didChangeValueForKey:@"name"];

    }

    KVO的一个简单应用(为downloadProgress添加观察者,影响下载进度的影响因素包括总的大小和已经写入的大小):

    如果是传统的做法需要同时监控两个值,但是使用下面的方法就更便捷一些:

    + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{

     

        NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];

        if ([key isEqualToString:@"downloadProgress"]) {

            NSArray *affectingKeys = @[@"totalData", @"writtenData"];

            keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];

        }

        return keyPaths;

    }

    上面的代码的主要作用是,当观察的值为downloadProgress时,添加连个影响因子totalData,writtenData,这样当这两个值改变的时候也会回调观察者方法。(联动观察 )

    或者是把对象当做观察对象时,对象的属性改变也会调用观察者回调方法。

    集合类观察:

    如果采用上面的方式观察数组时,如果给数组添加元素,会发现不会调用观察者回调。如果要了解这个原因,就需要了解KVC的内容。(因为集合和简单变量的取值和赋值过程是不一样的)

    [[self.student mutableArrayValueForKey:@"dataArray"] addObject:@"hello"];(满足KVC的设置流程)


    这样就可以了,必须先初始化数组,接下来分析下原因:

     

    根据之前KVC的官方文档中介绍。self.student是有可能获取不到值的,因为有_key,isKey,key这种变量的存在,赋值的时候不一定会复制到key中,有可能是_key中,所以self.student中不一定有值,所以获取不到。这样的话就需要直接避免这种情况,直接用KVC获取dataArray,这样可以规避KVC查找值导致的问题。

     

     

    KVO底层原理:

    首先验证下是否是观察的set方法:

    为类添加一个属性(属性会自动生成set方法)和成员变量

    ------------------------------------------

    #import "KVOPerson.h"

     

    NS_ASSUME_NONNULL_BEGIN

     

    @interface KVOStudent : KVOPerson{

        @public

        NSString *age;

    }

    @property (nonatomic, strong)   NSString *name;

    @property (nonatomic, assign) int progress;

    @property (nonatomic, assign) int total;

    @property (nonatomic, assign) int download;

    @property (nonatomic, strong) NSMutableArray *dataArray;

    @end

    ------------------------------------------

    NS_ASSUME_NONNULL_END

     

    [self.student addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:NULL];

    [self.student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];

    self.student.name = @"fanxing";

    self.student->age = @"age";

    ------------------------------------------

    只打印了name,没有打印age,证明确实是KVO默认在set方法里处理的相关逻辑。下面是官方文档:

     

    翻译:自动化KVO是由一种叫做isa-swizzing(isa改变类的指向)的技术实现的 :

    首先使用objc_getClass()方法获取:(打印发现并没有中间类生成)

    原因是应该查看对象的class而不是类的class,而且类对象的isa指向原类. 

    说明类是没有变化的,变化的是当前的对象的isa(原来是指向KVOStudent,现在指向 NSKVONotifying_KVOStudent )动态类

     这个类是什么时候生成的呢?和原来的类KVOStudent是什么关系?

    打印类的子类父类

    #pragma mark - 遍历类以及子类

    - (void)printClasses:(Class)cls{

        

        /// 注册类的总数

        int count = objc_getClassList(NULL, 0);

        /// 创建一个数组, 其中包含给定对象

        NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];

        /// 获取所有已注册的类

        Class* classes = (Class*)malloc(sizeof(Class)*count);

        objc_getClassList(classes, count);

        for (int i = 0; i<count; i++) {

            if (cls == class_getSuperclass(classes[i])) {

                [mArray addObject:classes[i]];

            }

        }

        free(classes);

        NSLog(@"classes = %@", mArray);

    }

     

    发现多了一个类,并且NSKVONotifying_KVOStudent是KVOStudent的子类,继承于KVOStudent。

    接下来看下NSKVONotifying_KVOStudent 这个类的其他东西有没有不一样的地方,查看methodList

    #pragma mark - 遍历方法-ivar-property

    - (void)printClassAllMethod:(Class)cls{

        unsigned int count = 0;

        Method *methodList = class_copyMethodList(cls, &count);

        for (int i = 0; i<count; i++) {

            Method method = methodList[i];

            SEL sel = method_getName(method);

            IMP imp = class_getMethodImplementation(cls, sel);

            NSLog(@"%@-%p",NSStringFromSelector(sel),imp);

        }

        free(methodList);

    }

    可以看到如果重写父类的方法,IMP实现的指针内存地址是不一样的 

    总结KVO做了哪些事情:

    1.. 修改了isa的指向。

    2. 从打印的内容来看,因为NSKVONotifying_KVOStudent是student的子类,并且setName的imp实现指针发生了变化,所以可以肯定的是,NSKVONotifying_KVOStudent类重写了setName方法。

    3. 添加了一个class方法。

    4. 重写了dealooc析构函数。

    5. 添加了一个标记 _isKVO标识,标记是KVO的类。 

     

  • 相关阅读:
    distcc加速内核编译
    ssh不检查server变化
    bbb u-boot SPI 启动
    Debian NAT共享上网
    Debian Epson L455 打印机
    Learn CMake's Scripting Language in 15 Minutes (ZZ)
    网络启动并安装Debian
    GNU LD 脚本学习笔记
    JLink defective
    获取真实mac地址
  • 原文地址:https://www.cnblogs.com/coolcold/p/12059193.html
Copyright © 2011-2022 走看看