iOS關(guān)于容器類型的KVO問題

今天群里面有人提問.之前面試也有被問到,所以做個總結(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的方法?

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方法來繼續(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.下圖中的方法是重寫了當前ViewControllerKVOArray.setter().

image.png

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

image.png

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


image.png

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

image.png

發(fā)現(xiàn)什么了嘛.沒有NSKeyValueChangeOldKey噢.
但是kind == NSKeyValueChangeInsertion.
也就解釋了,為什么是[self kvo:self key:kvoArray];因為他壓根沒監(jiān)聽原數(shù)組的改變,而是監(jiān)聽的當前的viewController.kvoArray.

總結(jié):對數(shù)組count的監(jiān)聽,其實是用mutableArrayValueForKeyNSMutableArray的子類重寫了- (void)addObject:(id)obj生成了一個新的數(shù)組,然后調(diào)用了viewController- (void)setKvoArray:(NSMutableArray:)kvoArray;把新數(shù)組傳遞過去.最后通知觀察者調(diào)用- (void)observeValueForKeyPath;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容