美文网首页
iOS关于容器类型的KVO问题

iOS关于容器类型的KVO问题

作者: 客三消 | 来源:发表于2021-01-19 15:59 被阅读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;

  • 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.下图中的方法是重写了当前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;

相关文章

网友评论

      本文标题:iOS关于容器类型的KVO问题

      本文链接:https://www.haomeiwen.com/subject/umiqzktx.html