[深入淺出Cocoa]詳解鍵值觀察(KVO)及其實現(xiàn)機理

一吵血,前言

Objective-C 中的鍵(key)-值(value)觀察(KVO)并不是什么新鮮事物谤绳,它來源于設計模式中的觀察者模式鞍帝,其基本思想就是:

一個目標對象管理所有依賴于它的觀察者對象令蛉,并在它自身的狀態(tài)改變時主動通知觀察者對象荡陷。這個主動通知通常是通過調用各觀察者對象所提供的接口方法來實現(xiàn)的狸膏。觀察者模式較完美地將目標對象與觀察者對象解耦沟饥。

在 Objective-C 中有兩種使用鍵值觀察的方式:手動或自動,此外還支持注冊依賴鍵(即一個鍵依賴于其他鍵湾戳,其他鍵的變化也會作用到該鍵)贤旷。下面將一一講述這些,并會深入 Objective-C 內部一窺鍵值觀察是如何實現(xiàn)的砾脑。

本文源碼下載:點此下載

二幼驶,運用鍵值觀察

1,注冊與解除注冊

如果我們已經(jīng)有了包含可供鍵值觀察屬性的類韧衣,那么就可以通過在該類的對象(被觀察對象)上調用名為?NSKeyValueObserverRegistration 的category?方法將觀察者對象與被觀察者對象注冊與解除注冊:

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context;- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

這兩個方法的定義在 Foundation/NSKeyValueObserving.h 中盅藻,NSObject,NSArray畅铭,NSSet均實現(xiàn)了以上方法氏淑,因此我們不僅可以觀察普通對象,還可以觀察數(shù)組或集合類對象硕噩。在該頭文件中假残,我們還 可以看到 NSObject 還實現(xiàn)了?NSKeyValueObserverNotification的?category 方法(更多類似方法,請查看該頭文件):

- (void)willChangeValueForKey:(NSString *)key;- (void)didChangeValueForKey:(NSString *)key;

這兩個方法在手動實現(xiàn)鍵值觀察時會用到炉擅,暫且不提辉懒。

值得注意的是:不要忘記解除注冊阳惹,否則會導致資源泄露。

2眶俩,設置屬性

將觀察者與被觀察者注冊好之后莹汤,就可以對觀察者對象的屬性進行操作,這些變更操作就會被通知給觀察者對象仿便。注意体啰,只有遵循 KVO 方式來設置屬性,觀察者對象才會獲取通知嗽仪,也就是說遵循使用屬性的 setter 方法荒勇,或通過 key-path 來設置:

[target setAge:30];[target setValue:[NSNumber numberWithInt:30] forKey:@"age"];

3,處理變更通知

觀察者需要實現(xiàn)名為?NSKeyValueObserving 的?category 方法來處理收到的變更通知:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)objectchange:(NSDictionary *)change context:(void*)context;

在這里闻坚,change 這個字典保存了變更信息沽翔,具體是哪些信息取決于注冊時的?NSKeyValueObservingOptions。

4窿凤,下面來看看一個完整的使用示例:

觀察者類:

//Observer.h@interfaceObserver : NSObject@end//Observer.m#import"Observer.h"#import#import"Target.h"@implementationObserver- (void) observeValueForKeyPath:(NSString *)keyPath? ? ? ? ? ? ? ? ? ? ? ofObject:(id)objectchange:(NSDictionary *)change? ? ? ? ? ? ? ? ? ? ? ? context:(void*)context{if([keyPath isEqualToString:@"age"])? ? {? ? ? ? Class classInfo = (Class)context;? ? ? ? NSString * className = [NSString stringWithCString:object_getClassName(classInfo)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? encoding:NSUTF8StringEncoding];? ? ? ? NSLog(@">> class: %@, Age changed", className);? ? ? ? NSLog(@"old age is %@", [change objectForKey:@"old"]);? ? ? ? NSLog(@"new age is %@", [change objectForKey:@"new"]);? ? }else{? ? ? ? [super observeValueForKeyPath:keyPath? ? ? ? ? ? ? ? ? ? ? ? ? ? ofObject:objectchange:change? ? ? ? ? ? ? ? ? ? ? ? ? ? ? context:context];? ? }}@end

注意:在實現(xiàn)處理變更通知方法?observeValueForKeyPath 時仅偎,要將不能處理的 key 轉發(fā)給 super 的?observeValueForKeyPath 來處理。

使用示例:

Observer * observer = [[[Observer alloc] init] autorelease];Target * target = [[[Target alloc] init] autorelease];[target addObserver:observer? ? ? ? forKeyPath:@"age"options:NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOldcontext:[Targetclass]];[target setAge:30];//[target setValue:[NSNumber numberWithInt:30] forKey:@"age"];[target removeObserver:observer forKeyPath:@"age"];

在這里 observer 觀察 target 的 age 屬性變化雳殊,運行結果如下:

>> class: Target, Age changed

old age is 10

new age is 30

三橘沥,手動實現(xiàn)鍵值觀察

上面的 Target 應該怎么實現(xiàn)呢?首先來看手動實現(xiàn)夯秃。

@interfaceTarget : NSObject{intage;}//for manual KVO - age- (int) age;- (void) setAge:(int)theAge;@end@implementationTarget- (id) init{? ? self = [super init];if(nil != self)? ? {? ? ? ? age =10;? ? }returnself;}//for manual KVO - age- (int) age{returnage;}- (void) setAge:(int)theAge{[self willChangeValueForKey:@"age"];age = theAge;[self didChangeValueForKey:@"age"];}+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {if([key isEqualToString:@"age"]) {returnNO;? ? }return[super automaticallyNotifiesObserversForKey:key];}@end

首先座咆,需要手動實現(xiàn)屬性的 setter 方法,并在設置操作的前后分別調用willChangeValueForKey:didChangeValueForKey方法仓洼,這兩個方法用于通知系統(tǒng)該 key 的屬性值即將和已經(jīng)變更了介陶;

其次,要實現(xiàn)類方法automaticallyNotifiesObserversForKey色建,并在其中設置對該 key 不自動發(fā)送通知(返回 NO 即可)哺呜。這里要注意,對其它非手動實現(xiàn)的 key箕戳,要轉交給 super 來處理某残。

四,自動實現(xiàn)鍵值觀察

自動實現(xiàn)鍵值觀察就非常簡單了漂羊,只要使用了自動屬性即可驾锰。

@interfaceTarget : NSObject//for automatic KVO - age@property (nonatomic, readwrite)intage;@end@implementationTarget@synthesizeage;//for automatic KVO - age- (id) init{? ? self = [super init];if(nil != self)? ? {? ? ? ? age =10;? ? }returnself;}@end

五,鍵值觀察依賴鍵

有時候一個屬性的值依賴于另一對象中的一個或多個屬性走越,如果這些屬性中任一屬性的值發(fā)生變更椭豫,被依賴的屬性值也應當為其變更進行標記。因此,object 引入了依賴鍵赏酥。

1喳整,觀察依賴鍵

觀察依賴鍵的方式與前面描述的一樣,下面先在 Observer 的?observeValueForKeyPath:ofObject:change:context: 中添加處理變更通知的代碼:

#import"TargetWrapper.h"- (void) observeValueForKeyPath:(NSString *)keyPath? ? ? ? ? ? ? ? ? ? ? ofObject:(id)objectchange:(NSDictionary *)change? ? ? ? ? ? ? ? ? ? ? ? context:(void*)context{if([keyPath isEqualToString:@"age"])? ? {? ? ? ? Class classInfo = (Class)context;? ? ? ? NSString * className = [NSString stringWithCString:object_getClassName(classInfo)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? encoding:NSUTF8StringEncoding];? ? ? ? NSLog(@">> class: %@, Age changed", className);? ? ? ? NSLog(@"old age is %@", [change objectForKey:@"old"]);? ? ? ? NSLog(@"new age is %@", [change objectForKey:@"new"]);? ? }elseif([keyPath isEqualToString:@"information"])? ? {? ? ? ? Class classInfo = (Class)context;? ? ? ? NSString * className = [NSString stringWithCString:object_getClassName(classInfo)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? encoding:NSUTF8StringEncoding];? ? ? ? NSLog(@">> class: %@, Information changed", className);? ? ? ? NSLog(@"old information is %@", [change objectForKey:@"old"]);? ? ? ? NSLog(@"new information is %@", [change objectForKey:@"new"]);? ? }else{? ? ? ? [super observeValueForKeyPath:keyPath? ? ? ? ? ? ? ? ? ? ? ? ? ? ofObject:objectchange:change? ? ? ? ? ? ? ? ? ? ? ? ? ? ? context:context];? ? }}

2裸扶,實現(xiàn)依賴鍵

