在iOS開發(fā)中,蘋果提供了許多機(jī)制給我們進(jìn)行回調(diào)。KVO(key-value-observing)是一種十分有趣的回調(diào)機(jī)制,在某個(gè)對象注冊監(jiān)聽者后,在被監(jiān)聽的對象發(fā)生改變時(shí),對象會發(fā)送一個(gè)通知給監(jiān)聽者,以便監(jiān)聽者執(zhí)行回調(diào)操作。最常見的KVO運(yùn)用是監(jiān)聽scrollView的contentOffset屬性,來完成用戶滾動(dòng)時(shí)動(dòng)態(tài)改變某些控件的屬性實(shí)現(xiàn)效果,包括漸變導(dǎo)航欄、下拉刷新控件等效果。
http://jbcdn2.b0.upaiyun.com/2015/12/36a1f0f0ac4af124dbdb65895b2d0fc9.gif
使用
KVO的使用非常簡單,使用KVO的要求是對象必須能支持kvc機(jī)制——所有NSObject的子類都支持這個(gè)機(jī)制。拿上面的漸變導(dǎo)航欄做,我們?yōu)閠ableView添加了一個(gè)監(jiān)聽者controller,在我們滑動(dòng)列表的時(shí)候,會計(jì)算當(dāng)前列表的滾動(dòng)偏移量,然后改變導(dǎo)航欄的背景色透明度。
<pre>//添加監(jiān)聽者
[self.tableView addObserver: self forKeyPath: @"contentOffset" options: NSKeyValueObservingOptionNew context: nil];
/**
- 監(jiān)聽屬性值發(fā)生改變時(shí)回調(diào)
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
CGFloat offset = self.tableView.contentOffset.y;
CGFloat delta = offset / 64.f + 1.f;
delta = MAX(0, delta);
[self alphaNavController].barAlpha = MIN(1, delta);
}
</pre>
毫無疑問,kvo是一種非常便捷的回調(diào)方式,但是編譯器是怎么完成監(jiān)聽這個(gè)任務(wù)的呢?先來看看蘋果文檔對于KVO的實(shí)現(xiàn)描述
Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ..
簡要的來說,在我們對某個(gè)對象完成監(jiān)聽的注冊后,編譯器會修改監(jiān)聽對象(上文中的tableView)的isa指針,讓這個(gè)指針指向一個(gè)新生成的中間類。從某個(gè)意義上來說,這是一場騙局。
<pre>typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id</pre>
KVO使用三步走:
(1)注冊成為觀察者
(2)觀察者定義KVO的回調(diào)
(3)移除觀察者
來點(diǎn)實(shí)際的,還得上代碼:
KVOClass接口:
<pre>
import
@interface KVOClass : NSObject
@property(strong,nonatomic) NSString * name;
@end</pre>
KVOClass實(shí)現(xiàn)
<pre>#import "KVOClass.h"
@implementation KVOClass
//在init注冊觀察者
-(id) init
{
if (self = [super init]) {
[self addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:@"name"];
}
return self;
}
//重寫觀察方
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == @"name") {
NSLog(@"name被改變啦!");
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
//移除觀察者
-(void) dealloc
{
[self removeObserver:self forKeyPath:@"name"];
[super dealloc];
}
@end</pre>
待續(xù)。。。。。。。。。。。。。。