iOS关于容器类型的KVO问题

2021-01-19  本文已影响0人  客三消

今天群里面有人提问.之前面试也有被问到,所以做个总结.

如果有不对的地方,希望大神们更正.谢谢

首先,我们可能会有这样的需求.观察一个数组的变化.

@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的方法?

NSArrayAddObserver.jpg

我们跟进去看一下描述吧.

@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;

那么还有其他方法实现这个需求吗?是有的.

// ⚠️注意这里的观察者是self,观察的是self的成员变量kvoArray指针!!!
[self addObserver:self
       forKeyPath:@"kvoArray"
          options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
          context:nil];
[[self mutableArrayValueForKey:@"kvoArray"] addObject:@"54"];

那么他是做了什么呢?

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

具体可以去看注释.大概意思是,返回一个全新的可变数组,mutableCopy自原数组,并且追加了54.下图中的方法是重写了当前ViewControllerKVOArray.setter().

image.png

⚠️等等.上图左侧的两个类是个啥?
NSKeyValueSlowMutableArray : NSKeyValueMutableArray : NSMutableArray : NSArray
NSKeyValueNotifiyingMutableArray : NSKeyValueMutableArray : NSMutableArray : NSArray
我估计,苹果的做法就是生成NSMutableArray子类,他既然不能重写setCount,那他就重写addObject.然后生成新数组,返回给监听者.厉害了.

image.png

然后打印新旧数组发现,有一个新的数组替换了.地址已经改变如图所示


image.png

接着我们来看:- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context返回值.

image.png

发现什么了嘛.没有NSKeyValueChangeOldKey噢.
但是kind == NSKeyValueChangeInsertion.
也就解释了,为什么是[self kvo:self key:kvoArray];因为他压根没监听原数组的改变,而是监听的当前的viewController.kvoArray.

总结:对数组count的监听,其实是用mutableArrayValueForKeyNSMutableArray的子类重写了- (void)addObject:(id)obj生成了一个新的数组,然后调用了viewController- (void)setKvoArray:(NSMutableArray:)kvoArray;把新数组传递过去.最后通知观察者调用- (void)observeValueForKeyPath;

上一篇下一篇

猜你喜欢

热点阅读