在這里框都,觀察的是?TargetWrapper 類的 information 屬性,該屬性是依賴于 Target 類的 age 和 grade 屬性呵晨。為此魏保,我在 Target 中添加了 grade 屬性:

@interfaceTarget : NSObject@property (nonatomic, readwrite)intgrade;@property (nonatomic, readwrite)intage;@end@implementationTarget@synthesizeage;//for automatic KVO - age@synthesizegrade;@end

下面來看看 TragetWrapper 中的依賴鍵屬性是如何實現(xiàn)的。

@classTarget;@interfaceTargetWrapper : NSObject{@privateTarget * _target;}@property(nonatomic, assign) NSString *information;@property(nonatomic, retain) Target * target;-(id) init:(Target *)aTarget;@end#import"TargetWrapper.h"#import"Target.h"@implementationTargetWrapper@synthesizetarget = _target;-(id) init:(Target *)aTarget{? ? self = [super init];if(nil != self) {? ? ? ? _target = [aTarget retain];? ? }returnself;}-(void) dealloc{? ? self.target = nil;? ? [super dealloc];}- (NSString *)information{return[[[NSString alloc] initWithFormat:@"%d#%d", [_target grade], [_target age]] autorelease];}- (void)setInformation:(NSString *)theInformation{? ? NSArray * array = [theInformation componentsSeparatedByString:@"#"];? ? [_target setGrade:[[array objectAtIndex:0] intValue]];? ? [_target setAge:[[array objectAtIndex:1] intValue]];}+ (NSSet *)keyPathsForValuesAffectingInformation{? ? NSSet * keyPaths = [NSSet setWithObjects:@"target.age",@"target.grade", nil];returnkeyPaths;}//+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key//{//NSSet * keyPaths = [super keyPathsForValuesAffectingValueForKey:key];//NSArray * moreKeyPaths = nil;////if ([key isEqualToString:@"information"])//{//moreKeyPaths = [NSArray arrayWithObjects:@"target.age", @"target.grade", nil];//}////if (moreKeyPaths)//{//keyPaths = [keyPaths setByAddingObjectsFromArray:moreKeyPaths];//}////return keyPaths;//}@end

首先摸屠,要手動實現(xiàn)屬性 information 的 setter/getter 方法谓罗,在其中使用 Target 的屬性來完成其 setter 和 getter。

其次季二,要實現(xiàn)keyPathsForValuesAffectingInformationkeyPathsForValuesAffectingValueForKey: 方法來告訴系統(tǒng) information 屬性依賴于哪些其他屬性檩咱,這兩個方法都返回一個key-path 的集合。在這里要多說幾句胯舷,如果選擇實現(xiàn)keyPathsForValuesAffectingValueForKey刻蚯,要先獲取 super 返回的結果 set,然后判斷 key 是不是目標 key桑嘶,如果是就將依賴屬性的 key-path 結合追加到 super 返回的結果 set 中炊汹,否則直接返回 super的結果。

在這里逃顶,information 屬性依賴于 target 的 age 和 grade 屬性兵扬,target 的 age/grade 屬性任一發(fā)生變化,information 的觀察者都會得到通知口蝠。

3,使用示例:

Observer * observer = [[[Observer alloc] init] autorelease];Target * target = [[[Target alloc] init] autorelease];TargetWrapper * wrapper = [[[TargetWrapper alloc] init:target] autorelease];[wrapper addObserver:observer? ? ? ? ? forKeyPath:@"information"options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld? ? ? ? ? ? context:[TargetWrapperclass]];[target setAge:30];[target setGrade:1];[wrapper removeObserver:observer forKeyPath:@"information"];

輸出結果:

>> class: TargetWrapper, Information changed

old information is 0#10

new information is 0#30

>> class: TargetWrapper, Information changed

old information is 0#30

new information is 1#30

六津坑,鍵值觀察是如何實現(xiàn)的

1妙蔗,實現(xiàn)機理

鍵值觀察用處很多,Core Binding 背后的實現(xiàn)就有它的身影疆瑰,那鍵值觀察背后的實現(xiàn)又如何呢眉反?想一想在上面的自動實現(xiàn)方式中,我們并不需要在被觀察對象 Target 中添加額外的代碼穆役,就能獲得鍵值觀察的功能寸五,這很好很強大,這是怎么做到的呢耿币?答案就是 Objective C 強大的 runtime 動態(tài)能力梳杏,下面我們一起來窺探下其內部實現(xiàn)過程。

