iOS-KVO原理

初探

Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.

KVO, 即Key-value observing,也就是我們常說的鍵值觀察跌榔,它是是一種機(jī)制眨攘,允許將其他對(duì)象的指定屬性的更改通知給指定對(duì)象畅形。

KVO機(jī)制對(duì)應(yīng)用程序中模型層和控制器層之間的通信特別有用齿风,控制器對(duì)象通常觀察模型對(duì)象的屬性榕吼,而視圖對(duì)象通過控制器觀察模型對(duì)象的屬性继低,模型對(duì)象可能會(huì)觀察其他模型對(duì)象(通常是確定從屬值何時(shí)更改)竹宋,甚至是自身(再次確定從屬值何時(shí)更改)劳澄。

比如說,我們的視圖控制器viewController上有一個(gè)label用來顯示模型對(duì)象person的一個(gè)屬性name逝撬,我們需要的實(shí)時(shí)顯示浴骂,當(dāng)name發(fā)生變化的時(shí)候,label的顯示也會(huì)及時(shí)變化宪潮,那么我們就可以給person添加一個(gè)觀察者viewController溯警,用以觀察name屬性,當(dāng)name發(fā)生變化的時(shí)候狡相,viewController就可以及時(shí)刷新label梯轻,這樣就做到了模型層和控制器層的通信。

KVO的使用

使用步驟

  • 使用addObserver:forKeyPath:options:context:方法將觀察者注冊(cè)到觀察對(duì)象
  • observeValueForKeyPath:ofObject:change:context:在觀察者內(nèi)部實(shí)現(xiàn)以接受更改通知消息
  • 當(dāng)觀察者removeObserver:forKeyPath:不再應(yīng)該接收消息時(shí)尽棕,使用該方法注銷觀察者喳挑。該方法至少在從內(nèi)存釋放觀察者之前調(diào)用。

下面我們配合一個(gè)例子進(jìn)行探究:

@interface TPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSMutableArray *hobbies;
@property (nonatomic, copy) NSString *downloadProgress;
@property (nonatomic, assign) double writtenData;
@property (nonatomic, assign) double totalData;

@end

@implementation TPerson

@end

然后在ViewController里實(shí)現(xiàn)如下代碼:

@interface ViewController ()

@property (nonatomic, strong) TPerson *person;
@property (nonatomic, assign) int pNumber;

@end

1. 注冊(cè)鍵值觀察

被觀察對(duì)象首先通過調(diào)用下面方法向觀察者注冊(cè)自己滔悉,并且傳入要觀察的屬性的keyPath伊诵,另外還指定了一個(gè)options參數(shù)和一個(gè)上下文指針context來管理通知的各個(gè)方面。

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

該方法的參數(shù)如下:

  • (NSObject *)observer: 觀察者
  • (NSString *)keyPath: 要觀察的屬性
  • (NSKeyValueObservingOptions)options: 設(shè)置回調(diào)的內(nèi)容和時(shí)機(jī)
  • (nullable void *)context: 上下文指針用以區(qū)分被觀察者和觀察屬性

我們?cè)賮砜纯?code>options的取值有什么含義:

  • NSKeyValueObservingOptionNew = 0x01: 變化之后的新值
  • NSKeyValueObservingOptionOld = 0x02: 變化之前的舊值
  • NSKeyValueObservingOptionInitial = 0x04: 變化之前通知
  • NSKeyValueObservingOptionPrior = 0x08: 變化之后通知

注意回官,options可以填寫多個(gè)曹宴。

我們可以使用context指針用來區(qū)分每一個(gè)監(jiān)聽,這樣在通知回調(diào)的時(shí)候可以直接根據(jù)context進(jìn)行區(qū)分處理歉提,這樣更簡(jiǎn)潔一些笛坦。使用方式如下:

static void *PersonNickNameContext = &PersonNickNameContext;

當(dāng)不使用context指針的時(shí)候区转,需要傳入NULL,而不能傳入nil版扩,因?yàn)椴皇?code>id類型废离。

2. 接收變化回調(diào)

觀察者必須實(shí)現(xiàn)下面方法,才能接收到觀察對(duì)象的變化礁芦。

- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object 
                        change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change 
                       context:(nullable void *)context;
  • (nullable NSString *)keyPath: 觀察的屬性
  • (nullable id)object: 觀察對(duì)象
  • (nullable NSDictionary<NSKeyValueChangeKey, id> *)change: 返回的觀察信息
  • (nullable void *)context: 上下文指針
  • NSKeyValueChangeKindKey: 返回的更改信息的時(shí)機(jī)蜻韭,1是變化之前,2是變化之后
  • NSKeyValueChangeNewKey: 返回的值是新值
  • NSKeyValueChangeOldKey: 返回值是舊值
  • NSKeyValueChangeIndexesKey: 觀察的屬性是數(shù)組或者NSSet的下標(biāo)變化
  • NSKeyValueChangeNotificationIsPriorKey: 觀察通知的時(shí)機(jī)是否是Prior
  • NSKeyValueChangeSetting = 1: 數(shù)據(jù)的設(shè)置
  • NSKeyValueChangeInsertion = 2: 集合的插入
  • NSKeyValueChangeRemoval = 3: 集合的移除
  • NSKeyValueChangeReplacement = 4: 集合的替換

當(dāng)被觀察對(duì)象是集合對(duì)象宴偿,在NSKeyValueChangeKindKey字段中會(huì)包含NSKeyValueChangeInsertion湘捎、NSKeyValueChangeRemovalNSKeyValueChangeReplacement的信息窄刘,表示集合對(duì)象的操作方式。

3. 移除觀察

當(dāng)我們不需要觀察者繼續(xù)觀察對(duì)象了舷胜,就需要移除觀察者娩践。在觀察者被釋放之前必須移除觀察者,否則會(huì)出現(xiàn)BUG烹骨。移除之前必須注冊(cè)翻伺,否則就會(huì)崩潰。

一般沮焕,我們?cè)谟^察者初始化期間(例如在init中或viewDidLoad中)注冊(cè)為觀察者吨岭,在釋放過程中(通常在中dealloc)注銷,確保在觀察者從內(nèi)存中釋放之前將其注銷峦树。

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;

接著上面的例子辣辫,我們?cè)?code>viewDidLoad進(jìn)行注冊(cè)KVO,回調(diào)通知的策略給name屬性使用改變之前的魁巩,hobbies使用改變之后的急灭。:

static void *PersonNameContext = &PersonNameContext;
static void *PersonHobbyContext = &PersonHobbyContext;

- (void)viewDidLoad {
    self.person = [[TPerson alloc] init];
    
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial context:PersonNameContext];
    
    self.person.hobbies = [NSMutableArray array];
    [self.person addObserver:self forKeyPath:@"hobbies" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:PersonHobbyContext];
    
    [self.person addObserver:self forKeyPath:@"downloadProgress" options: NSKeyValueObservingOptionNew context:NULL];
}

監(jiān)聽回調(diào):

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if (context == PersonNameContext) {
        NSLog(@"===name===change==%@=====", change);
    } else if (context == PersonHobbyContext) {
        NSLog(@"===hobbies===change==%@=====", change);
    } else {
        NSLog(@"===downloadProgress===change==%@=====", change);
    }
}

然后在下面方法中改變觀察的屬性的值:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.pNumber++;
    self.person.name = [NSString stringWithFormat:@"%@-%d-%@", @"Number", self.pNumber, @"Person"];
    
    switch (self.pNumber % 4) {
        case 1:
        case 3:
        {
            [[self.person mutableArrayValueForKeyPath:@"hobbies"] addObject:@"add"];
        }
            break;
        case 2:
        {
            [[self.person mutableArrayValueForKeyPath:@"hobbies"] removeObject:@"add"];
        }
            break;
            
        case 0:
        {
            [[self.person mutableArrayValueForKeyPath:@"hobbies"] replaceObjectAtIndex:0 withObject:@"replace"];
        }
            break;
        default:
            break;
    }
    
    NSLog(@"===%@===", self.person.hobbies);
    
    self.person.writtenData += 10;
    self.person.totalData += 20;
}

然后在dealloc方法中移除觀察:

- (void)dealloc {
    [self.person removeObserver:self forKeyPath:@"name"];
    [self.person removeObserver:self forKeyPath:@"hobbies"];
    [self.person removeObserver:self forKeyPath:@"downloadProgress"];
}

運(yùn)行程序,控制臺(tái)輸出:

===name===change=={
    kind = 1;
    new = "<null>";
}=====

因?yàn)?code>name屬性的監(jiān)聽策略使用了NSKeyValueObservingOptionInitial谷遂,所以進(jìn)入頁(yè)面就會(huì)響應(yīng)葬馋。點(diǎn)擊一下頁(yè)面:

