細(xì)數(shù)KVO的弊端:
- 所有實(shí)現(xiàn)都在同一個(gè)方法里調(diào)用
- (void)addObserver: (NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
比如說(shuō)我需要觀察tablview的contentsize屬性,我這樣來(lái)寫:
[_tableView addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[self configureView];
}
完成了?看起來(lái)很簡(jiǎn)單啊,so young,為了寫這個(gè),我們還需要做些額外的工作 orz
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == _tableView && [keyPath isEqualToString:@"contentSize"]) {
[self configureView];
}
}
- KVO is string-ly typed
keypath ‘contentSiz’ 是字符串類型的,這就意味著編譯器跟解析器不會(huì)告訴你這個(gè)屬性是什么類型的或者存不存在。它只是一個(gè)字符串。我們只能使用NSStringFromSelector(@selector(contentSize))來(lái)讓編譯器告訴我們這個(gè)存不存在。
另外,當(dāng)我們觀察一個(gè)view controller時(shí),想要獲取它scrollview的contentsize,這時(shí)候keypath為scrollview.contentOffset。這種情況下我們能做的事就更少了。 - KVO 必須處理父類實(shí)現(xiàn)
我們也許還有一個(gè)父類也在監(jiān)聽,并實(shí)現(xiàn)了這個(gè)接口方法,那我們就該調(diào)用super方法
if (object == _tableView && [keyPath isEqualToString:@"contentSize"]) {
[self configureView];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
如果我們一不小心忘了,那就可能導(dǎo)致父類監(jiān)聽失效。
所以保險(xiǎn)起見,我們最好不嫌麻煩地調(diào)用super方法 orz。
- KVO 在解除注冊(cè)的時(shí)候可能導(dǎo)致crash
我們一般會(huì)在-dealloc中[_tableView removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) context:NULL];
但值得注意的是,tableview有可能會(huì)dealloc兩次,這種情況下因?yàn)槲覀儑L試remove同一個(gè)observance兩次,就可能導(dǎo)致我們的app crash掉。另外,當(dāng)父類也在監(jiān)聽同一屬性的時(shí)候,也可能會(huì)調(diào)用兩次導(dǎo)致crash。
這個(gè)時(shí)候是該用到 context ,我們可能會(huì)context 當(dāng)做 self來(lái)用,但在父類里,就不好用了。 (因?yàn)閟elf不管在父類還是子類中都會(huì)指向同一個(gè)對(duì)象)所以推薦使用靜態(tài)指針來(lái)存儲(chǔ)context。
static void *ClassNameTableViewContentSizeContext = &ClassNameTableViewContentSizeContext;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == ClassNameTableViewContentSizeContext) {
[self doThing];
} else if (context == OtherContext) {
[self doOtherThing];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- KVO太難調(diào)試
delegate調(diào)試時(shí),很容易就追蹤到設(shè)置它的對(duì)象了,但KVO則可能需要在運(yùn)行時(shí)使用isKindOfClass:來(lái)追蹤。
這個(gè)就不用多舉例子了,用過的人都有體會(huì)。