【iOS KVO(上)实现过程】

这篇具有很好参考价值的文章主要介绍了【iOS KVO(上)实现过程】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

KVO 也适用于传值,在之前的学习只是学习了KVO的传值,今天详细学习 监听和实现

源码放在下一节学习

1.1 KVO

KVO(Key-Value Observing)是Objective-C语言中一种观察者模式的实现,可以用来监听对象属性值的变化。KVO机制允许一个对象注册为另一个对象的属性变化的观察者,并在被观察的属性值发生变化时,自动接收通知并进行相应处理。

KVO可以实现监听某个属性的变化 KVO机制只能监听对象属性值的变化,无法监听基本数据类型的变化,需要将其封装为对象属性才能实现传递。)

KVO可以实现界面之间的传值,跨界面可以。

1.2 使用KVO

现在有如下的场景
【iOS KVO(上)实现过程】
我们点击change按钮需要改变 上面的Label,同时我们需要监听Label的变化,看看如何用KVO实现键值监听

初始化按钮

- (UILabel *)test_label_init {
    if (!self.test_label) {
        self.test_label = [[UILabel alloc] init];
        self.test_label.text = @"Label not Change";
        self.test_label.font = [UIFont systemFontOfSize:25];
        [self.view addSubview:self.test_label];
        self.test_label.frame = CGRectMake(120, 170, 300, 30);
        [self.test_label addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    }
    return self.test_label;
}
- (UIButton *)test_button_init {
    if (!self.test_button) {
        self.test_button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        [self.test_button setTitle:@"change" forState:UIControlStateNormal];
        [self.test_button addTarget:self action:@selector(changeLabel) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:self.test_button];
        self.test_button.frame = CGRectMake(120, 260, 100, 100);
        self.test_button.backgroundColor = [UIColor redColor];
    }
    return self.test_button;
}

注册KVO监听

通过[addObserver:forKeyPath:options:context:]方法注册KVO,这样可以接收到keyPath属性的变化事件;
【iOS KVO(上)实现过程】
observer:观察者,监听属性变化的对象。该对象必须实现observeValueForKeyPath:ofObject:change:context: 方法。
keyPath:要观察的属性名称。要和属性声明的名称一致。
options:回调方法中收到被观察者的属性的旧值或新值等,对KVO机制进行配置,修改KVO通知的时机以及通知的内容
context:传入任意类型的对象,在"接收消息回调"的代码中可以接收到这个对象,是KVO中的一种传值方式。、

KVO监听实现

通过方法[observeValueForKeyPath:ofObject:change:context:]实现KVO的监听;
【iOS KVO(上)实现过程】

keyPath:被观察对象的属性
object:被观察的对象
change:字典,存放相关的值,根据options传入的枚举来返回新值旧值
context:注册观察者的时候,context传递过来的值

点击Button

【iOS KVO(上)实现过程】
【iOS KVO(上)实现过程】

移除KVO监听

在不需要监听的时候,通过方法[removeObserver:forKeyPath:],移除监听;

禁止KVO

我们可以手动禁止KVO监听某个属性的变化

automaticallyNotifiesObserversForKey:是一个可选的类方法,用于自定义KVO机制中属性变化通知的行为。当对象的属性发生变化时,系统会自动调用这个方法来获取是否自动发送通知。

这个方法通常被用于实现一些高级的KVO机制,比如当某个属性的变化依赖于其他属性时,可以在这个方法中检测相关属性的变化情况,从而决定是否发送属性变化通知。

我们实现一个Label的分类,在里面重写这个方法

#import "UILabel+autoCall.h"

@implementation UILabel (autoCall)
// 选择性的实现KVO
// 因为是类方法 并且改变的是Label 所以需要创建一个分类来给UILabel重写这个方法 即可完成禁止通知

// 那么也就不会实现 observeValueForKeyPath
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"text"]) {
        NSLog(@"NO WAY");
        return NO;
    } else {
        return [super automaticallyNotifiesObserversForKey:key];
    }
//    return NO;
}
@end

【iOS KVO(上)实现过程】