===name===change=={
    kind = 1; // NSKeyValueChangeSetting
    new = "Number-1-Person"; // NSKeyValueObservingOptionNew
    old = "<null>"; // NSKeyValueObservingOptionOld
}=====
// NSKeyValueObservingOptionPrior
===hobbies===change=={
    indexes = "<_NSCachedIndexSet: 0x600002949ac0>[number of indexes: 1 (in 1 ranges), indexes: (0)]"; // NSKeyValueChangeIndexesKey
    kind = 2; // NSKeyValueChangeInsertion
    notificationIsPrior = 1; // NSKeyValueObservingOptionPrior
}=====
===hobbies===change=={
    indexes = "<_NSCachedIndexSet: 0x600002949ac0>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
    kind = 2;
    new =     (
        add
    );
}=====

繼續(xù)點(diǎn)擊頁(yè)面,控制臺(tái)會(huì)輸出hobbiesremoveObjectreplaceObjectAtIndex的監(jiān)聽肾扰,kind也會(huì)有相應(yīng)的變化畴嘶,此處就不一一演示了。

注意集晚,觀察的屬性是數(shù)組的時(shí)候不能直接是add等方法:

[self.person.hobbies addObject:@"addd"]

由于KVO是基于KVC窗悯,而直接使用add等方法是不會(huì)觸發(fā)KVC的,所以我們要使用

[[self.person mutableArrayValueForKeyPath:@"hobbies"] addObject:@"add"]

符合KVO的使用標(biāo)準(zhǔn)

當(dāng)我們要使用KVO觀察某個(gè)對(duì)象的時(shí)候甩恼,必須得確保該對(duì)象符合KVO的使用標(biāo)準(zhǔn)才可以蟀瞧。

  • 該類和屬性必須要符合KVC沉颂,因?yàn)?code>KVO的實(shí)現(xiàn)依托于KVCKVO支持的數(shù)據(jù)類型與KVC相同悦污,包括Objective-C對(duì)象以及基本數(shù)據(jù)類型和結(jié)構(gòu)體铸屉。
  • 該類能為該屬性發(fā)出KVO更改的通知。
  • 當(dāng)有依賴關(guān)系的時(shí)候切端,注冊(cè)合適的依賴key彻坛。

發(fā)出KVO通知

發(fā)出KVO通知有兩種方式,一種是自動(dòng)通知踏枣,另外一種是手動(dòng)通知昌屉。

自動(dòng)通知

自動(dòng)通知需要該類和屬性必須要符合KVC,使用KVC的方式對(duì)屬性進(jìn)行賦值茵瀑。如下面方法:

- (void)setValue:(nullable id)value forKey:(NSString *)key

手動(dòng)通知

在某些情況下间驮,我們希望控制通知過程。例如马昨,最大程度地減少出于應(yīng)用程序特定原因而不必要的觸發(fā)通知竞帽,或?qū)⒍鄠€(gè)更改分組為一個(gè)通知。這是就可以使用手動(dòng)更改通知鸿捧。

手動(dòng)和自動(dòng)通知不是互斥的屹篓。當(dāng)我們使用手動(dòng)通知的時(shí)候,第一步需要重寫automaticallyNotifiesObserversForKey:匙奴,并且返回NO堆巧;第二步在在更改值之前調(diào)用willChangeValueForKey:、在更改值之后調(diào)用didChangeValueForKey:泼菌。當(dāng)一個(gè)類里既存在需要手動(dòng)通知的屬性谍肤,又存在需要自動(dòng)通知的屬性,在這個(gè)方法里就需要進(jìn)行判斷灶轰,分別處理谣沸。

- (void)setName:(NSString *)name {
    if (_name != name) {
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }
}

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    return NO;
}

如果操作的屬性是集合類型,需要使用以下方法:

- (void)willChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;
- (void)didChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;

注冊(cè)合適的依賴key

在許多情況下笋颤,一個(gè)屬性的值取決于另一個(gè)或多個(gè)其他屬性的值乳附。如果一個(gè)屬性的值發(fā)生更改,則派生屬性的值也應(yīng)標(biāo)記為更改伴澄。我們需要確保當(dāng)一個(gè)附屬的屬性發(fā)生變化時(shí)赋除,其父屬性也能收到改變通知。