當某個類的對象第一次被觀察時,系統(tǒng)就會在運行期動態(tài)地創(chuàng)建該類的一個派生類十性,在這個派生類中重寫基類中任何被觀察屬性的 setter 方法叛溢。

派生類在被重寫的 setter 方法實現(xiàn)真正的通知機制,就如前面手動實現(xiàn)鍵值觀察那樣劲适。這么做是基于設置屬性會調用 setter 方法楷掉,而通過重寫就獲得了 KVO 需要的通知機制。當然前提是要通過遵循 KVO 的屬性設置方式來變更屬性值霞势,如果僅是直接修改屬性對應的成員變量烹植,是無法實現(xiàn) KVO 的。

同時派生類還重寫了 class 方法以“欺騙”外部調用者它就是起初的那個類愕贡。然后系統(tǒng)將這個對象的 isa 指針指向這個新誕生的派生類草雕,因此這個對象就成為該派生類的對象了,因而在該對象上對 setter 的調用就會調用重寫的 setter颂鸿,從而激活鍵值通知機制促绵。此外,派生類還重寫了 dealloc 方法來釋放資源嘴纺。

如果你對類和對象的關系不太明白败晴,請閱讀《深入淺出Cocoa之類與對象》;如果你對如何動態(tài)創(chuàng)建類不太明白栽渴,請閱讀《深入淺出Cocoa 之動態(tài)創(chuàng)建類》尖坤。

蘋果官方文檔說得很簡潔:

Key-Value Observing Implementation Details

Automatic key-value observing is implemented using a technique calledisa-swizzling.

Theisapointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.

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. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

You should never rely on theisapointer to determine class membership. Instead, you should use theclassmethod to determine the class of an object instance.

2,代碼分析

由于派生類中被重寫的 class 對我們撒謊(它說它就是起初的基類)闲擦,我們只有通過調用 runtime 函數(shù)才能揭開派生類的真面目慢味。 下面來看Mike Ash的代碼:

首先是帶有 x, y, z 三個屬性的觀察目標 Foo:

@interfaceFoo : NSObject{intx;inty;intz;}@propertyintx;@propertyinty;@propertyintz;@end@implementationFoo@synthesizex, y, z;@end

下面是檢驗代碼:

#importstaticNSArray *ClassMethodNames(Class c){? ? NSMutableArray * array = [NSMutableArray array];? ? ? ? unsignedintmethodCount =0;? ? Method * methodList = class_copyMethodList(c, &methodCount);? ? unsignedinti;for(i =0; i < methodCount; i++) {? ? ? ? [array addObject: NSStringFromSelector(method_getName(methodList[i]))];? ? }? ? free(methodList);returnarray;}staticvoidPrintDescription(NSString * name,idobj){? ? NSString * str = [NSString stringWithFormat:@"\n\t%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",? ? ? ? ? ? ? ? ? ? ? name,? ? ? ? ? ? ? ? ? ? ? obj,? ? ? ? ? ? ? ? ? ? ? class_getName([objclass]),? ? ? ? ? ? ? ? ? ? ? class_getName(obj->isa),? ? ? ? ? ? ? ? ? ? ? [ClassMethodNames(obj->isa) componentsJoinedByString:@","]];? ? NSLog(@"%@", str);}intmain (intargc,constchar* argv[]){? ? @autoreleasepool {//Deep into KVO:kesalin@gmail.com//Foo * anything = [[Foo alloc] init];? ? ? ? Foo * x = [[Foo alloc] init];? ? ? ? Foo * y = [[Foo alloc] init];? ? ? ? Foo * xy = [[Foo alloc] init];? ? ? ? Foo * control = [[Foo alloc] init];? ? ? ? ? ? ? ? [x addObserver:anything forKeyPath:@"x"options:0context:NULL];? ? ? ? [y addObserver:anything forKeyPath:@"y"options:0context:NULL];? ? ? ? ? ? ? ? [xy addObserver:anything forKeyPath:@"x"options:0context:NULL];? ? ? ? [xy addObserver:anything forKeyPath:@"y"options:0context:NULL];? ? ? ? ? ? ? ? PrintDescription(@"control", control);? ? ? ? PrintDescription(@"x", x);? ? ? ? PrintDescription(@"y", y);? ? ? ? PrintDescription(@"xy", xy);? ? ? ? ? ? ? ? NSLog(@"\n\tUsing NSObject methods, normal setX: is %p, overridden setX: is %p\n",? ? ? ? ? ? ? [control methodForSelector:@selector(setX:)],? ? ? ? ? ? ? [xmethodForSelector:@selector(setX:)]);? ? ? ? NSLog(@"\n\tUsing libobjc functions, normal setX: is %p, overridden setX: is %p\n",? ? ? ? ? ? ? method_getImplementation(class_getInstanceMethod(object_getClass(control),? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @selector(setX:))),method_getImplementation(class_getInstanceMethod(object_getClass(x),@selector(setX:))));? ? }return0;}

