IOS开发

KVO

2020-10-20  本文已影响0人  越天高

基本使用

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    Person *p = [[Person alloc] init];
    _p = p;
    p.age = 3;
    [_p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    
    
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _p.age = 5;
}

//实现监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
     NSLog(@"监听到%@的属性%@改变了%@", object, keyPath,change);
}

-(void)dealloc
{
    [_p removeObserver:self forKeyPath:@"age"];
}

02 存在的疑问

在我们看来两个person的实例对象都是在调用setAge的方法,但是其中一个可以调用kvo的方法,另一个却不可以

03本质分析

我们通过打印两个实例对象的isa发现,他们的类对象并不是一个,如果实现了kvo他的类对象就不是person类对象,而是NSKVONotifying_Person,这个类是runtime动态生成类,他算是Person的一个子类,


l连个实例的不同父类

如果没有实现KVO我们一调用方法,他就会根据isa找到Person的类对象,然后再找到setAge进行调用,
实现了KVO之后,他会先isa找个类对象NSKVONotifying_Person,找到这个里面setAge,这个里面的setAge会调用foundation里面一个函数——NSSetIntValueAndNotify
这个函数大概干了

willChangeValueForKey;
[super setAge];
didChangeValueForKey

didChangeValueForKey
{
//通知坚听者 属性改变
}

调用willChangeValueForKey:

调用原来的setter实现

调用didChangeValueForKey:
didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:

这就是为什么两种不同结果

04本质验证

KVO没有代理的性能高,因为他还要创建新的类,代理直接找到代理执行方法
根据methodforselect获取方法的实现,可以看出来在添加KVO监听之后,他们的地址就不一样了
可以进入lldb调试来看一下该方法的具体实现
po (IMP)地址 可以看出来他调用的是foundation里面一个函数——NSSetIntValueAndNotify


截屏2020-10-20下午8.38.28.png

NSKVONotifying_Person他的isa也是只想自己的元类对象,

05窥探foundation

foundation就是系统给我默认添加框架,但是只有头文件,我们是看不见的。他.m文件都在framework里面keyi

6内部调用流程

我们虽然不能从在NSKVONotifying_Person里面重写willChangeValueForKey来查看他是否和我们预想的一样,但是我们可以通过更改他的父类来查看

@implementation Person

- (void)setAge:(int)age
{
    _age = age;
    NSLog(@"setAge:");

}
- (void)willChangeValueForKey:(NSString *)key
{
    NSLog(@"willChangeValueForKey");
    [super willChangeValueForKey:key];

}
- (void)didChangeValueForKey:(NSString *)key
{
    NSLog(@"didChangeValueForKeyBegin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKeyEnd");
}

打印的结果和我们预期是一致的说明我们的判断是正确的


KVO的方法调用

07字类内部的方法

NSKVONotifying_Person这个动态生成的子类除了会重写setAge之外,还是会重写class dealloc isKVOA方法
isKVOA 返回YES
dealloc 做一些收尾工作
class 他重写这个方法是为了让他返回的类对象还是Person类对象 ,用runtime来返回还是真正的对象NSKVONotifying_Person。屏蔽内部实现,隐藏的这个类的存在,如果不重写的话,就会返回这个类的真正的类型

08子类内部的方法2

可以利用runtime的方法来打印这个类的所有方法

-(void)printMethodNameFromClass:(Class)cls
{
    unsigned count;
    //获得方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    
    //储存方法名
    NSMutableString *methodNames = [NSMutableString string];
    
    for (int i = 0; i < count; i++)
    {
        Method *m = methodList[i];
        //获得方法名字
        NSString *name = NSStringFromSelector(method_getName(m));
        [methodNames appendString:name];
        [methodNames appendString:@" "];
        
    }
    NSLog(@"%@", methodNames);

}

可以查看到他的真实类对象却是包含了


真实的方法名

09答疑问

iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)

如何手动出发KVO
手动调用willChangeValueForKey:和didChangeValueForKey:

直接修改成员变量会触发KVO么?
_p1->_age = 5;
不会,因为他没有执行setAge方法

上一篇下一篇

猜你喜欢

热点阅读