在開(kāi)發(fā)過(guò)程中弟蚀,KVC支持我們使用字符串作為關(guān)聯(lián)標(biāo)識(shí)為對(duì)象的某個(gè)實(shí)例變量或?qū)傩赃M(jìn)行賦值媳纬,這個(gè)字符串可以是對(duì)象的某個(gè)屬性名或?qū)嵗兞棵肼簦疚奈覀儗⑼ㄟ^(guò)官方文檔描述來(lái)探尋KVC賦值邏輯释簿。
設(shè)置器方法:- (void)setValue:(nullable id)value forKey:(NSString *)key;
此方法根據(jù)關(guān)聯(lián)標(biāo)識(shí)字符串 key 以及設(shè)置值 value, 方法內(nèi)部通過(guò)以下三步進(jìn)行賦值操作:
1.查找設(shè)置器方法钦听。
根據(jù)方法參數(shù)key的值去依次匹配以下方法:
// 注意:以下<Key>為 - (void)setValue:(nullable id)value forKey:(NSString *)key; 中提供的key值
// 僅僅只是將首字母大寫(xiě)(如果以字母開(kāi)頭)并替換洒试,并不會(huì)對(duì)key值做其他額外操作來(lái)匹配存取器方法
// 例如key值以“_”下劃線開(kāi)頭,例如“_name”彪见,則匹配的方法為 -(void)set_name:(id)value; 2儡司、3同理
// 1.
- (void)set<Key>:(id)value;
// 2.
- (void)_set<Key>:(id)value;
// 3.
- (void)setIs<Key>:(id)value;
如果找到對(duì)應(yīng)方法,則將value作為參數(shù)調(diào)用此方法余指。此步驟不關(guān)心類中是否擁有相應(yīng)的屬性或成員變量捕犬,僅僅只是方法匹配。
2.查找相應(yīng)的實(shí)例變量
第一步中沒(méi)有找到相關(guān)設(shè)置器方法并且該類 accessInstanceVariablesDirectly 屬性返回YES酵镜,那么將按照 _<key>碉碉、 _is<Key>、 <key>淮韭、 is<Key> 的順序查找相匹配的實(shí)例變量垢粮,如果找到相應(yīng)的實(shí)例變量則對(duì)變量進(jìn)行賦值(注意:此過(guò)程直接對(duì)實(shí)例變量進(jìn)行賦值,不調(diào)用setter)靠粪。
3.-setValue:forUndefinedKey:
如果經(jīng)過(guò)步驟1和步驟2沒(méi)有找到相應(yīng)的屬性設(shè)置器或者實(shí)例變量蜡吧,-setValue:forUndefinedKey: 會(huì)被調(diào)用。
-setValue:forUndefinedKey: 方法默認(rèn)實(shí)現(xiàn)為拋出一個(gè) NSUndefinedKeyException 異常占键∥羯疲可以通過(guò)重寫(xiě)此方法進(jìn)行特殊處理或者空實(shí)現(xiàn)避免拋出異常。
- (void)setValue:(nullable id)value forKey:(NSString *)key 方法賦值過(guò)程中針對(duì)非OC對(duì)象的處理
- 設(shè)置器方法value參數(shù)是一個(gè)OC對(duì)象畔乙,但是有時(shí)我們需要設(shè)置的實(shí)例變量類型有可能是基本數(shù)據(jù)類型君仆、結(jié)構(gòu)體等,對(duì)于這種情況我們需要將值包裝成為NSNumber或者NSValue對(duì)象牲距,設(shè)置器方法內(nèi)部會(huì)在調(diào)用存取器方法或?yàn)閷?shí)例變量賦值之前對(duì)value進(jìn)行逆轉(zhuǎn)換操作返咱。
- 當(dāng)檢查發(fā)現(xiàn)存取器方法參數(shù)或?qū)嵗兞款愋蜑榉菍?duì)象類型,并且value為nil則 -setNilValueForKey: 方法會(huì)被調(diào)用牍鞠。-setNilValueForKey: 方法默認(rèn)實(shí)現(xiàn)為拋出一個(gè) NSInvalidArgumentException 異常咖摹,我們可以通過(guò)重寫(xiě)此方法將nil映射為有意義的值。
如果一個(gè)集合類型對(duì)象調(diào)用此方法皮服,則集合中每一個(gè)對(duì)象都會(huì)將 value楞艾、 key 作為參數(shù)調(diào)用設(shè)置器方法参咙。
NSArray * arr = [NSArray arrayWithObjects: obj1, obj2, nil];
[arr setValue:@"zh-cn" forKey:@"language"];
集合中每一個(gè)對(duì)象的 setValue:forKey: 方法都會(huì)被調(diào)用,等同于 :
NSArray * arr = [NSArray arrayWithObjects: obj1, obj2, nil];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj setValue:@"zh-cn" forKey:@"language"];
}];
訪問(wèn)器方法:- (nullable id)valueForKey:(NSString *)key;
此方法根據(jù)關(guān)聯(lián)標(biāo)識(shí)字符串 key 取值硫眯,方法內(nèi)部通過(guò)以下四個(gè)步驟進(jìn)行取值(僅討論iOS):
1.查找訪問(wèn)器方法
根據(jù)方法參數(shù)key的值依次匹配以下方法:
-get<Key>
-<key>
-is<Key>
-_<key>
如果找到對(duì)應(yīng)的訪問(wèn)器方法蕴侧,則調(diào)用此方法獲取返回值。此步驟依舊不關(guān)心類中是否擁有相應(yīng)的屬性或成員變量两入,僅僅匹配方法净宵。
2.查找集合訪問(wèn)方法
如果第一步中沒(méi)有找到相關(guān)訪問(wèn)器方法,則匹配查找集合訪問(wèn)器方法
-countOf<Key>
-objectIn<Key>AtIndex:
如果找到以上兩個(gè)方法則返回一個(gè) NSKeyValueArray 類型的集合代理對(duì)象裹纳。NSKeyValueArray類繼承自NSArray择葡,可以響應(yīng)NSArray所有消息,發(fā)送至集合代理對(duì)象的所有NSArray消息會(huì)被轉(zhuǎn)換為以上一個(gè)方法或兩個(gè)方法的組合發(fā)送至 -valueForKey: 方法的原始接收方剃氧。
關(guān)于 NSKeyValueArray 的更多內(nèi)容將在后面的部分講到敏储,這里我們只需要簡(jiǎn)單理解為 NSKeyValueArray 可以被當(dāng)做 NSArray 使用。
3.查找相應(yīng)的實(shí)例變量 (與設(shè)置器方法類似)
如果沒(méi)有找到相關(guān)訪問(wèn)器方法并且該類 accessInstanceVariablesDirectly 屬性返回YES朋鞍,那么將按照 _<key>已添、 _is<Key>、 <key>滥酥、 is<Key> 的順序查找相匹配的實(shí)例變量更舞,如果找到相應(yīng)的實(shí)例變量則對(duì)變量進(jìn)行賦值(注意:此過(guò)程直接為實(shí)例變量賦值,不調(diào)用getter)坎吻。
4.- (id)valueForUndefinedKey:(NSString *)key
當(dāng)經(jīng)過(guò)以上步驟沒(méi)有找到訪問(wèn)方法或?qū)嵗兞繒r(shí)缆蝉,- (id)valueForUndefinedKey:(NSString *)key 會(huì)被調(diào)用,此方法默認(rèn)實(shí)現(xiàn)為拋出 NSUndefinedKeyException 異常瘦真。
- (void)setValue:(nullable id)value forKey:(NSString *)key 方法取值過(guò)程中針對(duì)非OC對(duì)象的處理
- 與設(shè)置方法同理刊头,當(dāng)訪問(wèn)方法取得的值為非OC對(duì)象類型時(shí),如果結(jié)果的類型是NSNumber支持的數(shù)據(jù)類型之一诸尽,則將結(jié)果轉(zhuǎn)換為NSNumber對(duì)象返回芽偏,其他情況則將結(jié)果轉(zhuǎn)換為NSValue對(duì)象返回。
當(dāng)訪問(wèn)方法的接收對(duì)象為集合時(shí)弦讽,方法返回值為集合中每一個(gè)元素通過(guò)訪問(wèn)方法獲取的值的集合
NSArray * arr = [NSArray arrayWithObjects: obj1, obj2, nil];
NSArray * value = [arr valueForKey:@"language"];
集合中每一個(gè)對(duì)象的 valueForKey: 方法都會(huì)被調(diào)用,等同于 :
NSArray * arr = [NSArray arrayWithObjects: obj1, obj2, nil];
NSMutableArray * arrM = [NSMutableArray array];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[arrM addObject: [obj valueForKey:@"language"]];
}];
NSArray * value = arrM.copy;
NSKeyValueArray 集合代理對(duì)象
1. NSKeyValueArray 繼承自 NSArray 膀哲,NSKeyValueArray 集合代理對(duì)象中保存了 -valueForKey: 方法原始消息接收方(_container)以及方法參數(shù)( _key )往产。
當(dāng)集合代理對(duì)象接收到 NSArray 消息時(shí),消息會(huì)被轉(zhuǎn)換為 -countOf<Key> 和
-objectIn<Key>AtIndex: 的一個(gè)或多個(gè)消息組合發(fā)送至 -valueForKey: 方法原始接收方(_container 實(shí)例變量)某宪。
例如:
__kindof NSArray * value = [self valueForKey:@"list"];
id element = [value objectAtIndex:0];
此時(shí) -objectAtIndex: 會(huì)被轉(zhuǎn)換為 - objectInListAtIndex: 并發(fā)送至 - valueForKey: 方法的原始接收方self 仿村,相當(dāng)于:
id element = [self objectInListAtIndex:0];
2. 下標(biāo)越界問(wèn)題
通常我們?cè)讷@取數(shù)組中某個(gè)下標(biāo)元素時(shí)提供的下標(biāo)值超出了數(shù)組長(zhǎng)度會(huì)拋出異常導(dǎo)致程序崩潰。
對(duì)于 NSKeyValueArray " [ ] " 和 " - objectAtIndex: "消息會(huì)被轉(zhuǎn)換為 - objectIn<Key>AtIndex: 發(fā)送至原始接收方( _container )兴喂,無(wú)論下標(biāo)是否超出了 - countOfList: 方法返回的長(zhǎng)度都不會(huì)導(dǎo)致程序崩潰蔼囊。因此在 - objectIn<Key>AtIndex: 方法中我們應(yīng)該先對(duì) index 值進(jìn)行越界檢查焚志,避免由于下標(biāo)越界而出現(xiàn)一些匪夷所思的BUG。
3. NSKeyValueArray獲取集合元素
在上面的文章中我們提到 NSKeyValueArray 繼承自 NSArray 可以響應(yīng)所有的 NSArray 消息(消息會(huì)被轉(zhuǎn)換為集合訪問(wèn)器方法的一個(gè)或多個(gè)組合發(fā)送至 -valueForKey: 的原始接收方)畏鼓,因此每一次獲取 NSKeyValueArray 集合中的全部或某個(gè)元素時(shí)酱酬,原始接收方( _container )的 -objectIn<Key>AtIndex: 方法會(huì)都被調(diào)用,換言之 NSKeyValueArray 對(duì)象并不像 NSArray對(duì)象一樣會(huì)對(duì)集合中元素進(jìn)行強(qiáng)引用云矫, NSKeyValueArray 僅僅只是一個(gè)代理對(duì)象膳沽,所有元素均通過(guò) -objectIn<Key>AtIndex: 方法實(shí)時(shí)獲取,最終取得值由原始接收方?jīng)Q定让禀。
此處我們將通過(guò)一個(gè)簡(jiǎn)單的例子驗(yàn)證以上結(jié)論挑社,仔細(xì)思考一下,對(duì)于下面的例子element1和element2是同一個(gè)對(duì)象嗎巡揍?
- (void)test {
// 注意痛阻,此時(shí)我們的類中并沒(méi)有名為_(kāi)list的成員變量,也沒(méi)有訪問(wèn)器方法腮敌。
__kindof NSArray * value = [self valueForKey:@"list"];
// element1 和 element2 是同一個(gè)對(duì)象嗎阱当?
id element1 = value[0];
id element2 = value[0];
NSLog(@"\nelement1:%@\nelement2:%@", element1, element2);
}
- (NSUInteger)countOfList {
return 1;
}
- (id)objectInListAtIndex:(NSUInteger)index {
if (index < [self countOfList]) {
if (index == 0) {
return [NSObject new];
}
}
return nil;
}
對(duì)于上面的例子對(duì)value進(jìn)行的兩次取值相當(dāng)于調(diào)用了兩遍 _-objectInListAtIndex: 方法,而這個(gè)方法每一次調(diào)用都會(huì)創(chuàng)建一個(gè)新的NSObject對(duì)象返回缀皱,因此element1和element2并不是同一個(gè)對(duì)象斗这。
獲取可變集合器
獲取 key 對(duì)應(yīng)的可變集合訪問(wèn)器,訪問(wèn)器可以響應(yīng)對(duì)應(yīng)集合類型所有方法啤斗。該方法必定會(huì)返回一個(gè)對(duì)應(yīng)的可變集合訪問(wèn)器表箭,只是在使用時(shí)有不同的行為。
// 獲取可變數(shù)組集合訪問(wèn)器
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
// 獲取可變有續(xù)集和訪問(wèn)器
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key;
// 獲取可變不重復(fù)集合訪問(wèn)器
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
以 MutableArray 為例(MutableOrderSet钮莲、MutableSet 同理免钻,僅僅只是匹配的響應(yīng)方法不同,具體可以查看 api 對(duì)應(yīng)的注釋說(shuō)明)崔拥,獲取可變數(shù)組集合器經(jīng)過(guò)查找設(shè)置器和查找訪問(wèn)器兩個(gè)步驟步驟:
1.查找設(shè)置器
1.1 查找可變集合響應(yīng)方法
對(duì)于 MutableArray 會(huì)先查找以下方法:
// 插入元素
-insertObject:in<Key>AtIndex:
// 刪除元素
-removeObjectFrom<Key>AtIndex:
// 以下方法為可選极舔,系統(tǒng)會(huì)根據(jù)需要調(diào)用,不實(shí)現(xiàn)也不影響功能
// 批量插入链瓦,對(duì)應(yīng) -[NSMutableArray insertObjects:atIndexes:] 拆魏,
// 如果不實(shí)現(xiàn)系統(tǒng)會(huì)將批量操作拆分為單個(gè)操作,通過(guò)多次調(diào)用 -insertObject:in<Key>AtIndex: 實(shí)現(xiàn)慈俯,以下方法同理
-insert<Key>:atIndexes:
// 批量移除渤刃,對(duì)應(yīng) -[NSMutableArray removeObjectsAtIndexes:]
-remove<Key>AtIndexes:
// 替換, 如果不實(shí)現(xiàn)會(huì)將操作拆分為 插入/刪除 組合
-replaceObjectIn<Key>AtIndex:withObject:
// 批量替換
-replace<Key>AtIndexes:with<Key>:
1.2 查找 key 對(duì)應(yīng)的設(shè)置器
在此步驟中贴膘,會(huì)根據(jù)與 -setValue:forKey: 方法相同的方式去查找設(shè)置器卖子,并最終使用設(shè)置器。需要注意的是如果類中沒(méi)有對(duì)應(yīng)的設(shè)置器方法以及成員變量刑峡,最終會(huì)使用 -setValue:forKey: 作為設(shè)置器洋闽,如果此時(shí)使用可變集合訪問(wèn)器去修改集合時(shí)會(huì)執(zhí)行 -setValue:forUndefinedKey: 玄柠。
2. 查找訪問(wèn)器方法
在此步驟中,會(huì)根據(jù)與 -valueForKey: 方法相同的方式去查找訪問(wèn)器诫舅。需要注意的是如果類中沒(méi)有對(duì)應(yīng)的訪問(wèn)器方法以及成員變量羽利,最終會(huì)使用 -valueForKey: 作為訪問(wèn)器,如果此時(shí)使用可變集合訪問(wèn)器去獲取集合時(shí)會(huì)執(zhí)行 -valueForUndefinedKey: 骚勘。
KVO和KVC之間有什么聯(lián)系铐伴,使用KVC賦值可以觸發(fā)KVO回調(diào)嗎?
說(shuō)到KVC不得不提KVO俏讹,如果對(duì)于KVO還是不很熟悉的同學(xué)可以移步 KVO 鍵值觀察原理淺析 進(jìn)行了解当宴。
注意:如果你還不是很清楚KVO的原理建議先了解KVO原理后再閱讀此部分內(nèi)容
KVO方法需要一個(gè) keyPath 參數(shù),keyPath 參數(shù)雖然名為 keyPath 但是我們可以提供一個(gè) key (eg: name) 或者 keyPath (eg: dog.name)泽疆,而我們之前介紹的KVC方法也有對(duì)應(yīng)的 keyPath 存取方法户矢,基于此我們不禁好奇KVO和KVC究竟有什么關(guān)系呢?
通過(guò)對(duì)KVO原理的了解殉疼,我們知道KVO之所以能夠監(jiān)聽(tīng)某個(gè)屬性值改變梯浪,是由于其重寫(xiě)了原始類相關(guān)設(shè)置器方法,并在賦值前后分別調(diào)用 -willChangeValueForKey: 和 -didChangeValueForKey: 觸發(fā)KVO監(jiān)聽(tīng)回調(diào)瓢娜。
類中有相關(guān)設(shè)置器方法挂洛。
KVO在查找設(shè)置器方法時(shí)的邏輯是否與KVC查找設(shè)置器方法邏輯相同呢?我們不妨寫(xiě)demo來(lái)驗(yàn)證一下眠砾。
下面的例子 ClassA 中擁有一個(gè)名為 -setIsName: 的設(shè)置器方法虏劲,我們通過(guò) -setValue:forKey: 方法將 name 作為 key 賦值時(shí)會(huì)調(diào)用 -setIsName: 方法,如果我們同時(shí)將 name 作為KVO方法的 keyPath KVO回調(diào)方法會(huì)被執(zhí)行嗎褒颈?
@implementation ClassA {
NSString * _name;
}
- (instancetype)init {
if (self = [super init]) {
[self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
[self setValue:@"John" forKey:@"name"];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"my name is %@", _name);
}
}
// KVC賦值第一步操作可匹配的設(shè)置器方法
- (void)setIsName:(NSString *)name {
_name = name;
}
@end
上面的例子運(yùn)行起來(lái)柒巫,我們?cè)O(shè)置的KVO監(jiān)聽(tīng)被觸發(fā),控制臺(tái)有如下輸出:
現(xiàn)在我們不妨查看一下KVO生成的子類 NSKVONotifying_ClassA 的方法列表:
在其中我們發(fā)現(xiàn)了 -setIsName: 這個(gè)方法谷丸,而這個(gè)方法是 ClassA 類中為 _name 提供的供KVC賦值使用的設(shè)置器方法堡掏,如此我們可以確定,如果類中提供了相關(guān)設(shè)置器方法(-set<Key>:刨疼、- _set<Key>:泉唁、- setIs<Key>:),那么當(dāng)我們?cè)O(shè)置KVO監(jiān)聽(tīng)后設(shè)置器方法會(huì)被重寫(xiě)揩慕,設(shè)置器方法被調(diào)用時(shí)KVO會(huì)被觸發(fā)游两。
類中沒(méi)有相關(guān)設(shè)置器方法。
我們將上面的例子中 ClassA 類的設(shè)置器方法代碼刪除運(yùn)行漩绵,運(yùn)行發(fā)現(xiàn)即使沒(méi)有相關(guān)設(shè)置器方法,KVO依然會(huì)被觸發(fā)肛炮,這又是為什么呢止吐?
我們知道KVO回調(diào)是通過(guò) -willChangeValueForKey: 和 -didChangeValueForKey: 這兩個(gè)方法中觸發(fā)的宝踪,那么我們可以重寫(xiě) -willChangeValueForKey: 方法設(shè)置斷點(diǎn)來(lái)觀察一下從調(diào)用KVC方法到第一次觸發(fā)KVO回調(diào)中間的方法調(diào)用堆棧。
雖然我們無(wú)法直接查看 _NSSetValueAndNotifyForKeyInIvar 函數(shù)實(shí)現(xiàn)碍扔,但是通過(guò)字面意思我們能夠大致了解瘩燥,這一步直接為相關(guān)成員變量賦值并且通知回調(diào),所以這就是為什么類中沒(méi)有相關(guān)設(shè)置器方法的情況下使用KVC賦值依舊能夠觸發(fā)KVO回調(diào)的原因不同。
類中沒(méi)有相關(guān)設(shè)置器方法以及成員變量
將上例中 ClassA 類的設(shè)置器方法以及成員變量代碼刪除運(yùn)行調(diào)用堆棧如下:
在運(yùn)行上例時(shí)厉膀,我們會(huì)得到 valueForUndefinedKey: 方法拋出的異常,由此我們可以大概確定KVO內(nèi)部利用KVC來(lái)獲取舊值二拐。需要注意的是服鹅,即使我們?yōu)轭愄砑恿伺c key 同名的方法,雖然 KVO 得以正嘲傩拢回調(diào)企软,但是最后程序也會(huì)因?yàn)閳?zhí)行 -setValue:forUndefinedKey: 崩潰。
實(shí)際上通過(guò)對(duì)KVC賦值邏輯三個(gè)步驟以及在每一種情況下的調(diào)用堆棧觀察饭望,我們可以得出以下結(jié)論:
查找到相關(guān)設(shè)置器方法:
setValue:forKey: -> _NSSetObjectValueAndNotify -> willChangeValueForKey:查找到相關(guān)成員變量:
setValue:forKey: -> _NSSetValueAndNotifyForKeyInIvar -> willChangeValueForKey:沒(méi)有查找到相關(guān)設(shè)置器方法以及成員變量:
setValue:forKey: -> _NSSetValueAndNotifyForUndefinedKey -> willChangeValueForKey:
也就是說(shuō)KVC設(shè)置器方法實(shí)際上會(huì)根據(jù)每一種情況提供對(duì)KVO的處理仗哨,所以我們?cè)谠O(shè)置了KVO監(jiān)聽(tīng)之后使用KVC賦值是可以觸發(fā)KVO回調(diào)的。
那么KVC在什么情況下會(huì)去處理KVO監(jiān)聽(tīng)呢铅辞? 不知大家是否還記得我們?cè)谥安榭碖VO監(jiān)聽(tīng)后生成的子類方法列表時(shí)厌漂,其中有一個(gè)特殊的方法 _isKVOA,當(dāng)時(shí)我們并沒(méi)有提到它斟珊,那么現(xiàn)在它的作用不言自明苇倡。
KVO 監(jiān)聽(tīng)與可變集合訪問(wèn)器
上文中我們說(shuō)到了獲取可變集合訪問(wèn)器方法,那么如果對(duì)一個(gè) key 設(shè)置了 KVO 監(jiān)聽(tīng)倍宾,我們?cè)谑褂每勺兗显L問(wèn)器去修改集合的時(shí)候雏节,KVO 回調(diào)會(huì)被觸發(fā)嗎?
答案是取決于可變集合訪問(wèn)器獲取的時(shí)機(jī)
如下的例子
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"arr"]) {
NSLog(@"arr改變了 %@", change);
}
}
// 添加 KVO 前獲取可變集合訪問(wèn)器
NSMutableArray * mutableArr1 = [t mutableArrayValueForKey:@"arr"];
// 添加 KVO 監(jiān)聽(tīng)
[t addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew context:nil];
// 添加 KVO 之后獲取可變集合訪問(wèn)器
NSMutableArray * mutableArr2 = [t mutableArrayValueForKey:@"arr"];
// 初始化 t 的 arr 值高职,觸發(fā) KVO 回調(diào)
[t setValue:[NSMutableArray array] forKey:@"arr"];
// 通過(guò)添加 KVO 前獲取的可變集合訪問(wèn)器添加元素钩乍,不會(huì)觸發(fā) KVO 回調(diào)
[mutableArr1 addObject:@"1"];
// 通過(guò)添加 KVO 后獲取的可變集合訪問(wèn)器添加元素,觸發(fā) KVO 回調(diào)
[mutableArr2 addObject:@"2"];
輸出結(jié)果如下:
2023-12-09 11:17:34.752834+0800 Demo1[34580:3276019] arr改變了 {
kind = 1;
new = (
);
}
2023-12-09 11:17:54.928467+0800 Demo1[34580:3276019] arr改變了 {
indexes = "<_NSCachedIndexSet: 0x280a343c0>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
kind = 2;
new = (
2
);
}
根據(jù)結(jié)果可知怔锌,在添加 KVO 前獲取的可變集合訪問(wèn)器修改元素不會(huì)觸發(fā) KVO寥粹,而之后獲取的可以觸發(fā) KVO 且 KVO 回調(diào)中 change 包含改變的元素和改變的元素下標(biāo)。