iOS Objective-C KVO 常見(jiàn)用法
前言
KVO
即Key-Value Observing
是蘋(píng)果提供給開(kāi)發(fā)者的一套鍵值觀察的API怔毛,在我們?nèi)粘i_(kāi)發(fā)中經(jīng)常用到KVO
進(jìn)行屬性的觀察,接下來(lái)我們將通過(guò)該篇文章對(duì)KVO
的常見(jiàn)用法進(jìn)行總結(jié)。
1. 觀察屬性的變化
1.1 基本的屬性觀察代碼
我們最簡(jiǎn)單的使用KVO就是觀察屬性的變化,在本例中,我們觀察person
對(duì)象的name
的改變。
添加觀察代碼:
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
監(jiān)聽(tīng)觀察代碼:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@ - %@",self, change);
}
移除代碼:
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"name"];
}
1.2 一些問(wèn)題
Question?
在上面我們是觀察person
對(duì)象name
屬性的變化,如果我們同樣有一個(gè)新的student
對(duì)象也有個(gè)name
屬性弃秆,那么我們?cè)诒O(jiān)聽(tīng)代碼處該如何判斷是那個(gè)對(duì)象的name
呢?
Answer:
- 首先我們可以通過(guò)對(duì)
object
的判斷是否是要觀察的對(duì)象髓帽,但是這樣寫(xiě)代碼就顯得很low菠赚,并且需要觀察的對(duì)象如果太多了,代碼看起來(lái)很復(fù)雜 - 然后我們還可以用
context
郑藏,它的定義是(nullable void *)
類(lèi)型衡查,平常我們基本都不用這個(gè)屬性,只是傳個(gè)nil
必盖,其實(shí)按照官方的說(shuō)法則應(yīng)該傳NULL
拌牲,其實(shí)我們可以通過(guò)context
字段來(lái)區(qū)分不同的對(duì)象屬性監(jiān)聽(tīng)氯檐。例如:
static void *PersonNameContext = &PersonNameContext;
static void *StudentNameContext = &StudentNameContext;
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:PersonNameContext];
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:StudentNameContext];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if (context == PersonNameContext) {
NSLog(@"person name change %@ - %@",self, change);
} else if (context == StudentNameContext) {
NSLog(@"student name change %@ - %@",self, change);
}
}
2. 嵌套觀察
其實(shí)在我們的開(kāi)發(fā)中經(jīng)常會(huì)有顯示下載進(jìn)度的需求宿饱,下載進(jìn)度由 totalCount
和completedCount
兩個(gè)值組成,如果我們觀察這兩個(gè)值就需要在兩個(gè)地方處理進(jìn)度的改變芯急,這時(shí)候我們希望通過(guò)觀察一個(gè)downloadProgress
就能實(shí)現(xiàn)獲取下載進(jìn)度的需求失驶。
2.1 實(shí)現(xiàn)代碼
相關(guān)屬性:
// 相關(guān)屬性
@property (nonatomic, copy) NSString *downloadProgress;
@property (nonatomic, assign) double completedCount;
@property (nonatomic, assign) double totalCount;
downloadProgress Getter:
- (NSString *)downloadProgress{
if (self.completedCount <= 0) {
self.completedCount = 0;
}
if (self.totalCount <= 0) {
return @"0";
}
return [[NSString alloc] initWithFormat:@"%f",1.0f*self.completedCount/self.totalCount];
}
keyPathsForValuesAffectingValueForKey:
通過(guò)keyPathsForValuesAffectingValueForKey
方法實(shí)現(xiàn)多鍵值的觀察土居。
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"downloadProgress"]) {
NSArray *affectingKeys = @[@"totalCount", @"completedCount"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
添加觀察代碼:
[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];
監(jiān)聽(tīng)觀察代碼:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@ - %@",self, change);
}
當(dāng)totalCount
和completedCount
任一值改變后都可以觸發(fā)downloadProgress
的改變,在這里最重要的就是通過(guò)+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
方法實(shí)現(xiàn)一組鍵值的觀察嬉探。
3. 觀察可變集合
這里我們?cè)?code>KVC那篇文章中有提到過(guò)擦耀,對(duì)于可變數(shù)組的觀察需要使用mutableArrayValueForKey
等相關(guān)方法。其實(shí)主要原因是KVO
觀察屬性是基于Setter
方法甲馋,但是直接調(diào)用可變數(shù)組的addObject
方法是不走Setter
的埂奈,所以此時(shí)無(wú)法實(shí)現(xiàn)可變數(shù)組的觀察迄损,其他可變集合同理定躏。
添加觀察代碼:
[self.person addObserver:self forKeyPath:@"dataArray" options:(NSKeyValueObservingOptionNew) context:NULL];
監(jiān)聽(tīng)和移除代碼跟上面的類(lèi)似。這里主要是改變的代碼會(huì)有不同。
改變數(shù)組的代碼:
// 數(shù)組變化(不會(huì)觸發(fā)改變)
[self.person.dataArray addObject:@"1"];
// 數(shù)組變化(會(huì)觸發(fā)改變)
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"2"];
打印結(jié)果:
通過(guò)上圖結(jié)果我們可以知道痊远,添加@"1"
的時(shí)候并不會(huì)觸發(fā)KVO
垮抗,在添加@"2"
的時(shí)候觸發(fā)了KVO
的監(jiān)聽(tīng)改變,所以對(duì)于可變集合的KVO
的監(jiān)聽(tīng)需要用對(duì)應(yīng)的mutableXXXValueForKey
方法進(jìn)行改變碧聪。
4. 自動(dòng)觀察與手動(dòng)觀察
默認(rèn)情況下冒版,我們只需要按照上面的代碼就可以實(shí)現(xiàn)屬性的觀察,其實(shí)這是由系統(tǒng)完全控制的逞姿,屬于自動(dòng)觀察辞嗡。其實(shí)KVO
還給我們提供了手動(dòng)觀察的選項(xiàng)。
如果我們想要開(kāi)啟手動(dòng)觀察就要通過(guò)重寫(xiě)類(lèi)方法+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key
滞造,如果返回YES
就是自動(dòng)觀察续室,返回NO
就是手動(dòng)觀察,根據(jù)方法的我們還可以判斷key
值對(duì)不同的key
分別實(shí)現(xiàn)自動(dòng)觀察和手動(dòng)觀察谒养。
// 自動(dòng)開(kāi)關(guān)
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
if ([key isEqualToString:@"name"]) {
return NO;
} else {
return YES;
}
}
對(duì)于需要手動(dòng)觀察的key
在改變前需要調(diào)用willChangeValueForKey
方法挺狰,在改變后需要調(diào)用didChangeValueForKey
方法,如果不調(diào)用买窟,就不會(huì)觸發(fā)KVO
的監(jiān)聽(tīng)丰泊。
示例代碼:
[self.person willChangeValueForKey:@"name"];
self.person.name = @"null";
[self.person didChangeValueForKey:@"name"];
5. KVO是否需要移除觀察
在實(shí)驗(yàn)中,對(duì)于普通的對(duì)象我們不進(jìn)行移除并沒(méi)有出現(xiàn)問(wèn)題始绍,但是對(duì)于單例對(duì)象就會(huì)有問(wèn)題瞳购。這里就不一一驗(yàn)證了,總是就是一句話
一定要移除觀察亏推!
一定要移除觀察苛败!
一定要移除觀察!