一對(duì)一關(guān)系

要自動(dòng)觸發(fā)一對(duì)一關(guān)系的通知非凌,需要重寫 keyPathsForValuesAffectingValueForKey:或?qū)崿F(xiàn)遵循其定義的用于注冊(cè)從屬key的式的方法举农。

  1. 使用keyPathsForValuesAffectingValueForKey方法
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"downloadProgress"]) {
        NSArray *affectingKeys = @[@"totalData", @"writtenData"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}

- (NSString *)downloadProgress {
    if (self.writtenData == 0) {
        self.writtenData = 10;
    }
    if (self.totalData == 0) {
        self.totalData = 100;
    }
    return [[NSString alloc] initWithFormat:@"%f",1.0f*self.writtenData/self.totalData];
}

運(yùn)行程序,控制臺(tái)輸出:

===downloadProgress===change=={
    kind = 1;
    new = "0.100000";
}=====
===downloadProgress===change=={
    kind = 1;
    new = "0.083333";
}=====
  1. 使用keyPathsForValuesAffectingDownloadProgress代替keyPathsForValuesAffectingValueForKey方法也可以達(dá)到同樣的效果敞嗡。
+ (NSSet<NSString *> *)keyPathsForValuesAffectingDownloadProgress {
    return [NSSet setWithObjects:@"totalData", @"writtenData", nil];
}

一對(duì)多關(guān)系

假設(shè)有一個(gè)Department對(duì)象颁糟,Department又包含多個(gè)employees航背,所以該對(duì)象與Employee形成了一對(duì)多關(guān)系,而Employee具有薪金屬性棱貌。當(dāng)Department對(duì)象具有一個(gè)totalSalary屬性玖媚,該屬性取決于所有雇員的薪水,可以在observeValueForKeyPath進(jìn)行處理婚脱。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if (context == totalSalaryContext) {
        [self updateTotalSalary];
    }
}
 
- (void)updateTotalSalary {
    [self setTotalSalary:[self valueForKeyPath: @"employees.@ sum.salary"]];
}
 
- (void)setTotalSalary:(NSNumber *)newTotalSalary {
    if (totalSalary今魔!= newTotalSalary) {
        [self willChangeValueForKey:@“totalSalary”];
        _totalSalary = newTotalSalary;
        [self didChangeValueForKey:@“totalSalary”];
    }
}
 
-(NSNumber *)totalSalary {
    return _totalSalary;
}

KVO原理

看完KVO的使用,我們來看一下KVO的實(shí)現(xiàn)原理障贸,官方文檔這么說:

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.

KVO是使用isa-swizzling技術(shù)實(shí)現(xiàn)的错森。這個(gè)isa指針指向?qū)ο蟮念悾S護(hù)一個(gè)派發(fā)表篮洁。該派發(fā)表實(shí)質(zhì)上包含該類實(shí)現(xiàn)的方法的指針以及其他數(shù)據(jù)涩维。

在為對(duì)象的屬性注冊(cè)觀察者時(shí),將修改觀察對(duì)象的isa指針嘀粱,讓其指向一個(gè)中間類而不是原來的類激挪。表現(xiàn)出來的現(xiàn)象就是,isa指針的值不一定指向正確類锋叨,因?yàn)樗赡苤赶蛞粋€(gè)中間類。所以不要依靠isa指針來確定類宛篇,相反娃磺,應(yīng)該使用類方法確定實(shí)例對(duì)象的類。

我們可以使用上述demo來驗(yàn)證一下叫倍,在注冊(cè)觀察者的前后設(shè)置log偷卧,運(yùn)行程序:

image

從控制臺(tái)我們可以看出,此時(shí)生成了一個(gè)中間類NSKVONotifying_TPerson吆倦。

當(dāng)觀察一個(gè)對(duì)象A時(shí)听诸,KVO機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象A當(dāng)前類的中間類,其類名為NSKVONotifying_A蚕泽,該類繼承自對(duì)象A的本類晌梨,并為這個(gè)新的類重寫了被觀察屬性keyPathsetter方法。setter方法會(huì)負(fù)責(zé)在調(diào)用原setter方法之前和之后须妻,通知所有觀察對(duì)象屬性值的更改情況仔蝌。

被觀察對(duì)象Aisa指針從指向原來的A類,被KVO機(jī)制修改為指向NSKVONotifying_A類荒吏,來實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽敛惊。

