今天群里面有人提問.之前面試也有被問到,所以做個總結(jié).
如果有不對的地方,希望大神們更正.謝謝
首先,我們可能會有這樣的需求.觀察一個數(shù)組的變化.
@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];
但是運行時發(fā)生崩潰,由于__NSArrayM不支持添加觀察者.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<__NSArrayM 0x6000009ce820> addObserver:forKeyPath:options:context:] is not supported. Key path: count'
為什么報這個錯呢? 查看頭文件描述發(fā)現(xiàn)NSMutableArray根本沒有addObserver方法.他調(diào)用的居然是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方法來繼續(xù)實現(xiàn)KVO的.按理說可以給NSMutableArray添加一個- (void)setCount:(NSInteger)count?從而在運行時的時候可以找到setter方法?但是我嘗試了一下,沒有成功.所以應(yīng)該不是readonly的問題,而是運行時做了限制,直接拋出異常了.
給個鏈接:https://juejin.cn/post/6844903971488808968.
他說了前半部分,后半部分沒有解釋,我繼續(xù)說下去.
那么還有其他方法實現(xiàn)這個需求嗎?是有的.
// ??注意這里的觀察者是self,觀察的是self的成員變量kvoArray指針!!!
[self addObserver:self
forKeyPath:@"kvoArray"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
[[self mutableArrayValueForKey:@"kvoArray"] addObject:@"54"];
那么他是做了什么呢?
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
具體可以去看注釋.大概意思是,返回一個全新的可變數(shù)組,mutableCopy自原數(shù)組,并且追加了54.下圖中的方法是重寫了當前ViewController的KVOArray.setter().

??等等.上圖左側(cè)的兩個類是個啥?
NSKeyValueSlowMutableArray : NSKeyValueMutableArray : NSMutableArray : NSArray
NSKeyValueNotifiyingMutableArray : NSKeyValueMutableArray : NSMutableArray : NSArray
我估計,蘋果的做法就是生成NSMutableArray子類,他既然不能重寫setCount,那他就重寫addObject.然后生成新數(shù)組,返回給監(jiān)聽者.厲害了.

然后打印新舊數(shù)組發(fā)現(xiàn),有一個新的數(shù)組替換了.地址已經(jīng)改變?nèi)鐖D所示

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

發(fā)現(xiàn)什么了嘛.沒有NSKeyValueChangeOldKey噢.
但是kind == NSKeyValueChangeInsertion.
也就解釋了,為什么是[self kvo:self key:kvoArray];因為他壓根沒監(jiān)聽原數(shù)組的改變,而是監(jiān)聽的當前的viewController.kvoArray.
總結(jié):對數(shù)組count的監(jiān)聽,其實是用mutableArrayValueForKey在NSMutableArray的子類重寫了- (void)addObject:(id)obj生成了一個新的數(shù)組,然后調(diào)用了viewController的- (void)setKvoArray:(NSMutableArray:)kvoArray;把新數(shù)組傳遞過去.最后通知觀察者調(diào)用- (void)observeValueForKeyPath;