在上面的代碼中,輔助函數(shù)?ClassMethodNames 使用 runtime 函數(shù)來獲取類的方法列表墅冷,PrintDescription 打印對象的信息纯路,包括通過 -class 獲取的類名, isa 指針指向的類的名字以及其中方法列表寞忿。

在這里驰唬,我創(chuàng)建了四個對象,x 對象的 x 屬性被觀察腔彰,y?對象的 y 屬性被觀察叫编,xy 對象的 x 和 y 屬性均被觀察,參照對象 control 沒有屬性被觀察霹抛。在代碼的最后部分搓逾,分別通過兩種方式(對象方法和 runtime 方法)打印出參數(shù)對象 control 和被觀察對象 x 對象的 setX 方面的實現(xiàn)地址,來對比顯示正常情況下 setter 實現(xiàn)以及派生類中重寫的 setter 實現(xiàn)杯拐。

編譯運行霞篡,輸出如下:

control:

NSObject classFoo

libobjc classFoo

implements methods

x:

NSObject class Foo

libobjc classNSKVONotifying_Foo

implements methods

y:

NSObject class Foo

libobjc classNSKVONotifying_Foo

implements methods

xy:

NSObject class Foo

libobjc classNSKVONotifying_Foo

implements methods

Using NSObject methods, normal setX: is 0x100001df0, overridden setX: is 0x100001df0

Using libobjc functions, normal setX: is0x100001df0, overridden setX: is0x7fff8458e025

從上面的輸出可以看到世蔗,如果使用對象的 -class 方面輸出類名始終為:Foo,這是因為新誕生的派生類重寫了 -class 方法聲稱它就是起初的基類寇损,只有使用 runtime 函數(shù)?object_getClass 才能一睹芳容:NSKVONotifying_Foo凸郑。 注意看:x,y 以及 xy 三個被觀察對象真正的類型都是?NSKVONotifying_Foo矛市,而且該類實現(xiàn)了:setY:, setX:, class, dealloc, _isKVOA 這些方法芙沥。其中 setX:, setY:, class 和 dealloc 前面已經(jīng)講到過,私有方法?_isKVOA 估計是用來標示該類是一個 KVO 機制聲稱的類浊吏。在這里 Objective C 做了一些優(yōu)化而昨,它對所有被觀察對象只生成一個派生類,該派生類實現(xiàn)所有被觀察對象的 setter 方法找田,這樣就減少了派生類的數(shù)量歌憨,提供了效率。所有?NSKVONotifying_Foo 這個派生類重寫了 setX墩衙,setY方法(留意:沒有必要重寫 setZ 方法)务嫡。

接著來看最后兩行輸出,地址0x100001df0是 Foo 類中的實現(xiàn)漆改,而地址是0x7fff8458e025是派生類NSKVONotifying_Foo類中的實現(xiàn)心铃。那后面那個地址到底是什么呢?可以通過 GDB 的 info 命令加 symbol 參數(shù)來查看該地址的信息:

(gdb)info symbol 0x7fff8458e025

_NSSetIntValueAndNotifyin section LC_SEGMENT.__TEXT.__text of /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation

看起來它是 Foundation 框架提供的私有函數(shù):_NSSetIntValueAndNotify挫剑。更進一步去扣,我們來看看 Foundation 到底提供了哪些用于 KVO 的輔助函數(shù)。打開 terminal樊破,使用 nm -a 命令查看 Foundation 中的信息:

nm -a /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation

其中查找到我們關注的函數(shù):

