今天群里面有人提问.之前面试也有被问到,所以做个总结.
如果有不对的地方,希望大神们更正.谢谢
首先,我们可能会有这样的需求.观察一个数组的变化.
@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;