KVO的使用和底層實(shí)現(xiàn)

本文代碼下載地址

一:kvo簡介

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)對象與觀察者對象解耦嘀略。

二:運(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í)會(huì)用到库糠,暫且不提,值得注意的是:不要忘記解除注冊,否則會(huì)導(dǎo)致資源泄露涮毫。

2:設(shè)置屬性

將觀察者與被觀察者注冊好之后瞬欧,就可以對觀察者對象的屬性進(jìn)行操作,這些變更操作就會(huì)被通知給觀察者對象罢防。注意艘虎,只有遵循 KVO 方式來設(shè)置屬性,觀察者對象才會(huì)獲取通知咒吐,也就是說遵循使用屬性的 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
#import <Foundation/Foundation.h>
@interface Observer : NSObject
@end

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

@implementation Observer

//屬性改變時(shí)的回調(diào)方法

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    
    NSLog(@"%@類的對象%@改變了\n%@",object,keyPath,change);
    
//    下面是對TargetWtapper類的對象information(其值依賴于Target類的屬性age和grade的值)的監(jiān)聽的實(shí)現(xiàn)
    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];
    }
}

@end

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

 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"];


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

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

在這里 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)。

1:首先绪励,需要手動(dòng)實(shí)現(xiàn)屬性的 setter 方法

并在設(shè)置操作的前后分別調(diào)用 willChangeValueForKey: 和 didChangeValueForKey方法肿孵,這兩個(gè)方法用于通知系統(tǒng)該 key 的屬性值即將和已經(jīng)變更了;

2:其次疏魏,要實(shí)現(xiàn)類方法

automaticallyNotifiesObserversForKey停做,并在其中設(shè)置對該 key 不自動(dòng)發(fā)送通知(返回 NO 即可)。這里要注意大莫,對其它非手動(dòng)實(shí)現(xiàn)的 key蛉腌,要轉(zhuǎn)交給 super 來處理。
具體代碼如下

Target.h
#import <Foundation/Foundation.h>

@interface Target : NSObject
{
    int age;
}

@property (nonatomic, readwrite) int grade;
@property (nonatomic, readwrite) int age;

@end

Target.m
#import "Target.h"

@implementation Target

@synthesize age; // for automatic KVO - age
@synthesize grade;

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

//手動(dòng)監(jiān)聽age的操作

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

五:鍵值觀察依賴鍵

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

1:觀察依賴鍵

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

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

@implementation Observer

//屬性改變時(shí)的回調(diào)方法

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    
//    NSLog(@"%@類的對象%@改變了\n%@",object,keyPath,change);
    
//    下面是對TargetWtapper類的對象information(其值依賴于Target類的屬性age和grade的值)的監(jiān)聽的實(shí)現(xiàn)
    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];
    }
}

@end

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

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

<1>首先,要手動(dòng)實(shí)現(xiàn)屬性 information 的 setter/getter 方法

在其中使用 Target 的屬性來完成其 setter 和 getter媚值。

<2>其次狠毯,要實(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 的觀察者都會(huì)得到通知。

TargetWrappe.h
#import <Foundation/Foundation.h>

@class Target;

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

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

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

@end

TargetWrappe.m
#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]];
}

//關(guān)聯(lián)的屬性的改變需要實(shí)現(xiàn)的代理方法是下面的這兩個(gè)之中的其中一個(gè),不要再實(shí)現(xiàn)- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 這個(gè)方法了,因?yàn)樗麜?huì)回調(diào)在Observe.m之中的那個(gè)方法
+ (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;
//}

輸出結(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)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類,在這個(gè)派生類中重寫基類中任何被觀察屬性的 setter 方法辆雾。
派生類在被重寫的 setter 方法實(shí)現(xiàn)真正的通知機(jī)制肪笋,就如前面手動(dòng)實(shí)現(xiàn)鍵值觀察那樣。這么做是基于設(shè)置屬性會(huì)調(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)用就會(huì)調(diào)用重寫的 setter划咐,從而激活鍵值通知機(jī)制拴念。此外,派生類還重寫了 dealloc 方法來釋放資源尖殃。**

877100-146caa2385fe8d2a.png

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 。
本文代碼下載地址

八:引用,如有侵權(quán),本人立刻刪除

深入淺出Cocoa詳解鍵值觀察KVO及其實(shí)現(xiàn)機(jī)理
Key-Value Observing Programming Guide官方文檔
官方 KVO 實(shí)現(xiàn)的缺陷
深入淺出Cocoa 之動(dòng)態(tài)創(chuàng)建類
深入淺出Cocoa之類與對象

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末翎迁,一起剝皮案震驚了整個(gè)濱河市栋猖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汪榔,老刑警劉巖蒲拉,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異痴腌,居然都是意外死亡雌团,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門士聪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锦援,“玉大人,你說我怎么就攤上這事剥悟×樗拢” “怎么了曼库?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長略板。 經(jīng)常有香客問我毁枯,道長,這世上最難降的妖魔是什么叮称? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任种玛,我火速辦了婚禮,結(jié)果婚禮上颅拦,老公的妹妹穿的比我還像新娘蒂誉。我一直安慰自己,他們只是感情好距帅,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布右锨。 她就那樣靜靜地躺著,像睡著了一般碌秸。 火紅的嫁衣襯著肌膚如雪绍移。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天讥电,我揣著相機(jī)與錄音蹂窖,去河邊找鬼。 笑死恩敌,一個(gè)胖子當(dāng)著我的面吹牛瞬测,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纠炮,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼月趟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了恢口?” 一聲冷哼從身側(cè)響起孝宗,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耕肩,沒想到半個(gè)月后因妇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猿诸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年婚被,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梳虽。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摔寨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怖辆,到底是詐尸還是另有隱情是复,我是刑警寧澤删顶,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站淑廊,受9級(jí)特大地震影響逗余,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜季惩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一录粱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧画拾,春花似錦啥繁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜜另,卻和暖如春适室,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背举瑰。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工捣辆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人此迅。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓汽畴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耸序。 傳聞我的和親對象是個(gè)殘疾皇子整袁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容