需要注意的是,此處生成的是一個(gè)類绰更,而不是對(duì)象瞧挤,修改的是原對(duì)象的isa指向锡宋,讓其指向中間類

下面我們來驗(yàn)證一下NSKVONotifying_AA類的關(guān)系到底是不是繼承?我們?cè)谧?cè)前后使用下面打印一下TPerson類及其子類的名稱:

- (void)printClasses:(Class)cls {
    
    // 注冊(cè)類的總數(shù)
    int count = objc_getClassList(NULL, 0);
    // 創(chuàng)建一個(gè)數(shù)組特恬, 其中包含給定對(duì)象
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    // 獲取所有已注冊(cè)的類
    Class* classes = (Class*)malloc(sizeof(Class)*count);
    objc_getClassList(classes, count);
    for (int i = 0; i < count; i++) {
        if (cls == class_getSuperclass(classes[i])) {
            [mArray addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@"==classes==%@", mArray);
}

控制臺(tái)輸出:

image

可以看出执俩,生成的中間類是被觀察對(duì)象的類的子類

接著我們給TPerson添加一個(gè)實(shí)例變量,看看屬性和實(shí)例變量由于setter方法的差異會(huì)不會(huì)有不同的結(jié)果鸵鸥。

@public
NSString *ivarName;

touchesBegan事件奠滑,給ivarName賦值:

self.person->ivarName = @"ivarName";

運(yùn)行程序,控制臺(tái)輸出:

==change=={
    kind = 1;
    new = "Number-1-Person";
    old = "<null>";
}==

可以發(fā)現(xiàn)妒穴,并沒有ivarName的改變通知宋税,這也說明了ivarName沒有setter方法,所以并不能發(fā)出改變通知讼油。

同樣杰赛,我們可以在注冊(cè)前后打印類的方法來分析前后的差異:

- (void)printClassAllMethod:(Class)cls{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i < count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = class_getMethodImplementation(cls, sel);
        NSLog(@"--sel--%@--imp--%p",NSStringFromSelector(sel),imp);
    }
    free(methodList);
}

- (void)viewDidLoad {
    [self printClassAllMethod:[TPerson class]];
    NSLog(@"--注冊(cè)之前--");
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial context:PersonNameContext];
    NSLog(@"--注冊(cè)之后--");
    [self printClassAllMethod:NSClassFromString(@"NSKVONotifying_TPerson")];
}

由于注冊(cè)之后就生成了中間類NSKVONotifying_TPerson,所以注冊(cè)后我們直接打印中間類的方法矮台》ν停控制臺(tái)輸出結(jié)果如下:

 --sel--setHobbies:--imp--0x10f10ef70
 --sel--hobbies--imp--0x10f10ef50
 --sel--writtenData--imp--0x10f10eff0
 --sel--setWrittenData:--imp--0x10f10f010
 --sel--totalData--imp--0x10f10f040
 --sel--setTotalData:--imp--0x10f10f060
 --sel--.cxx_destruct--imp--0x10f10f090
 --sel--name--imp--0x10f10ef20
 --sel--setName:--imp--0x10f10eb90
 --sel--downloadProgress--imp--0x10f10ee10
 --sel--setDownloadProgress:--imp--0x10f10efb0
 --注冊(cè)之前--
 --注冊(cè)之后--
 --sel--setName:--imp--0x7fff25721c7a
 --sel--class--imp--0x7fff2572073d
 --sel--dealloc--imp--0x7fff257204a2
 --sel--_isKVOA--imp--0x7fff2572049a

從以上結(jié)果可以看出,在注冊(cè)之前瘦赫,屬性name和實(shí)例變量ivarName的區(qū)別就是沒有setter/getter辰晕。另外,在注冊(cè)之后确虱,又打印出來了setName方法含友,NSKVONotifying_TPerson作為TPerson的子類,如果還有setName方法的話校辩,說明NSKVONotifying_TPerson窘问,重寫了父類的setName的方法,也就是中間類重寫了原來類的setter宜咒。

既然中間類NSKVONotifying_TPerson重寫了dealloc方法惠赫,那么它肯定是在該方法里做了某些處理,我們?cè)谟^察者的dealloc方法里故黑,移除觀察者的之后儿咱,加入以下方法:

NSLog(@"--移除之后--%s--", object_getClassName(self.person));

運(yùn)行程序,控制臺(tái)輸出:

