今天群里面有人提问.之前面试也有被问到,所以做个总结.
如果有不对的地方,希望大神们更正.谢谢
首先,我们可能会有这样的需求.观察一个数组的变化.
@interface ViewController ()
@property (nonatomic, strong) NSMutableArray * kvoArray;
@end
self.kvoArray = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", nil];
[self.kvoArray addObserver:self
forKeyPath:@"count"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
但是运行时发生崩溃,由于__NSArrayM
不支持添加观察者.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<__NSArrayM 0x6000009ce820> addObserver:forKeyPath:options:context:] is not supported. Key path: count'
为什么报这个错呢? 查看头文件描述发现NSMutableArray
根本没有addObserver
方法.他调用的居然是NSArray的方法?
我们跟进去看一下描述吧.
@property (readonly) NSUInteger count;
/* NSArrays are not observable,
* so these methods raise exceptions when invoked on NSArrays.
* Instead of observing an array,
* observe the ordered to-many relationship for which the array is the collection of related objects.
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
-
NSMutableArray.count
是只读的. -
NSArray
是不可观察的.
自定义的类,如果属性声明为readonly
其实是可以通过手动添加setter方法来继续实现KVO的.按理说可以给NSMutableArray添加一个- (void)setCount:(NSInteger)count
?从而在运行时的时候可以找到setter方法?但是我尝试了一下,没有成功.所以应该不是readonly
的问题,而是运行时做了限制,直接抛出异常了.
给个链接:https://juejin.cn/post/6844903971488808968.
他说了前半部分,后半部分没有解释,我继续说下去.
那么还有其他方法实现这个需求吗?是有的.
// ⚠️注意这里的观察者是self,观察的是self的成员变量kvoArray指针!!!
[self addObserver:self
forKeyPath:@"kvoArray"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
[[self mutableArrayValueForKey:@"kvoArray"] addObject:@"54"];
那么他是做了什么呢?
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
具体可以去看注释.大概意思是,返回一个全新的可变数组,mutableCopy自原数组,并且追加了54
.下图中的方法是重写了当前ViewController
的KVOArray.setter()
.
⚠️等等.上图左侧的两个类是个啥?
NSKeyValueSlowMutableArray : NSKeyValueMutableArray : NSMutableArray : NSArray
NSKeyValueNotifiyingMutableArray : NSKeyValueMutableArray : NSMutableArray : NSArray
我估计,苹果的做法就是生成NSMutableArray
子类,他既然不能重写setCount
,那他就重写addObject
.然后生成新数组,返回给监听者.厉害了.
然后打印新旧数组发现,有一个新的数组替换了.地址已经改变如图所示
接着我们来看:- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
返回值.
发现什么了嘛.没有NSKeyValueChangeOldKey
噢.
但是kind == NSKeyValueChangeInsertion
.
也就解释了,为什么是[self kvo:self key:kvoArray];
因为他压根没监听原数组的改变,而是监听的当前的viewController.kvoArray
.
总结:对数组count
的监听,其实是用mutableArrayValueForKey
在NSMutableArray
的子类重写了- (void)addObject:(id)obj
生成了一个新的数组,然后调用了viewController
的- (void)setKvoArray:(NSMutableArray:)kvoArray
;把新数组传递过去.最后通知观察者调用- (void)observeValueForKeyPath
;