1.3 KVO实现监听数组内部元素变化

KVO默认只能监听到数组对象本身的变化,而无法监听到数组内部元素的变化。例如,如果将一个对象添加到数组中,KVO会收到数组count属性的变化通知,但不会收到数组内部元素的变化通知。

KVO机制只能监听对象属性值的变化,无法监听基本数据类型的变化,那么如何实现KVO监听数组内部元素的变化?

KVO(Key-Value Observing)是Objective-C语言中一种观察者模式的实现,可以用来监听对象属性值的变化。但是KVO不能直接监听数组的变化,因为NSArray和NSMutableArray并没有实现KVO机制。如果需要监听数组的变化,可以使用以下两种方式:

1.3.1 手动触发KVO通知

当数组中的元素发生变化时,手动触发KVO通知即可实现监听。具体实现方式如下:

在被观察对象的类中,重写该对象所包含的可变数组的对应方法,比如addObject:、removeObject:、insertObject:atIndex:等方法。
在重写的方法中,调用willChangeValueForKey:和didChangeValueForKey:方法,手动触发KVO通知。

- (void)addObject:(id)anObject {
    [self willChangeValueForKey:@"myArray"];
    [super addObject:anObject];
    [self didChangeValueForKey:@"myArray"];
}

在观察者中注册被观察对象的数组属性,当数组中的元素发生变化时,观察者会收到KVO通知。

[observedObject addObserver:self forKeyPath:@"myArray" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

需要注意的是,手动触发KVO通知需要在重写的方法中手动添加代码,实现起来比较麻烦,容易出错,因此不是一种推荐的方式。

1.3.2 使用NSKeyValueObservingOptionNewNSKeyValueObservingOptionOld选项

KVO支持使用NSKeyValueObservingOptionNewNSKeyValueObservingOptionOld选项**,来监听可变数组中的元素变化。这两个选项会在KVO通知中包含新旧值的信息,因此可以在观察者中获取到数组中元素的变化。**

[observedObject addObserver:self forKeyPath:@"myArray" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

在观察者中实现observeValueForKeyPath:ofObject:change:context:方法,根据KVO通知中的信息来处理数组元素的变化。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"myArray"]) {
        NSArray *oldArray = change[NSKeyValueChangeOldKey];
        NSArray *newArray = change[NSKeyValueChangeNewKey];
        // 处理数组元素的变化
    }
}

这种方式需要被观察的对象的数组属性必须是可变的,而且只能监听到元素的增加、删除和替换操作,

1.3.4 使用KVO的注意事项⚠️

  • keyPath 不能为空字符串
  • 注意在适合的地方removeObersver,如果观察实例比被观察实例先释放,这时候改变观察属性,会产生崩溃。
  • 没有添加,直接移除观察关系,也会产生崩溃

1.4 KVO的实现

KVO的实现原理分为3步走

1.4.1 实现

首先明确 KVO机制的实现原理是,当一个对象被观察时,系统会动态地生成一个派生类并将被观察对象的isa指针指向该派生类。这个派生类重写了被观察对象的setter方法,在setter方法中,除了进行属性值的赋值操作,还会通知观察者对象属性值的变化。