--移除之后--TPerson--

可以看出倍阐,移除觀察者之后概疆,被觀察對(duì)象的isa指針會(huì)指回到原來的A類。

總結(jié)

KVO是基于KVC的一種機(jī)制峰搪,通過觀察回調(diào)岔冀,可用于不同層面的通信。其使用步驟如下:

    1. 注冊(cè)觀察者
    1. 接受更改回調(diào)
    1. 移除觀察者

KVO原理

  1. 動(dòng)態(tài)生成了一個(gè)中間類,該類是被觀察對(duì)象的類的子類使套,類名是NSKVONotifying_A罐呼,被觀察對(duì)象Aisa指針從指向原來的A類,被KVO機(jī)制修改為指向NSKVONotifying_A
  2. 觀察的是中間類NSKVONotifying_A重寫的setter方法
  3. 中間類NSKVONotifying_A重寫了很多方法:
    • setter: 發(fā)送改變通知
    • class: 返回被觀察對(duì)象的類
    • dealloc: 釋放相關(guān)方法
    • _isKVOA: 標(biāo)志是生成的中間類
  4. 移除觀察的之后, 被觀察對(duì)象的isa指針會(huì)指回到原來的A
  5. 移除觀察之后中間類NSKVONotifying_A不會(huì)銷毀

參考文獻(xiàn): Aplle官方文檔

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侦高,一起剝皮案震驚了整個(gè)濱河市嫉柴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奉呛,老刑警劉巖计螺,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瞧壮,居然都是意外死亡登馒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門咆槽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陈轿,“玉大人,你說我怎么就攤上這事秦忿÷笊洌” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵灯谣,是天一觀的道長(zhǎng)潜秋。 經(jīng)常有香客問我,道長(zhǎng)胎许,這世上最難降的妖魔是什么半等? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮呐萨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘莽囤。我一直安慰自己谬擦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布朽缎。 她就那樣靜靜地躺著惨远,像睡著了一般。 火紅的嫁衣襯著肌膚如雪话肖。 梳的紋絲不亂的頭發(fā)上北秽,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音最筒,去河邊找鬼贺氓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛床蜘,可吹牛的內(nèi)容都是我干的辙培。 我是一名探鬼主播蔑水,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼扬蕊!你這毒婦竟也來了搀别?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤尾抑,失蹤者是張志新(化名)和其女友劉穎歇父,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體再愈,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榜苫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了践磅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片单刁。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖府适,靈堂內(nèi)的尸體忽然破棺而出羔飞,到底是詐尸還是另有隱情,我是刑警寧澤檐春,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布逻淌,位于F島的核電站,受9級(jí)特大地震影響疟暖,放射性物質(zhì)發(fā)生泄漏卡儒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一俐巴、第九天 我趴在偏房一處隱蔽的房頂上張望骨望。 院中可真熱鬧,春花似錦欣舵、人聲如沸擎鸠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)劣光。三九已至,卻和暖如春糟把,著一層夾襖步出監(jiān)牢的瞬間绢涡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工遣疯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雄可,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像滞项,于是被迫代替她去往敵國(guó)和親狭归。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

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

  • kvo全稱Key-Value-Observing文判,俗稱“鍵值監(jiān)聽”过椎,可以用于對(duì)對(duì)象的某個(gè)屬性值的監(jiān)聽。kvo是Ob...
    Devbrave閱讀 442評(píng)論 0 0
  • 導(dǎo)語: KVO全稱Key Value Observing戏仓,直譯為鍵值觀察疚宇。KVO 作為 iOS 中一種強(qiáng)大并且有效...
    xianminxiao閱讀 1,115評(píng)論 0 2
  • 1、KVO的基本使用 定義:KVO的全稱是Key-Value-Observing赏殃,俗稱“鍵值監(jiān)聽”敷待,可以用于監(jiān)聽某...
    Jerky_Guo閱讀 1,730評(píng)論 0 5
  • KVO是iOS開發(fā)中常用的不同類之間通信的技術(shù),叫做鍵值觀察仁热,跟通知NSNotifacation一樣是榜揖,可以一對(duì)多...
    huxinwen閱讀 1,234評(píng)論 0 2
  • 感覺很久都沒有寫新文章了,還是寫一些小文章吧抗蠢,記錄一下也好须肆!這一次和大家講解的是KVO -- key value ...
    chouson_chan閱讀 364評(píng)論 0 1