美文网首页
KVO原理分析

KVO原理分析

作者: 志在交流 | 来源:发表于2021-11-25 20:52 被阅读0次

    1、KVO简介

    KVO官方简介

    KVO是键值观察Key-Value Observing的简称,在iOS开发中,可使用KVO来监听一个对象属性的变化。

    2、KVO的使用

    2.1对某个类添加监听

    //content后面填NULL不要填nil,官方介绍的,OC是C的超集
    [self.person addObserver:self forKeyPath:@"nick" options:(NSKeyValueObservingOptionNew) context:NULL];
    

    1.self.person是LGPerson的实例对象
    2.给LGPerson的实例对象self.person添加观察者self,监听self.person的属性nick的新值变化(NSKeyValueObservingOptionNew)

    context的使用:

    static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
    static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
    
    - (void)registerAsObserverForAccount:(Account*)account {
        [account addObserver:self
                  forKeyPath:@"balance"
                     options:(NSKeyValueObservingOptionNew |
                              NSKeyValueObservingOptionOld)
                     context:PersonAccountBalanceContext];
     
        [account addObserver:self
                  forKeyPath:@"interestRate"
                     options:(NSKeyValueObservingOptionNew |
                              NSKeyValueObservingOptionOld)
                      context:PersonAccountInterestRateContext];
    }
    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary *)change
                           context:(void *)context {
     
        if (context == PersonAccountBalanceContext) {
            // Do something with the balance…
     
        } else if (context == PersonAccountInterestRateContext) {
            // Do something with the interest rate…
     
        } else {
            // Any unrecognized context must belong to super
            [super observeValueForKeyPath:keyPath
                                 ofObject:object
                                   change:change
                                   context:context];
        }
    }
    

    2.2观察者的回调方法。

    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary *)change
                           context:(void *)context {
    }
    

    当观察者的属性变化时,会来到这个回调方法,keyPath是被监听的属性,object是被观察的对象,change是数据的变化。

    2.3移除观察者

    - (void)dealloc{
        [self.person removeObserver:self forKeyPath:@"nick"];
    }
    

    2.4数组类型的属性监听。

    self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
        // 5: 可变数组 KVO -- 
        [self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];
    
    // KVO 建立在 KVC
        [[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"2"];
    

    对象的属性-数组,需要使用KVC的形式赋值才能监听

    2.5多个因素综合影响

    // 4: 多个因素影响 - 下载进度 = 当前下载量 / 总量
        [self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];
    
    #import "LGPerson.h"
    
    @implementation LGPerson
    
    // 下载进度 -- writtenData/totalData
    
    + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
        
        NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
        if ([key isEqualToString:@"downloadProgress"]) {
            NSArray *affectingKeys = @[@"totalData", @"writtenData"];
            keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
        }
        return keyPaths;
    }
    
    - (NSString *)downloadProgress{
        if (self.writtenData == 0) {
            self.writtenData = 10;
        }
        if (self.totalData == 0) {
            self.totalData = 100;
        }
        return [[NSString alloc] initWithFormat:@"%f",1.0f*self.writtenData/self.totalData];
    }
    
    

    2.6KVO开关

    // 自动开关
    + (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
        return YES;
    }
    //单个属性设置关闭
    + (BOOL) automaticallyNotifiesObserversForNick:(NSString *)nick{
        return YES;
    }
    
    
    - (void)setNick:(NSString *)nick{
        [self willChangeValueForKey:@"nick"];
        _nick = nick;
        [self didChangeValueForKey:@"nick"];
    }
    

    当上面的方法返回NO,整个类的监听都被关掉,反之,返回YES,就被打开。

    2.7使用集合类型字典等

    /* Possible values in the NSKeyValueChangeKindKey entry in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information.
    */
    typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
        NSKeyValueChangeSetting = 1,
        NSKeyValueChangeInsertion = 2,
        NSKeyValueChangeRemoval = 3,
        NSKeyValueChangeReplacement = 4,
    };
    
    /* Possible kinds of set mutation for use with -willChangeValueForKey:withSetMutation:usingObjects: and -didChangeValueForKey:withSetMutation:usingObjects:. Their semantics correspond exactly to NSMutableSet's -unionSet:, -minusSet:, -intersectSet:, and -setSet: method, respectively.
    */
    typedef NS_ENUM(NSUInteger, NSKeyValueSetMutationKind) {
        NSKeyValueUnionSetMutation = 1,
        NSKeyValueMinusSetMutation = 2,
        NSKeyValueIntersectSetMutation = 3,
        NSKeyValueSetSetMutation = 4
    };
    

    3、KVO的底层原理

    3.1添加监听的处理

    实现以下方法打印类及其子类

    #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);
    }
    

    实现以下方法打印类的方法列表

    #pragma mark - 遍历方法-ivar-property
    - (void)printClassAllMethod:(Class)cls{
        NSLog(@"*********************");
        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);
    }
    
    image.png

    通过监听前后对LGPerson及其子类的列表的打印,我们发现,当对,LGPerson的对象监听后,系统自动生成了一个LGPerson的子类NSKVONotifying_LGPerson。再来看看NSKVONotifying_LGPerson的方法列表。

    image.png

    从上图可以得知,NSKVONotifying_LGPerson重写了父类的setNickName方法。

    note:注意只有监听属性才有生成set方法,监听成员变量是不会生成的,比如监听name.

    [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LGPerson : NSObject{
        @public
        NSString *name;
    }
    @property (nonatomic, copy) NSString *nickName;
    
    
    - (void)sayHello;
    - (void)sayLove;
    
    @end
    

    再来看看self.person 的isa指向。


    image.png

    可以看到self.person的isa指向,有LGPerson变成了NSKVONotifying_LGPerson.
    通过上图可以分析得出:当对一个对象的属性进行监听的时候,会生成对前对象所属类的子类NSKVONotifying_XXX,并将这个对象的isa指向这个派生类。在这个派生类中,重写当前属性的setter方法。还重写了class方法,[self.person class];返回的是LGPerson。还实现了一个isKVO的标记,标记当前类实现监听的派生类。

    3.2监听的销毁

    image.png

    1、从上图可知,销毁监听后,派生类NSKVONotifying_LGPerson依然存在,这样下次监听不需要重复创建了。
    2、销毁后,对象的isa指针又指回了LGPerson.
    对象销毁后,isa指回来,保证不出现混乱。

    谨记:一定要移除观察,在使用RAC的时候要手动接受(dispose)并在dealloc中释放掉。
    如果观察没有移除会出现野指针的情况。

    相关文章

      网友评论

          本文标题:KVO原理分析

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