iOS开发底层之KVO探索上 - 17
Posted iOS_developer_zhong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之KVO探索上 - 17相关的知识,希望对你有一定的参考价值。
文章目录
前言
本章内容主要是围绕KVO进行探索,从KVO的介绍 -》KVO的坑点 -》 KVO的大致流程 -》KVO的自定义实现 -》优秀的KVO封装库介绍。
一、KVO是什么?
KVO的全称为:Key-Value Observing,“键值监听”。
主要作用为:监听某个对象属性值的改变,继而进行对应的业务处理。
简单使用的代码:
self.person = [[MYPerson alloc] init];
// 属性监听
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
// KVO的释放
-(void)dealloc
[self.person removeObserver:self forKeyPath:@"likeSome"];
// 方便调试
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
int b = 100 + (arc4random() % 101);
NSString *tt = [NSString stringWithFormat:@"value: %d",b];
self.person.name = tt;
// kvo值改变回调
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
NSLog(@"--keypath = 【%@】 -change = 【%@】", keyPath,change);
二、KVO注意项
1. KVO中的Context有什么作用?
根据官方文档 : KVO官方说明
上面介绍到: Context起到的作用就是一个标识符, 主要是为了监听对象更加安全,万一监听的属性路径一致,导致无法区分监听场景而诞生的,下面展示下官方的一段源码:
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
- (void)registerAsObserverForAccount:(Account*)account
//不同的context的监听
[account addObserver:self
forKeyPath:@"balance"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountBalanceContext];
[account addObserver:self
forKeyPath:@"interestRate"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountInterestRateContext];
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
if (context == PersonAccountBalanceContext)
// Do something with the balance…
else if (context == PersonAccountInterestRateContext)
// Do something with the interest rate…
else
// Any unrecognized context must belong to super
2. 忘记移除观察者,而造成程序的崩溃
官方文档中,有这么一句话:
An observer does not automatically remove itself when deallocated. The observed object continues to send notifications, oblivious to the state of the observer. However, a change notification, like any other message, sent to a released object, triggers a memory access exception. You therefore ensure that observers remove themselves before disappearing from memory.
大概的意思就是: 观察者不会在dealloc的时候自动移除,而观察对象后续的操作就算观察者依附的对象已经dealloc也会继续发送消息, 这样就会导致程序崩溃。 所以我们需要手动移除观察者, 后面有更加优化的方案解决这个问题。 请继续读下去。
3. 控制某些属性不能使用KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
if ([key isEqual: @"name"])
return false;
else
return true;
4. 一对多的观察。
是否有遇到过这种场景: A属性的值, 是取决于另外两个属性B、C之间的计算才能得出, 这个时候对A属性进行观察,怎么办呢?
官方举例: fullName = firstName + lastName
当firstName 或者 lastName ,任意一个属性发生改变,必然会影响到 fullName。
那么就需要用到下面这个方法:
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"fullName"])
NSArray *affectingKeys = @[@"lastName", @"firstName"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
return keyPaths;
// 写法二:
+ (NSSet *)keyPathsForValuesAffectingFullName
return [NSSet setWithObjects:@"lastName", @"firstName", nil];
// fullName的get方法
- (NSString *)fullName
return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
// 其他的流程和前面写的一样只要去监听 fullName
[self.person addObserver:self forKeyPath:@"fullName" options:NSKeyValueObservingOptionNew context:nil];
5. 对可变数组的KVO。
通过mutableArrayValueForKey 来实现, 直接上用法。
[self.person addObserver:self forKeyPath:@"dateArray" options:NSKeyValueObservingOptionNew context:NULL];
self.person.dateArray = [NSMutableArray array];
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"hello"];
// 通过 mutableArrayValueForKey ,kvo是基于KVC之上
三、KVO的流程和原理
自动键值观察:是通过isa-swizzling的技术实现。
- 在原有类的基础上新增一个派生类 NSKVONotifying_ 开头。
可通过代码来调试出来:
self.person = [[MYPerson alloc] init];
// 没有使用KVO之前,打印所有类。
[self printClasses:[self.person class]];
// 属性
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
// 使用KVO再打印所有类。
[self printClasses:[self.person class]];
// 利用runtime打印所有的类方法。
- (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);
// 打印结果
2021-09-10 17:31:51.644193+0800 001-内存对齐原则[18934:903108] classes = (
MYPerson
)
2021-09-10 17:31:51.665579+0800 001-内存对齐原则[18934:903108] classes = (
MYPerson,
"NSKVONotifying_MYPerson"
)
// 很清楚的看见,当使用kvo后, 会新增加一个NSKVONotifying_MYPerson 类。
- 关于在移除观察者的时候, 会不会吧isa指向会原类。
我们在移除观察者代码前后,打上断点去观察, 就会发现,isa会指向原类 。
-(void)dealloc
[self.person removeObserver:self forKeyPath:@"name"];
// lldb调试 结果
(lldb) p object_getClassName(self.person)
(const char * _Nonnull) $0 = 0x000000028232f380 "NSKVONotifying_MYPerson"
(lldb) p object_getClassName(self.person)
(const char * _Nonnull) $1 = 0x0000000100907618 "MYPerson"
整体流程为:
当前类设置观察者后, 会派生一个子类(名称开头为NSKVONotifying_), 这个派生类和原来的类一模一样, 并且会在原来的类之上增加对属性的 setter方法监听,也就是 willChange , didChange。 当这些被观察的属性发生改变的时候,就会给所有的观察者,发送指令, 这个属性改变了,你该干啥就要干啥了。 这些设置好后,就把原类的isa指向这个派生的类, 最后在原类移除观察者的时候,就会将isa还原回来。
以上是关于iOS开发底层之KVO探索上 - 17的主要内容,如果未能解决你的问题,请参考以下文章