美文网首页面试宝点
『ios』你真的了解kvo吗?NSKVONotifying_类s

『ios』你真的了解kvo吗?NSKVONotifying_类s

作者: butterflyer | 来源:发表于2021-06-07 20:37 被阅读0次

    对已经学会的知识,不断地总结,可以让你变得更强的。

    什么情况下会调用kvo。
    什么情况下不会调用kvo。
    kvo的底层原理实现。

    什么情况下会调用kvo呢?

    @interface customObject : NSObject
    {
     NSString *age; //
    }
    @property (copy, nonatomic) NSString *name;
    @end
     // 1
     _person.name = @"名字";
     // 2
    [_person setValue:@"名字" forKey:@"name"];
    // 3
    [_person setValue:@"名字" forKey:@"_name"];
    //4
    self.name = @"名字";
    // 5
     _name = @"名字";
    // 6
    [self setValue:@"名字" forKey:@"name"];
    // 7
    [self setValue:@"名字" forKey:@"_name"];
    //8
     unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList([Person class], &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            if ([[NSString stringWithUTF8String:ivar_getName(ivar)] isEqualToString:@"_name"]) {
            // 8
                object_setIvar(_person, ivar, @"名字");
            }
        }
    //9
    self ->name = @"名字"
    

    我们对name,哪些会真正的触发kvo呢
    我可以告诉你,1 2 4 6会监听到kvo的变化,而其他的则检测不到。

    又比如下面这个例子。

    #import <Foundation/Foundation.h>
    @class AnimalClass;
    @interface PersonClass : NSObject
    @property (nonatomic, assign) NSInteger age;
    @property (nonatomic, strong) AnimalClass *animal;
    @end
    
    @interface AnimalClass : NSObject
    @property (nonatomic, copy) NSString *name;
    @end
    

    当我们对animal进行kvo监听,然后修改name的值,是否会被kvo监听到。
    当我们对personclass对象重新赋值一个AnimalClass对象,是否会被kvo监听到。
    我可以告诉你,第一种情况是监听不到的,第二种情况是可以监听到的。
    然后,为什么呢?

    我这里有两张图片。


    20190629085243472.png
    20190629085734114.png

    当我们对一个对象进行kvo监听的时候,会生成一个NSKVONotifying_前缀的类,然后我们实际的操作是对这个类进行的。
    通俗的讲,对对象的进行kvo监听后,这个对象的isa指针已经指向了NSKVONotifying_前缀的类,NSKVONotifying_Person。这个类是person的子类,他的superclass就是person类对象。
    我们在调用setage方法的时候,会根据对象的isa找到NSKVONotifying_Person,然后在他的类里面找setage的实现。
    那么有没有疑惑,setage中到底做了什么操作呢?
    _NSsetIntValueAndNotify记住这个函数。

    // 通过methodForSelector找到方法实现的地址
    NSLog(@"添加KVO监听之前 - p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);
        
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [p1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    NSLog(@"添加KVO监听之后 - p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);
    
    

    我们可以这样打印出setage的方法实现。
    可以打印出

    p1进行监听之前:[person setAge:]
    p1进行监听之后:(foundation _NSsetIntValueAndNotify)
    

    这个_NSsetIntValueAndNotify方法就是setage的实现,具体的实现,请再往后看。
    Foundation框架中还有很多例如_NSSetBoolValueAndNotify、_NSSetCharValueAndNotify、_NSSetFloatValueAndNotify、_NSSetLongValueAndNotify等等函数。

    想不想知道NSKVONotifyin_Person内部结构是怎样的?

    继续往下看。

    - (void)viewDidLoad {
        [super viewDidLoad];
        Person *p1 = [[Person alloc] init];
        p1.age = 1.0;
        Person *p2 = [[Person alloc] init];
        p1.age = 2.0;
        // self 监听 p1的 age属性
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [p1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
        [self printMethods: object_getClass(p2)];
        [self printMethods: object_getClass(p1)];
    
        [p1 removeObserver:self forKeyPath:@"age"];
    }
    
    - (void) printMethods:(Class)cls
    {
        unsigned int count ;
        Method *methods = class_copyMethodList(cls, &count);
        NSMutableString *methodNames = [NSMutableString string];
        [methodNames appendFormat:@"%@ - ", cls];
        
        for (int i = 0 ; i < count; i++) {
            Method method = methods[i];
            NSString *methodName  = NSStringFromSelector(method_getName(method));
            
            [methodNames appendString: methodName];
            [methodNames appendString:@" "];
            
        }
        
        NSLog(@"%@",methodNames);
        free(methods);
    }
    
    

    打印结果如下

    2021-06-07 19:43:56.574173+0800 DSKVO[31160:4348683] Person - setAge: age
    2021-06-07 19:43:56.574346+0800 DSKVO[31160:4348683] NSKVONotifying_Person - setAge: class dealloc _isKVOA
    

    在kvo监听下,包含了四个方法,setAge: class dealloc _isKVOA

    我们先从clas来进行入手。

    NSLog(@"%@,%@",[p1 class],[p2 class]);
    

    打印结果都是person。为什么呢?
    进行kvo监听之后,不是进行变为NSKVONotifyin_Person这个类吗?怎么打印出来都是person。
    其实是苹果不希望将这个NSKVONotifyin_Person暴露出来。然后在类的内部,重写了clas方法。

    - (Class) class {
         // 得到类对象,在找到类对象父类
         return class_getSuperclass(object_getClass(self));
    }
    

    setAge方法的再深一层的内部实现?

    - (void)setName:(NSString *)name {
        _NSSetObjectValueAndNotify()
    }
    
    - (void)willChangeValueForKey:(NSString *)key {
        [super willChangeValueForKey:key];
    }
    
    - (void)didChangeValueForKey:(NSString *)key {
        [super didChangeValueForKey:key];
        [observer observeValueForKeyPath:@"name"];
    }
    
    void _NSSetObjectValueAndNotify() {
        [self willChangeValueForKey:@"name"];
        [super setName:name];
        [self didChangeValueForKey:@"name"];
    }
    

    我相信看完都会有所收获。

    看完这些最开始的例子,我觉得会懂了。
    我们来看下kvc的原理:

    当调用setValue:forKey:时 ,程序会先通过setter(set:)方法,对属性进行设置;
    如果没有找到setKey:方法,KVC机制会检查+
    (BOOL)accessInstanceVariablesDirectly方法有没有返回YES
    ,默认该方法是返回YES的,如果重写返回了NO,那么这一步会执行setValue
    forUndefinedKey:方法。若为YES,KVC机制会搜索该类中是否有名为key的成员变量,不管变量在类接口处定义没有,只要存在以key命名的变量,KVC都可以对该成员变量赋值。
    如果该类既没有setKey方法,也没有_key成员变量,KVC机制会搜索_isKey的成员变量;如果_isKey成员变量也没有,KVC机制再会继续搜索和is的成员变量给它们赋值,如果上面列出的方法或者成员变量都不存在,系统会执行该对象的setValue:forUndefinedKey:方法
    抛出异常(既 如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作)
    

    触发kvc的set方法才可以触发kvo机制。
    所以这两种情况

    这个可以完美触发kvo
    [_person setValue:@"名字" forKey:@"name"];
    这个直接给name赋值,所以不能触发kvo
    [_person setValue:@"名字" forKey:@"_name"];
    

    相关文章

      网友评论

        本文标题:『ios』你真的了解kvo吗?NSKVONotifying_类s

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