如何查看派生类?
情景:现在有一个testClass的类,里面有一个className属性,现在监听className属性

  self.testClass = [[TestClass alloc] init];
    NSLog(@"%s: isa = %s", object_getClassName(self.testClass), class_getName(object_getClass(self.testClass)));
   [self.testClass addObserver:self forKeyPath:@"className" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    NSLog(@"%s: isa = %s", object_getClassName(self.testClass), class_getName(object_getClass(self.testClass)));

其中,object_getClassName()函数可以返回对象所属的类名,object_getClass()函数可以返回对象的实际类(class),而不是对象所属的类的类型。

当这段代码执行后,就会在控制台输出该对象的类名和实际类名。实际类名即为系统动态生成的派生类名,以NSKVONotifying_为前缀,后面紧跟着被观察对象的类名。例如,如果被观察对象是一个Person对象,那么实际类名就是NSKVONotifying_Person。

注意,这种方法只适用于Objective-C的KVO机制。对于Swift的KVO机制,由于其机制不同,不能通过此方法来打印查看。【iOS KVO(上)实现过程】

如何生成派生中间类?

isa-swizzling(类指针交换):
就是把当前某个实例对象的isa指针指向一个新建造的中间类,在这个新建造的中间类上面做hook方法或者别的事情,这样不会影响这个类的其他实例对象,仅仅影响当前的实例对象。

【iOS KVO(上)实现过程】
【iOS KVO(上)实现过程】
在添加观察者之后 NSKVONotifying_testClass如何内部实现

1.4.2 NSKVONotifying_testClass如何内部实现

- setName:最主要的重写方法,set值时调用通知函数
- class:返回原来类的class
- dealloc
- _isKVOA判断这个类有没有被KVO动态生成子类

- (void)setClassName:(NSString *)className {

}

- (Class)class {
- 这是为了保证该中间类在外部使用时可以替代原始类,实现完全透明的KVO功能。
    return [testClass class];
}

- (void)dealloc {
    // 收尾工作
}

- (BOOL)_isKVOA {
- 添加一个名为_isKVOA的实例变量**,用于标识该对象是否支持KVO机制。
    return YES;
}


isa指向中间类之后如何调用方法:【iOS KVO(上)实现过程】
对于这两个属性 我们只监听了className属性而没有监听testArray属性
【iOS KVO(上)实现过程】

  • 调用监听的属性的设置方法,例如:setClassName:,都会先调用NSKVONotify_testClass对应的属性设置方法
  • 调用非监听属性的设置方法,如setClassArray方法,就会通过NSKVONotify_ApplesuperClass来找到testClass类对象,在调用其Apple类对象中的test方法

重写Class方法 :这是为了保证该中间类在外部使用时可以替代原始类,实现完全透明的KVO功能。

添加一个名为_isKVOA的实例变量,用于标识该对象是否支持KVO机制。

1.4.3 _NSSetObjectValueAndNotify

在具体实现过程中,系统会动态生成一个继承自原始类的中间类,并且在该类的初始化方法中,调用了一个叫做_NSSetObjectValueAndNotify()的函数,用于实现属性改变的通知。

_NSSetObjectValueAndNotify()函数的实现过程如下:

a) 首先会调用 willChangeValueForKey

b) 然后给属性赋值

c) 最后调用 didChangeValueForKey

d) 最后调用 observer 的 observeValueForKeyPath 去告诉监听器属性值发生了改变 .文章来源地址https://www.toymoban.com/news/detail-434065.html

1.5 总结KVO

1.5.1 KVO的本质

  • 利用runtime的API动态生成一个子类,并让实例对象的isa指向这个全新的子类
  • 当修改实例变量对象的属性时候,在全新子类的set方法中会调用Foundation的_NSSetXXXValueAndNotify函数
    willChangeValueForKey
  • 调用原来的setter
  • didChangeValueForKey:内部会触发监听器的监听方法

1.5.2 KVO使用场景

  • 对于时刻变化的对象,例如colletionView的items,总是动态的变化,这个时候可以使用KVO监听对象。
  • 在AVFounditon中获取AVPlayer的播放进度,播放状态,也需要使用KVO来观察。

1.5.3 实现过程总结

  • addObserver:forKeyPath:options:context:context调用的时候,会自动生成并注册一个该对象(被观察的对象)对应类的子类,取名NSKVONotify_Class,并且将该对象的isa指针指向这个新的类。
  • 在该子类内部实现4个方法-被观察属性的set方法、class方法、isKVO、delloc。
  • 最关键的是set方法中,先调用willChangeValueForKey,再给成员变量赋值,最后调用didChangeValueForKeywillChangeValueForKey和didChangeValueForKey需要成对出现才能生效,在didChangeValueForKey中会去调用观察者的observeValueForKeyPath: ofObject: 方法。
  • 重写class方法,这样避免外部感知子类的存在,同时防止在一些使用isKindOfClass判断的时候出错。
  • isKVO方法作为能否实现KVO功能的一个标识。
  • delloc里面还原isa指针