00000000000233e7 t __NSSetDoubleValueAndNotify00000000000f32ba t __NSSetFloatValueAndNotify0000000000025025t __NSSetIntValueAndNotify000000000007fbb5 t __NSSetLongLongValueAndNotify00000000000f33e8 t __NSSetLongValueAndNotify000000000002d36c t __NSSetObjectValueAndNotify0000000000024dc5 t __NSSetPointValueAndNotify00000000000f39ba t __NSSetRangeValueAndNotify00000000000f3aeb t __NSSetRectValueAndNotify00000000000f3512 t __NSSetShortValueAndNotify00000000000f3c2f t __NSSetSizeValueAndNotify00000000000f363b t __NSSetUnsignedCharValueAndNotify000000000006e91ft __NSSetUnsignedIntValueAndNotify0000000000034b5b t __NSSetUnsignedLongLongValueAndNotify00000000000f3766 t __NSSetUnsignedLongValueAndNotify00000000000f3890 t __NSSetUnsignedShortValueAndNotify00000000000f3060 t __NSSetValueAndNotifyForKeyInIvar00000000000f30d7 t __NSSetValueAndNotifyForUndefinedKey

Foundation 提供了大部分基礎數(shù)據(jù)類型的輔助函數(shù)(Objective C中的 Boolean 只是?unsigned char 的?typedef愉棱,所以包括了,但沒有 C++中的 bool)哲戚,此外還包括一些常見的 Cocoa 結構體如 Point, Range, Rect, Size奔滑,這表明這些結構體也可以用于自動鍵值觀察,但要注意除此之外的結構體就不能用于自動鍵值觀察了顺少。對于所有 Objective C 對象對應的是?__NSSetObjectValueAndNotify 方法档押。

七,總結

KVO 并不是什么新事物祈纯,換湯不換藥,它只是觀察者模式在 Objective C 中的一種運用叼耙,這是 KVO 的指導思想所在腕窥。其他語言實現(xiàn)中也有“KVO”,如 WPF 中的 binding筛婉。而在 Objective C 中又是通過強大的 runtime 來實現(xiàn)自動鍵值觀察的簇爆。至此癞松,對 KVO 的使用以及注意事項,內部實現(xiàn)都介紹完畢入蛆,對 KVO 的理解又深入一層了响蓉。Objective 中的 KVO 雖然可以用,但卻非完美哨毁,有興趣的了解朋友請查看《KVO 的缺陷》?以及改良實現(xiàn)MAKVONotificationCenter枫甲。

八,引用

Key-value observing:官方文檔

Key-Value Observing Done Right: 官方 KVO 實現(xiàn)的缺陷

MAKVONotificationCenter: 一個改良的 Notification 實現(xiàn)扼褪,托管在 GitHub 上

Friday Q&A 2009-01-23

深入淺出Cocoa 之動態(tài)創(chuàng)建類

深入淺出Cocoa之類與對象

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末想幻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子话浇,更是在濱河造成了極大的恐慌脏毯,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幔崖,死亡現(xiàn)場離奇詭異食店,居然都是意外死亡,警方通過查閱死者的電腦和手機赏寇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門吉嫩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蹋订,你說我怎么就攤上這事率挣。” “怎么了露戒?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵椒功,是天一觀的道長。 經(jīng)常有香客問我智什,道長动漾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任荠锭,我火速辦了婚禮旱眯,結果婚禮上,老公的妹妹穿的比我還像新娘证九。我一直安慰自己删豺,他們只是感情好,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布愧怜。 她就那樣靜靜地躺著呀页,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拥坛。 梳的紋絲不亂的頭發(fā)上蓬蝶,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天尘分,我揣著相機與錄音,去河邊找鬼丸氛。 笑死培愁,一個胖子當著我的面吹牛,可吹牛的內容都是我干的缓窜。 我是一名探鬼主播定续,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雹洗!你這毒婦竟也來了香罐?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤时肿,失蹤者是張志新(化名)和其女友劉穎庇茫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體螃成,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡旦签,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了寸宏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宁炫。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖氮凝,靈堂內的尸體忽然破棺而出羔巢,到底是詐尸還是另有隱情,我是刑警寧澤罩阵,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布竿秆,位于F島的核電站,受9級特大地震影響稿壁,放射性物質發(fā)生泄漏幽钢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一傅是、第九天 我趴在偏房一處隱蔽的房頂上張望匪燕。 院中可真熱鬧,春花似錦喧笔、人聲如沸帽驯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尼变。三九已至,卻和暖如春梗劫,著一層夾襖步出監(jiān)牢的瞬間享甸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工梳侨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛉威,地道東北人。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓走哺,卻偏偏與公主長得像蚯嫌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子丙躏,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內容