• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            羅朝輝(飄飄白云)

            關(guān)注嵌入式操作系統(tǒng),移動(dòng)平臺,圖形開發(fā)。-->加微博 ^_^

              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
              85 隨筆 :: 0 文章 :: 169 評論 :: 0 Trackbacks

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

            羅朝輝 (http://www.shnenglu.com/kesalin/)

            本文遵循“署名-非商業(yè)用途-保持一致”創(chuàng)作公用協(xié)議

            一,前言

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

            一個(gè)目標(biāo)對象管理所有依賴于它的觀察者對象,并在它自身的狀態(tài)改變時(shí)主動(dòng)通知觀察者對象。這個(gè)主動(dòng)通知通常是通過調(diào)用各觀察者對象所提供的接口方法來實(shí)現(xiàn)的。觀察者模式較完美地將目標(biāo)對象與觀察者對象解耦。

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

            本文源碼下載:點(diǎn)此下載

            二,運(yùn)用鍵值觀察

            1,注冊與解除注冊

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

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

            這兩個(gè)方法的定義在 Foundation/NSKeyValueObserving.h 中,NSObject,NSArray,NSSet均實(shí)現(xiàn)了以上方法,因此我們不僅可以觀察普通對象,還可以觀察數(shù)組或結(jié)合類對象。在該頭文件中,我們還可以看到 NSObject 還實(shí)現(xiàn)了 NSKeyValueObserverNotification 的 category 方法(更多類似方法,請查看該頭文件):

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

            這兩個(gè)方法在手動(dòng)實(shí)現(xiàn)鍵值觀察時(shí)會用到,暫且不提。

            值得注意的是:不要忘記解除注冊,否則會導(dǎo)致資源泄露。

            2,設(shè)置屬性

            將觀察者與被觀察者注冊好之后,就可以對觀察者對象的屬性進(jìn)行操作,這些變更操作就會被通知給觀察者對象。注意,只有遵循 KVO 方式來設(shè)置屬性,觀察者對象才會獲取通知,也就是說遵循使用屬性的 setter 方法,或通過 key-path 來設(shè)置:

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

            3,處理變更通知

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

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

            在這里,change 這個(gè)字典保存了變更信息,具體是哪些信息取決于注冊時(shí)的 NSKeyValueObservingOptions。

            4,下面來看看一個(gè)完整的使用示例:

            觀察者類:

            // Observer.h
            @interface Observer : NSObject
            @end

            // Observer.m
            #import "Observer.h"
            #import <objc/runtime.h>
            #import "Target.h"

            @implementation Observer

            - (void) observeValueForKeyPath:(NSString *)keyPath
                                   ofObject:(id)object 
                                     change:(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:object
                                           change:change
                                          context:context];
                }
            }

            @end

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

            使用示例:

            Observer * observer = [[[Observer alloc] init] autorelease];

            Target * target = [[[Target alloc] init] autorelease];
            [target addObserver:observer
                     forKeyPath:@"age"
                        options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                        context:[Target class]];

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

            [target removeObserver:observer forKeyPath:@"age"];

            在這里 observer 觀察 target 的 age 屬性變化,運(yùn)行結(jié)果如下:

              >> class: Target, Age changed

              old age is 10

              new age is 30

             

            三,手動(dòng)實(shí)現(xiàn)鍵值觀察

            上面的 Target 應(yīng)該怎么實(shí)現(xiàn)呢?首先來看手動(dòng)實(shí)現(xiàn)。

            @interface Target : NSObject
            {
                int age;
            }

            // for manual KVO - age
            - (int) age;
            - (void) setAge:(int)theAge;

            @end

            @implementation Target

            - (id) init
            {
                self = [super init];
                if (nil != self)
                {
                    age = 10;
                }
                
                return self;
            }

            // for manual KVO - age
            - (int) age
            {
                return age;
            }

            - (void) setAge:(int)theAge
            {
                [self willChangeValueForKey:@"age"];
                age = theAge;
                [self didChangeValueForKey:@"age"];
            }

            + (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
                if ([key isEqualToString:@"age"]) {
                    return NO;
                }

                return [super automaticallyNotifiesObserversForKey:key];
            }

            @end

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

            其次,要實(shí)現(xiàn)類方法 automaticallyNotifiesObserversForKey,并在其中設(shè)置對該 key 不自動(dòng)發(fā)送通知(返回 NO 即可)。這里要注意,對其它非手動(dòng)實(shí)現(xiàn)的 key,要轉(zhuǎn)交給 super 來處理。

             

            四,自動(dòng)實(shí)現(xiàn)鍵值觀察

            自動(dòng)實(shí)現(xiàn)鍵值觀察就非常簡單了,只要使用了屬性即可。

            @interface Target : NSObject// for automatic KVO - age
            @property (nonatomic, readwrite) int age;
            @end

            @implementation Target
            @synthesize age; // for automatic KVO - age

            - (id) init
            {
                self = [super init];
                if (nil != self)
                {
                    age = 10;
                }
                
                return self;
            }

            @end

             

            五,鍵值觀察依賴鍵

            有時(shí)候一個(gè)屬性的值依賴于另一對象中的一個(gè)或多個(gè)屬性,如果這些屬性中任一屬性的值發(fā)生變更,被依賴的屬性值也應(yīng)當(dāng)為其變更進(jìn)行標(biāo)記。因此,object 引入了依賴鍵。

            1,觀察依賴鍵

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

            #import "TargetWrapper.h"

            - (void) observeValueForKeyPath:(NSString *)keyPath
                                   ofObject:(id)object 
                                     change:(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 if ([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:object
                                           change:change
                                          context:context];
                }
            }

            2,實(shí)現(xiàn)依賴鍵

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

            @interface Target : NSObject
            @property (nonatomic, readwrite) int grade;
            @property (nonatomic, readwrite) int age;
            @end

            @implementation Target
            @synthesize age; // for automatic KVO - age
            @synthesize grade;
            @end

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

            @class Target;

            @interface TargetWrapper : NSObject
            {
            @private
                Target * _target;
            }

            @property(nonatomic, assign) NSString * information;
            @property(nonatomic, retain) Target * target;

            -(id) init:(Target *)aTarget;

            @end

            #import "TargetWrapper.h"
            #import "Target.h"

            @implementation TargetWrapper

            @synthesize target = _target;

            -(id) init:(Target *)aTarget
            {
                self = [super init];
                if (nil != self) {
                    _target = [aTarget retain];
                }
                
                return self;
            }

            -(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];
                return keyPaths;
            }

            //+ (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

            首先,要手動(dòng)實(shí)現(xiàn)屬性 information 的 setter/getter 方法,在其中使用 Target 的屬性來完成其 setter 和 getter。

            其次,要實(shí)現(xiàn) keyPathsForValuesAffectingInformation  或 keyPathsForValuesAffectingValueForKey: 方法來告訴系統(tǒng) information 屬性依賴于哪些其他屬性,這兩個(gè)方法都返回一個(gè)key-path 的集合。在這里要多說幾句,如果選擇實(shí)現(xiàn) keyPathsForValuesAffectingValueForKey,要先獲取 super 返回的結(jié)果 set,然后判斷 key 是不是目標(biāo) key,如果是就將依賴屬性的 key-path 結(jié)合追加到 super 返回的結(jié)果 set 中,否則直接返回 super的結(jié)果。

            在這里,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:[TargetWrapper class]];

            [target setAge:30];
            [target setGrade:1];
            [wrapper removeObserver:observer forKeyPath:@"information"];

            輸出結(jié)果:

             >> 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

             

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

            1,實(shí)現(xiàn)機(jī)理

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

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

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

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

            如果你對類和對象的關(guān)系不太明白,請閱讀《深入淺出Cocoa之類與對象》;如果你對如何動(dòng)態(tài)創(chuàng)建類不太明白,請閱讀《深入淺出Cocoa 之動(dòng)態(tài)創(chuàng)建類》。

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

            Key-Value Observing Implementation Details

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

            The isa pointer, 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 the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

            2,代碼分析

            由于派生類中被重寫的 class 對我們?nèi)鲋e(它說它就是起初的基類),我們只有通過調(diào)用 runtime 函數(shù)才能揭開派生類的真面目。 下面來看  Mike Ash 的代碼:

            首先是帶有 x, y, z 三個(gè)屬性的觀察目標(biāo) Foo:

            @interface Foo : NSObject
            {
                int x;
                int y;
                int z;
            }

            @property int x;
            @property int y;
            @property int z;

            @end

            @implementation Foo
            @synthesize x, y, z;
            @end

            下面是檢驗(yàn)代碼:

            #import <objc/runtime.h>

            static NSArray * ClassMethodNames(Class c)
            {
                NSMutableArray * array = [NSMutableArray array];
                
                unsigned int methodCount = 0;
                Method * methodList = class_copyMethodList(c, &methodCount);
                unsigned int i;
                for(i = 0; i < methodCount; i++) {
                    [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
                }

                free(methodList);
                
                return array;
            }

            static void PrintDescription(NSString * name, id obj)
            {
                NSString * str = [NSString stringWithFormat:
                                  @"\n\t%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",
                                  name,
                                  obj,
                                  class_getName([obj class]),
                                  class_getName(obj->isa),
                                  [ClassMethodNames(obj->isa) componentsJoinedByString:@", "]];
                NSLog(@"%@", str);
            }

            int main (int argc, const char * 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:0 context:NULL];
                    [y addObserver:anything forKeyPath:@"y" options:0 context:NULL];
                    
                    [xy addObserver:anything forKeyPath:@"x" options:0 context:NULL];
                    [xy addObserver:anything forKeyPath:@"y" options:0 context: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:)],
                           [x methodForSelector:@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:))));
                }

                return 0;
            }

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

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

            編譯運(yùn)行,輸出如下:

            control: <Foo: 0x10010c980>

            NSObject class Foo

            libobjc class Foo

            implements methods <x, setX:, y, setY:, z, setZ:>

            x: <Foo: 0x10010c920>

            NSObject class Foo

            libobjc class NSKVONotifying_Foo

            implements methods <setY:, setX:, class, dealloc, _isKVOA>

            y: <Foo: 0x10010c940>

            NSObject class Foo

            libobjc class NSKVONotifying_Foo

            implements methods <setY:, setX:, class, dealloc, _isKVOA>

            xy: <Foo: 0x10010c960>

            NSObject class Foo

            libobjc class NSKVONotifying_Foo

            implements methods <setY:, setX:, class, dealloc, _isKVOA>

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

            Using libobjc functions, normal setX: is 0x100001df0, overridden setX: is 0x7fff8458e025

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

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

             (gdb) info symbol 0x7fff8458e025

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

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

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

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

            00000000000233e7 t __NSSetDoubleValueAndNotify
            00000000000f32ba t __NSSetFloatValueAndNotify
            0000000000025025 t __NSSetIntValueAndNotify
            000000000007fbb5 t __NSSetLongLongValueAndNotify
            00000000000f33e8 t __NSSetLongValueAndNotify
            000000000002d36c t __NSSetObjectValueAndNotify
            0000000000024dc5 t __NSSetPointValueAndNotify
            00000000000f39ba t __NSSetRangeValueAndNotify
            00000000000f3aeb t __NSSetRectValueAndNotify
            00000000000f3512 t __NSSetShortValueAndNotify
            00000000000f3c2f t __NSSetSizeValueAndNotify
            00000000000f363b t __NSSetUnsignedCharValueAndNotify
            000000000006e91f t __NSSetUnsignedIntValueAndNotify
            0000000000034b5b t __NSSetUnsignedLongLongValueAndNotify
            00000000000f3766 t __NSSetUnsignedLongValueAndNotify
            00000000000f3890 t __NSSetUnsignedShortValueAndNotify
            00000000000f3060 t __NSSetValueAndNotifyForKeyInIvar
            00000000000f30d7 t __NSSetValueAndNotifyForUndefinedKey

            Foundation 提供了大部分基礎(chǔ)數(shù)據(jù)類型的輔助函數(shù)(Objective C中的 Boolean 只是 unsigned char 的 typedef,所以包括了,但沒有 C++中的 bool),此外還包括一些常見的 Cocoa 結(jié)構(gòu)體如 Point, Range, Rect, Size,這表明這些結(jié)構(gòu)體也可以用于自動(dòng)鍵值觀察,但要注意除此之外的結(jié)構(gòu)體就不能用于自動(dòng)鍵值觀察了。對于所有 Objective C 對象對應(yīng)的是 __NSSetObjectValueAndNotify 方法。

             

            七,總結(jié)

            KVO 并不是什么新事物,換湯不換藥,它只是觀察者模式在 Objective C 中的一種運(yùn)用,這是 KVO 的指導(dǎo)思想所在。其他語言實(shí)現(xiàn)中也有“KVO”,如 WPF 中的 binding。而在 Objective C 中又是通過強(qiáng)大的 runtime 來實(shí)現(xiàn)自動(dòng)鍵值觀察的。至此,對 KVO 的使用以及注意事項(xiàng),內(nèi)部實(shí)現(xiàn)都介紹完畢,對 KVO 的理解又深入一層了。Objective 中的 KVO 雖然可以用,但卻非完美,有興趣的了解朋友請查看《KVO 的缺陷》 以及改良實(shí)現(xiàn) MAKVONotificationCenter 。

             

            八,引用

            Key-value observing:官方文檔

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

            MAKVONotificationCenter : 一個(gè)改良的 Notification 實(shí)現(xiàn),托管在 GitHub 上

            Friday Q&A 2009-01-23

            深入淺出Cocoa之類與對象

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

            posted on 2012-11-17 17:41 羅朝輝 閱讀(21921) 評論(4)  編輯 收藏 引用 所屬分類: Cocoa 開發(fā)

            評論

            # re: [深入淺出Cocoa]詳解鍵值觀察(KVO)及其實(shí)現(xiàn)機(jī)理 2013-11-11 09:50 hhb
            你這個(gè)程序是 mac 的?  回復(fù)  更多評論
              

            # re: [深入淺出Cocoa]詳解鍵值觀察(KVO)及其實(shí)現(xiàn)機(jī)理 2013-11-11 17:27 飄飄白云
            @hhb

            是的  回復(fù)  更多評論
              

            # re: [深入淺出Cocoa]詳解鍵值觀察(KVO)及其實(shí)現(xiàn)機(jī)理 2013-12-03 13:36 Xing He
            KVO does now support arbitrary types.

            " It appears that KVO uses the NSInvocation support to package up the parameters being passed in. This allows it to support any type that the forwarding machinery can understand, which should be everything."

            mentioned in http://www.mikeash.com/pyblog/friday-qa-2009-01-23.html  回復(fù)  更多評論
              

            # re: [深入淺出Cocoa]詳解鍵值觀察(KVO)及其實(shí)現(xiàn)機(jī)理[未登錄] 2014-08-01 16:31 Mars
            - 非常不錯(cuò),看了有了新的認(rèn)識。  回復(fù)  更多評論
              

            国产精品成人99久久久久| 亚洲精品乱码久久久久久蜜桃图片| 狠狠色婷婷综合天天久久丁香| a级毛片无码兔费真人久久| 亚洲日本va午夜中文字幕久久 | 久久国产乱子伦免费精品| 四虎国产精品免费久久久| 一本久久a久久精品亚洲| 91久久福利国产成人精品| 中文字幕久久久久人妻| 国产综合成人久久大片91| 亚洲精品无码久久一线| 合区精品久久久中文字幕一区| 国产亚洲欧美精品久久久| 欧美精品乱码99久久蜜桃| 国内精品久久久久影院免费| 97精品依人久久久大香线蕉97| 狠狠精品久久久无码中文字幕 | 丁香狠狠色婷婷久久综合| 久久久久国产精品嫩草影院| 99久久精品国产一区二区| 国内精品久久久久久久97牛牛 | 久久精品国产乱子伦| 久久综合九色欧美综合狠狠| 国产精品综合久久第一页| 婷婷久久综合九色综合98| 国产人久久人人人人爽 | 久久久99精品成人片中文字幕 | 77777亚洲午夜久久多人| 欧美亚洲另类久久综合婷婷| 激情五月综合综合久久69| 国产亚洲精午夜久久久久久| 中文精品久久久久国产网址| 久久精品国产99国产电影网 | 午夜精品久久久久久毛片| 久久婷婷色综合一区二区| 久久夜色精品国产亚洲| 国产毛片欧美毛片久久久 | 国内精品伊人久久久久妇| 色妞色综合久久夜夜| 久久99九九国产免费看小说|