KVO还很多内部的类和实现 先学会实现,知道实现的过程,接着学习源码和之前的实现相对应学习会更好。

到了这里,关于【iOS KVO(上)实现过程】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Winform学习笔记(八)】通过委托实现跨窗体传值

    在本文中主要介绍 如何通过委托实现跨窗体传值,并以简单的示例进行展示。 委托 :委托是方法的抽象,与类一样是一种用户自定义类型,存储的是一系列具有相同签名和返回类型的方法的地址。调用委托的时候,委托包含的所有方法将被执行; 委托类型声明 :委托是类

    2024年02月11日
    浏览(45)
  • 【iOS】多界面传值

    在写网易云音乐以及3GShare包括后面的学生管理系统时,用到许多界面传值方法,特撰写博客记录目前学过的几种多界面传值方法 属性传值是通过定义属性并设置值来实现传递数据的方式, 多用于前一个页面向后一个页面传值 假设有两个视图控制器:ViewControllerA 和 ViewContr

    2024年02月16日
    浏览(35)
  • 如何使用租用的云服务器实现神经网络训练过程(超详细教程,新手小白适用)

    超级感谢up主7_xun的B站教学视频: 适合深度学习小白的CV实战——在AutoDL上租用云服务器跑YOLOv5的全过程 链接:适合深度学习小白的CV实战——在AutoDL上租用云服务器跑YOLOv5的全过程_哔哩哔哩_bilibili 在GitHub中搜索yolov,点击第一个项目,ultralytics/yolov5 点击第一个项目进入后,

    2023年04月21日
    浏览(51)
  • 【c语言】详解c语言#预处理期过程 | 宏定义前言

    c语言系列专栏: c语言之路重点知识整合   创作不易,本篇文章如果帮助到了你,还请点赞支持一下♡𖥦)!!  主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ 代码编译到执

    2024年02月01日
    浏览(46)
  • 驱动开发,IO多路复用实现过程,epoll方式

    被称为当前时代最好用的io多路复用方式; 核心操作:一棵树(红黑树)、一张表(内核链表)以及三个接口;  思想:(fd代表文件描述符)         epoll要把检测的事件fd挂载到内核空间红黑树上,遍历红黑树,调用每个fd对应的操作方法,找到发生事件的fd,如果没有发

    2024年02月07日
    浏览(51)
  • TCP服务器的演变过程:IO多路复用机制select实现TCP服务器

    手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。 为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。 本节,在上一章节的基础上,将并发的实现改为IO多路复用机制,使用select管理每个新接入的客户端连

    2024年02月03日
    浏览(56)
  • 课程学习前言

    app 抓包分析可以看到有签名有加固,毕竟需要 APK 去访问服务、获取数据,都需要 APK 有完整的信息,而这些信息、代码经过各种加密,还是放在 APK 里面。说白了,就是门锁紧了,钥匙藏在门口某个地方,也许就是地垫下面 逆向流程 拿到 App 应用的 apk ; 使用工具进行查壳

    2024年02月06日
    浏览(43)
  • 【C++】C++学习前言

    C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(objectoriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。

    2024年03月12日
    浏览(54)
  • 【自制C++深度学习框架】前言

    此GitHub项目是一个初学者的深度学习框架,使用C++编写,旨在为用户提供一种简单、易于理解的深度学习实现方式。以下是本项目的主要特点和功能: 计算图:使用计算图来描述深度学习模型的计算过程,利用计算图将神经网络的计算过程视为一个有向无环图。通过构建计算

    2024年02月07日
    浏览(42)
  • Gowin FPGA学习记录——前言

            好久没有写博客了,想想是不是又该写点啥东西了么,准备写点国产FPGA的使用经历吧                  得益于目前国内的政策对国产化芯片扶持,越来越要求核心器件能够自主可控,因此作为核心芯片FPGA,国产FPGA的势头也发展很快。          现在FPGA的这

    2024年02月16日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包