iOS 探索KVO二(原理探索)

9 篇文章 0 订阅
4 篇文章 0 订阅

前言

上篇文章介绍了一些KVO的使用方式,包括监听多个字段改变的值,和监听可变数组等;这篇文章来探索一下KVO的实现原理;

原理探究

       本篇文章源码,欢迎大家一起查看下载

首先还是看一下官网的介绍

Key-Value Observing Implementation Details

Automatic key-value observing is implemented using a technique called isa-swizzling.

The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.

When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

翻译:

自动键值观察是使用一种叫做isa swizzing的技术实现的。

顾名思义,isa指针指向维护分派表的对象类。这个分派表本质上包含指向类实现的方法的指针以及其他数据。

当观察者注册一个对象的属性时,观察对象的isa指针被修改,指向一个中间类,而不是真类。因此,isa指针的值不一定反映实例的实际类。

决不能依赖isa指针来确定类成员身份。相反,您应该使用类方法来确定对象实例的类。

代码探索

我们定义XZPerson类,然后监听其中属性,与成员变量

@interface XZPerson : NSObject{
    @public//切记如果外部需要访问的话要加上@public
    NSString *name;
}
@property (nonatomic, copy) NSString *nickName;


- (void)sayHello;
- (void)sayLove;

@end

@implementation XZPerson
- (void)setNickName:(NSString *)nickName{
    _nickName = nickName;
}


- (void)sayHello{
    
}
- (void)sayLove{
    
}

@end
@interface XZViewController ()
@property (nonatomic, strong) XZPerson *person;

@end

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

- (void)viewDidLoad {
    [super viewDidLoad];
    self.person = [[XZPerson alloc]init];
    
    [self.person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:NULL];
    
    // Do any additional setup after loading the view.
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    NSLog(@"%@",change);

}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person.nickName = @"alan";
}

@end

运行程序我们在nickName添加处打断点,使用LLDB调试查看下person的对象然后执行过在查看isa,

这里附上object_getClassName的源码,为获取isa 

const char *object_getClassName(id obj)
{
    return class_getName(obj ? obj->getIsa() : nil);
}

根据打印结果可以看出:在为监听前persond对象指向为:XZPerson 监听后为新的:NSKVONotifying_XZPerson;

注意:

这里虽然生成了NSKVONotifying_XZPerson类但是改变的只是person对象的

内部关系:画了个大概图 

那么我们NSKVONotifying_XZPerson和XZPerson之间的关系是什么呢?

写了方法来遍历一下当前类的所有子类进行验证一下:

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

在执行观察前后,打印出类的子类和方法:

    [self printClasses:[XZPerson class]];

    [self.person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:NULL];
    [self printClasses:[XZPerson class]];

执行结果为:

可以看出NSKVONotifying_XZPerson是继承于XZPerson类的;

监听成员变量和属性

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

- (void)viewDidLoad {
    [super viewDidLoad];
    self.person = [[XZPerson alloc]init];
    
    [self printClasses:[XZPerson class]];

    [self.person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:NULL];
    [self printClasses:[XZPerson class]];


    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    // Do any additional setup after loading the view.
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    NSLog(@"%@",change);

}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"实际情况:%@--%@",self.person.nickName,self.person->name);
    self.person.nickName = @"alan";
    self.person->name = @"星";
}

执行后,点击屏幕来看一下打印结果:

只观察到了属性的变化;由此可以得到,KVO实际观察的是setter方法值的变化

接下来来看一下NSKVONotifying_XZPerson方法,

遍历类中方法:

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

在监听前后添加上:

  [self printClasses:[XZPerson class]];
    [self printClassAllMethod:[XZPerson class]];

    [self.person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:NULL];
    [self printClasses:[XZPerson class]];
    [self printClassAllMethod:NSClassFromString(@"NSKVONotifying_XZPerson")];

看下打印结果:

因为这里打印的是当前子类的方法:

可以看出这个子类中实现了setNickName,class,delloc,_isKVOA方法:(也可以根据指针看出)

class 方法,指针指针指回XZPerson;delloc 析构;_isKVOA 标示KVO

接下来当移除KVO后person对象是否会指回XZPerson;

在delloc处打断点

可以看出当delloc,移除观察后,self.person指回了XZPerson;

那么移除观察后NSKVONotifying_XZPerson类是否可以像自己创建的类一样一直持续在缓存中呢?

以及页面写入打印XZPersong所有子类方法

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    NSLog(@"%@",change);

}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"实际情况:%@--%@",self.person.nickName,self.person->name);
    self.person.nickName = @"alan";
    self.person->name = @"星";
}

重新运行程序,将二级界面的打印情况都去掉,只保存注册通知,前后点击第一页面屏幕,查看打印结果

由此可以看出,在移除通知后NSKVONotifying_XZPerson类还在内存中保存;

总结

这篇文章主要描述了KVO的原理探究过程;KVO原理为:

1.动态生成子类:NSKVONotifying_xxx

2.观察的是setter方法

3.动态子类重写很多方法setNickName,class,delloc,_isKVOA

        4.开启手动观察,消息转发给原类  

        5.消息发送 响应回调方法

6.移除观察后,isa指回了XZPerson

7.移除观察后中间动态子类是没有销毁

希望对大家有用处,欢迎大家点赞+评论,关注我的CSDN,我会定期做一些技术分享!未完待续。。。

 

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值