iOS底层探索之KVO(二)—KVO原理分析

50 篇文章 5 订阅
5 篇文章 1 订阅

回顾

上一篇博客中,已经介绍了KVO的相关操作,那么接下来就去探索一下KVO的底层逻辑,KVO到底是如何实现的呢?

在这里插入图片描述

  • 在官方文档中有如下图中的说明
    isa-swizzling

键值观察是使用称为isa-swizzling的技术实现的。

  • isa指针,顾名思义,指向对象的类,它保持一个调度表。该调度表主要包含指向类实现的方法的指针,以及其他数据。

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

  • 你不应该依赖isa指针来确定类的成员。相反,应该使用该class方法来确定实例对象的类。

1. isa-swizzling验证

在添加观察者处,打上断点,再控制台lldb调试看看。

- (void)viewDidLoad {
	[super viewDidLoad];
	self.student = [[JPStudent alloc]init];
	[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
}
  • 控制台打印如下
    lldb调试

addObserverstudentJPStudent变成了NSKVONotifying_JPStudent

我们知道,实例对象的关系实际上就是实例对象的isa指向了类对象。所以这里我们可以推断,self.student在调用addObserver方法后,已经从JPStudent类的实例对象,变成了NSKVONotifying_JPStudent
的实例对象。

2. NSKVONotifying_JPStudent子类验证

  • 那么这个NSKVONotifying_JPStudent是什么东西呢?是一开始就直接存在,还是和JPStudent类之间有什么关系呢?那么看看NSKVONotifying_JPStudent是不是一开始就存在的,再次运行代码,断点还是断在添加观察者者处,打印一下
    NSKVONotifying_JPStudent生产测试

提醒objc_getClassruntimeapi,一定要导入头文件才可以正常使用,如图所示。

在调用addObserver方法前后分别打印,结果说明NSKVONotifying_JPStudent是系统动态生成添加的一个类。这两个类名字这么相似,有没有可能是JPStudent的子类呢?我们打印一下看看
验证NSKVONotifying_JPStudent是JPStudent的子类

从打印来看,发现了新大陆,NSKVONotifying_JPStudent 确实是继承自JPStudent的。那么这个中间类,有没有可能存在自己的子类呢?我们通过下面这段代码来看看

#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_JPStudentJPStudent的子类。
    那么NSKVONotifying_JPStudent这个类里面都有些什么内容呢?类里面一般也就是存储了成员变量方法协议等信息,那么通过下面这段代码来看看它里面都有什么。
#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);
}
  • 打印如下
    遍历方法-ivar-property

从打印结果来看,系统重写了setNickNameclassdealloc这几个方法,并且添加了一个叫_isKVOA的方法,来区分是不是系统通过KVO自动生成的。

3. 观察者被移除isa的指向?

官方文档中说:调用addObserver方法会修改isa指向,那么现在我们移除观察者后系统会怎么做呢?我们在dealloc方法中移除观察者这里打上断点,然后继续观察self.studentisa指向。
观察者被移除isa的指向?

移除观察者之后,self.studentisa又指回了JPStudent类。并且生成的子类NSKVONotifying_JPStudent 还在,没有进行销毁。

原因是如果下次继续进行观察者添加,系统就不会再生成新的中间类,而是直接使用这个类了,防止资源的浪费。

4. class方法

在添加观察者之后,我们都知道会生成动态子类NSKVONotifying_JPStudent

那么调用class方法p self.student.class打印的是 NSKVONotifying_JPStudent 吗???

  • 断点在添加观察者之后,我们控制台验证一下
    self.student.class
    从打印结果看,输出的还是JPStudent,虽然self.studentisa已经指向NSKVONotifying_JPStudent了,但是由于NSKVONotifying_JPStudent重写了class方法,最后打印输出的还是JPStudent ,苹果这么做的目的是为了隐藏系统在背后做的一系列操作,让开发者更少的关注底层逻辑,只关注上层的代码实现就可以。

5. setter方法

既然重写了setter方法观察属性,那么如果有成员变量,是否也可以能观察呢?增加age成员变量,测试一下

@interface JPStudent : NSObject
{
	@public
	int age;
}
@property (nonatomic, copy) NSString *name;

@end 

setter方法测试

当对age进行赋值的时候,并没有触发监听的回调方法。那么就说明了只是对属性的setter方法进行的监听。

我们再看看在dealloc中观察者移除isa指回的时候,查看name的值
观察者移除后Name的值

那就说明在KVO生成的类中对name的修改影响到了原始类。

name下个内存断点调试看看
断点调试

  • bt 打印堆栈信息
    bt 打印堆栈信息

发现调用了Foundation的一些方法,最后才是[JPStudent setName:]name赋值。

提示Foundation框架是不开源的。

  • Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
  • Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey🔑key:usingBlock:]
  • Foundation`_NSSetObjectValueAndNotify

_NSSetObjectValueAndNotify汇编调用主要如下:

_NSSetObjectValueAndNotify

"willChangeValueForKey:"
这里是调用setter方法赋值
"didChangeValueForKey:"
"_changeValueForKey:key:key:usingBlock:"

从堆栈信息和汇编可以知道,在_changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:中进行赋值后的回调,那么肯定得通知监听者

  • observeValueForKeyPath的回调中打个断点:

在这里插入图片描述

确认是在NSKeyValueNotifyObserver通知中进行的回调。

6.总结

  • KVO添加观察者addObserver动态生成子类NSKVONotifying_XXX
  • 重写class方法,返回父类class信息。父类isa指向子类。
    给动态子类添加setter方法(所有要观察的属性)。
    消息转发给父类。
  • setter会调用父类原来的方法进行赋值,完成后进行回调通知。
  • 移除observer的时候isa指回父类,动态生成的子类并不会销毁.

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Flutter和Objective-C(简称OC)都是开发移动应用程序的技术,但它们在很多方面有着不同的特点。 首先,Flutter是一种跨平台的移动应用开发框架,由谷歌开发。它使用Dart语言编写,具有热重载、响应式UI框架和丰富的UI组件等特点。Flutter的一大优势是可以同时在iOS和Android平台上开发应用程序,并且拥有高性能和良好的用户体验。Flutter也支持使用原生代码进行集成,因此可以很好地与Objective-C进行交互。 Objective-C是一种面向对象的编程语言,主要用于iOS和macOS平台的应用程序开发。Objective-C采用了一种称为KVO(Key-Value Observing)的机制,允许对象对属性和值的变化进行观察和响应。通过注册观察者,当被观察对象的属性发生变化时,观察者可以接收到通知并执行相应的操作。KVO是一种非常强大的工具,可以用于实现对象之间的数据绑定和通信。 在使用Flutter开发应用时,可以与Objective-C进行集成,并利用Objective-C提供的KVO机制来实现对Flutter应用内部变量的监视和响应。这可以通过在Flutter与Objective-C之间建立桥接来实现,从而达到在Flutter应用中使用KVO的目的。 总的来说,Flutter和Objective-C KVO是两种不同的技术,Flutter是一个跨平台的移动应用开发框架,而Objective-C KVO是一种可以用于观察和响应对象属性变化的机制。在合适的场景下,可以通过Flutter与Objective-C进行集成,从而利用KVO机制来实现对Flutter应用内部变量的监视和响应。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡卡西Sensei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值