iOS 關(guān)于KVO的一些總結(jié)

本文參考鏈接:

iOS KVO詳解

Foundation: NSKeyValueObserving(KVO)

KVO原理分析及使用進(jìn)階


概述

KVO是基于觀察者模式來(lái)實(shí)現(xiàn)的。

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

KVO全稱為Key-Value Observing,是Foundation框架提供的一種機(jī)制,使用KVO,可以方便地對(duì)指定對(duì)象的某個(gè)屬性進(jìn)行觀察當(dāng)屬性發(fā)生變化時(shí),進(jìn)行通知****。

使用KVO只需要兩個(gè)步驟

  1. 注冊(cè)O(shè)bserver;

  2. 接收通知。

1、注冊(cè)O(shè)bserver

使用下面方法注冊(cè)O(shè)bserver

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

即:

addObserver:forKeyPath:options:context:

參數(shù)含義:

  • observer:觀察者,需要響應(yīng)屬性變化的對(duì)象。該對(duì)象必須實(shí)現(xiàn) observeValueForKeyPath:ofObject:change:context: 方法。
  • keyPath:要觀察的屬性名稱。要和屬性聲明的名稱一致。
  • options:對(duì)KVO機(jī)制進(jìn)行配置,修改KVO通知的時(shí)機(jī)以及通知的內(nèi)容。
  • context:context是一個(gè)c指針,可以傳入任意類型的對(duì)象,在觀察者接收通知回調(diào)的方法 observeValueForKeyPath:ofObject:change:context: 中可以接收到這個(gè)對(duì)象,是KVO中的一種傳值方式這個(gè)參數(shù)可以用來(lái)區(qū)分同一對(duì)象對(duì)同一個(gè)屬性的多個(gè)不同的監(jiān)聽(tīng)。
  1. 注意:分清觀察者對(duì)象和目標(biāo)對(duì)象,調(diào)用 addObserver:forKeyPath:options:context: 方法的對(duì)象是目標(biāo)對(duì)象,observer是觀察者對(duì)象,keyPath是目標(biāo)對(duì)象的屬性。

  2. 注意,在注冊(cè)了Observer后,一定要在合適時(shí)機(jī)移除注冊(cè),否則會(huì)crash。移除注冊(cè)的兩種方法:

- (void)removeObserver:(NSObject *)anObserver
            forKeyPath:(NSString *)keyPath

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

蘋果官方推薦的方式是,在init的時(shí)候進(jìn)行addObserver,在dealloc時(shí)removeObserver,這樣可以保證add和remove是成對(duì)出現(xiàn)的,是一種比較理想的使用方式。

第二種方法帶有context屬性,主要是用來(lái)區(qū)分不同的觀察者Observer的。

如果observer沒(méi)有監(jiān)聽(tīng)keyPath屬性,則調(diào)用這兩個(gè)方法會(huì)拋出異常并崩潰。所以,必須確保先注冊(cè)了觀察者,才能調(diào)用移除方法。。實(shí)際上,在添加觀察者的時(shí)候,觀察者對(duì)象與被觀察屬性所屬的對(duì)象都不會(huì)被retain,然而在這些對(duì)象被釋放后,相關(guān)的監(jiān)聽(tīng)信息卻還存在,KVO做的處理是直接讓程序崩潰。

  1. options參數(shù)是一個(gè)枚舉類型,共有四種取值方式:
enum {
NSKeyValueObservingOptionNew = 0x01, //新值
NSKeyValueObservingOptionOld = 0x02, //舊值
NSKeyValueObservingOptionInitial = 0x04, //
NSKeyValueObservingOptionPrior = 0x08
};
  • NSKeyValueObservingOptionNew:接收方法中使用change參數(shù)傳入變化后的新值,鍵為:NSKeyValueChangeNewKey;

  • NSKeyValueObservingOptionOld:接收方法中使用change參數(shù)傳入變化前的舊值,鍵為:NSKeyValueChangeOldKey;

  • NSKeyValueObservingOptionInitial:注冊(cè)之后立即調(diào)用一次接收方法。如果還如果配置了NSKeyValueObservingOptionNew,change參數(shù)內(nèi)容會(huì)包含新值,鍵為:NSKeyValueChangeNewKey。

  • NSKeyValueObservingOptionPrior:如果加入這個(gè)參數(shù),接收方法會(huì)在變化前后分別調(diào)用一次,共兩次,變化前的通知change參數(shù)包含notificationIsPrior = 1。其他內(nèi)容根據(jù)NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld的配置確定。

  1. 注意:options參數(shù)可以配置多個(gè),如:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld,使用 | 或運(yùn)算符連接。

  2. 調(diào)用addObserver:forKeyPath:options:context:方法時(shí),觀察者對(duì)象與被觀察屬性所屬的對(duì)象都不會(huì)被retain,也就是說(shuō),引用計(jì)數(shù)不會(huì)加1。

  3. 可以重復(fù)添加監(jiān)聽(tīng):可以多次調(diào)用addObserver:..方法,將同一對(duì)象注冊(cè)為同一屬性的的觀察者(參數(shù)可以完全相同,可以使用context參數(shù)進(jìn)行區(qū)分)。這些觀察者會(huì)并存。

2、接收通知

當(dāng)被監(jiān)聽(tīng)的屬性的值發(fā)生變化時(shí),KVO會(huì)自動(dòng)通知注冊(cè)了的觀察者

上文提到,觀察者必須實(shí)現(xiàn)以下方法,這個(gè)方法就是觀察者接收通知的方法

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

參數(shù):

  • object:目標(biāo)對(duì)象,即所監(jiān)聽(tīng)的對(duì)象,也就是所監(jiān)聽(tīng)的屬性所屬的對(duì)象。

  • change:是傳入的變化量,通過(guò)在注冊(cè)時(shí)用options參數(shù)進(jìn)行的配置,會(huì)包含不同的內(nèi)容。

  1. change參數(shù)

除了根據(jù)options參數(shù)控制的change參數(shù)內(nèi)容,默認(rèn)change參數(shù)會(huì)包含一個(gè)NSKeyValueChangeKindKey鍵值對(duì),傳遞被監(jiān)聽(tīng)屬性的變化類型

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};
  • NSKeyValueChangeSetting:屬性的值被重新設(shè)置;

  • NSKeyValueChangeInsertion、NSKeyValueChangeRemoval、NSKeyValueChangeReplacement:表示更改的是集合屬性,分別代表插入、刪除、替換操作。

  • 如果NSKeyValueChangeKindKey參數(shù)是針對(duì)集合屬性的三個(gè)之一,change參數(shù)還會(huì)包含一個(gè)NSKeyValueChangeIndexesKey鍵值對(duì),表示變化的index。

  1. chang字典里,新值的key為“new”,舊值的key為“old”,變化類型的key為“kind”。
3、示例

注意,KVO的運(yùn)行是通過(guò)重寫setter方法來(lái)觸發(fā)通知機(jī)制的,也就是說(shuō),如果你直接賦值給實(shí)例變量而不是使用屬性賦值的話,是不會(huì)觸發(fā)KVO的。也就是說(shuō),下面的self.str如果換成了_str是無(wú)效的,因?yàn)閟elf.str賦值時(shí)調(diào)用了setter方法。但是使用KVC來(lái)給實(shí)例變量賦值,會(huì)觸發(fā)KVO。這點(diǎn)下面會(huì)詳細(xì)說(shuō)明。

#import "ViewController.h"

@interface ViewController ()

@property (copy, nonatomic) NSString *str;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.str = @"1111111";
    
    [self addObserver:self forKeyPath:@"str" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:nil];
    
    self.str = @"2222222";
    self.str = @"3333333";

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    for (NSString  *key in change) {
        NSLog(@"%@",change[key]);
    }
    
}

- (void)dealloc{
    
    [self removeObserver:self forKeyPath:@"str"];
    
}

@end

4、自動(dòng)通知和手動(dòng)通知

上面提到,KVO默認(rèn)會(huì)自動(dòng)通知觀察者。取消自動(dòng)通知的方法是實(shí)現(xiàn)下面的類方法,通過(guò)返回NO來(lái)取消自動(dòng)通知。

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key

系統(tǒng)也會(huì)單獨(dú)針對(duì)這個(gè)屬性自動(dòng)生成相關(guān)的類方法,是否自動(dòng)通知這個(gè)屬性被改變,也可以單獨(dú)重寫這個(gè)類方法:

//假如有一個(gè)屬性
@property (copy, nonatomic) NSString *str;

//則系統(tǒng)會(huì)自動(dòng)生成關(guān)于這個(gè)屬性的類方法,是否自動(dòng)通知
+ (BOOL)automaticallyNotifiesObserversOfTest;

針對(duì)非自動(dòng)通知的屬性,可以分別在變化之前和之后手動(dòng)調(diào)用如下方法(will在前,did在后)來(lái)手動(dòng)通知觀察者:

- (will/did)ChangeValueForKey:
- (will/did)ChangeValueForKey:withSetMutation:usingObjects:
- (will/did)Change:valuesAtIndexes:forKey:

手動(dòng)通知的好處就是,可以靈活加上自己想要的判斷條件,事實(shí)上自動(dòng)通知也是框架通過(guò)調(diào)用這些方法實(shí)現(xiàn)的。

需要注意的是,對(duì)于對(duì)象中其它沒(méi)有處理的屬性,我們需要調(diào)用[super automaticallyNotifiesObserversForKey:key],以避免無(wú)意中修改了父類的屬性的處理方式。

下面的代碼是在setter方法中使用 -(will/did)ChangeValueForKey: 方法加上了KVO的通知,如果是在 setter 方法之外改變了實(shí)例變量,且希望這種修改被觀察者監(jiān)聽(tīng)到,則需要像在setter方法里面做一樣的處理。

@interface ViewController ()
@property (copy, nonatomic) NSString *str;
@end

@implementation ViewController

- (void)setStr:(NSString *)str{
    
    [self willChangeValueForKey:@"str"];
    
    _str = [str copy];
    
    [self didChangeValueForKey:@"str"];
    
}

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

//也可以是以下方法:
//+ (BOOL)automaticallyNotifiesObserversOfStr{
//
//    return NO;
//
//}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.str = @"1111111";
    
    [self addObserver:self forKeyPath:@"str" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:nil];
    
    self.str = @"2222222";
    self.str = @"3333333";

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    for (NSString  *key in change) {
        NSLog(@"%@",key);
        NSLog(@"%@",change[key]);
    }
    
}

- (void)dealloc{
    
    [self removeObserver:self forKeyPath:@"str"];
    
}

@end

5、KVO實(shí)現(xiàn)原理

KVO的實(shí)現(xiàn)是基于runtime運(yùn)行時(shí)機(jī)制的,下面就來(lái)詳細(xì)介紹一下原理,如下圖:

KVO實(shí)現(xiàn)原理
  1. 當(dāng)某個(gè)類的對(duì)象第一次被觀察時(shí),系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類,在這個(gè)派生類中重寫****基類中任何**被觀察屬性的 **setter 方法****。
  2. 派生類在被重寫的 setter 方法中實(shí)現(xiàn)真正的通知機(jī)制就如前面手動(dòng)實(shí)現(xiàn)鍵值觀察那樣。這么做是基于設(shè)置屬性會(huì)調(diào)用 setter 方法,而通過(guò)重寫就獲得了 KVO 需要的通知機(jī)制。當(dāng)然前提是要通過(guò)遵循 KVO 的屬性設(shè)置方式來(lái)變更屬性值,如果僅是直接修改屬性對(duì)應(yīng)的成員變量,是無(wú)法實(shí)現(xiàn) KVO 的。
  3. 同時(shí)派生類還重寫了 class 方法以“欺騙”外部調(diào)用者它就是起初的那個(gè)類。然后系統(tǒng)將這個(gè)對(duì)象的 isa 指針指向這個(gè)新誕生的派生類,因此這個(gè)對(duì)象就成為該派生類的對(duì)象了,因而在該對(duì)象上對(duì) setter 的調(diào)用就會(huì)調(diào)用重寫的 setter,從而激活鍵值通知機(jī)制。此外,派生類還重寫了 dealloc 方法來(lái)釋放資源。

概括總結(jié)一下:KVO的實(shí)現(xiàn)原理,KVO是基于Runtime機(jī)制的,當(dāng)某個(gè)類的對(duì)象第一次被觀察時(shí),在運(yùn)行時(shí)會(huì)動(dòng)態(tài)地生成一個(gè)派生類,派生類會(huì)重寫setter方法實(shí)現(xiàn)通知機(jī)制,并重寫class方法,使對(duì)象的isa指針指向該派生類,以及重寫delloc方法來(lái)釋放資源。

6. KVO 和線程

一個(gè)需要注意的地方是,KVO行為是同步的,并且是在與觀察的屬性發(fā)生變化同樣的線程上。沒(méi)有隊(duì)列或者 Run-loop 的處理。手動(dòng)或者自動(dòng)調(diào)用 -didChange... 會(huì)觸發(fā) KVO 通知。

所以,當(dāng)我們?cè)噲D從其他線程改變屬性值的時(shí)候我們應(yīng)當(dāng)十分小心,除非能確定所有的觀察者都用線程安全的方法處理 KVO 通知。通常來(lái)說(shuō),我們不推薦把 KVO 和多線程混起來(lái)。如果我們要用多個(gè)隊(duì)列和線程,我們不應(yīng)該在它們互相之間用 KVO。

KVO 是同步運(yùn)行的這個(gè)特性非常強(qiáng)大,只要我們?cè)趩我痪€程上面運(yùn)行(比如主隊(duì)列 main queue),KVO 會(huì)保證下列兩種情況的發(fā)生:

首先,如果我們調(diào)用一個(gè)支持 KVO 的 setter 方法,如下所示:

self.exchangeRate = 2.345;

KVO 能保證所有 exchangeRate 的觀察者在 setter 方法返回前被通知到

其次,如果某個(gè)鍵被觀察的時(shí)候附上了 NSKeyValueObservingOptionPrior (改變前后各調(diào)用接受方法一次) 選項(xiàng),直到 -observeValueForKeyPath... 被調(diào)用之前, exchangeRate 的 存取方法都會(huì)返回同樣的值。

7、重要注意點(diǎn)
  1. 可以通過(guò) KVO 在 Model 和 Controller 之間進(jìn)行通信。

  2. 在類的內(nèi)部,要區(qū)分 self.str 和 _str,賦值給成員變量是不會(huì)觸發(fā) KVO 回調(diào)的,因?yàn)橘x值給成員變量是不會(huì)調(diào)用 setter 方法的。KVO 是通過(guò) 重寫的setter方法來(lái)觸發(fā)的。點(diǎn)語(yǔ)法的調(diào)用是通過(guò)存取方法來(lái)訪問(wèn)的,例如:student.name = @"xds";

  3. 但是可以通過(guò) KVC 來(lái)觸發(fā) KVO 的回調(diào)函數(shù),也就是說(shuō)對(duì)成員變量可以使用 KVC 來(lái)觸發(fā) KVO。對(duì)一個(gè)實(shí)例變量調(diào)用KVC,KVC內(nèi)部主動(dòng)調(diào)用了對(duì)象的willChangeValueForKey:和didChangeValueForKey: 這兩個(gè)方法,所以會(huì)觸發(fā)KVO操作

  4. 調(diào)用KVO時(shí)需要傳入一個(gè)keyPath,由于keyPath是字符串的形式,所以其對(duì)應(yīng)的屬性發(fā)生改變后,字符串沒(méi)有改變?nèi)菀讓?dǎo)致Crash。我們可以利用系統(tǒng)的反射機(jī)制將keyPath反射出來(lái),這樣編譯器可以在@selector()中進(jìn)行合法性檢查。

NSStringFromSelector(@selector(isFinished))
  1. 觸發(fā) KVO 的三種方式
    1. 如果使用了 KVC 訪問(wèn)屬性或成員變量,如果有訪問(wèn)器方法,則運(yùn)行時(shí)會(huì)在訪問(wèn)器方法中調(diào)用 will/didChangeValueForKey: 方法;沒(méi)有訪問(wèn)器方法,運(yùn)行時(shí) 會(huì)在 setValue:forKey: 方法中調(diào)用 will/didChangeValueForKey: 方法。
    2. 直接使用了訪問(wèn)器方法(點(diǎn)語(yǔ)法),會(huì)在運(yùn)行時(shí)重寫 setter 方法,調(diào)用 will/didChangeValueForKey: 方法;
    3. 顯示調(diào)用 will/didChangeValueForKey: 方法。
8、計(jì)算屬性(注冊(cè)依賴鍵)

有時(shí)候,我們監(jiān)聽(tīng)的某個(gè)屬性可能會(huì)依賴于其它多個(gè)屬性的變化(類似于swift,可以稱之為計(jì)算屬性),不管依賴的哪個(gè)屬性發(fā)生了變化,都會(huì)導(dǎo)致計(jì)算屬性的變化。

我們首先要確定計(jì)算屬性與所依賴屬性的關(guān)系。

假如有一個(gè)Student類,其resume屬性依賴于name、age屬性。

//Student.h

@interface Student : NSObject

@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;

@property (copy, nonatomic) NSString *resume;//簡(jiǎn)歷信息
@end


//Student.m

@implementation Student

- (NSString *)resume{
    
    return [NSString stringWithFormat:@"name = %@,age = %ld",self.name,self.age];
}

@end

定義了這種依賴關(guān)系后,我們就需要以某種方式告訴KVO,當(dāng)我們的被依賴屬性name和age修改時(shí),要發(fā)送resume屬性被修改的通知。此時(shí),我們需要重寫NSKeyValueObserving協(xié)議的方法

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key

這個(gè)方法返回的是一個(gè)集合對(duì)象,包含了依賴屬性的名稱對(duì)應(yīng)的字符串。

另外,我們也可以實(shí)現(xiàn)一個(gè)命名為 keyPathsForValuesAffecting<Key> 的類方法來(lái)達(dá)到同樣的目的,其中<Key>是我們計(jì)算屬性的名稱

注意:

  • 需要注意的就是當(dāng)我們重寫+keyPathsForValuesAffectingValueForKey:時(shí),需要去調(diào)用super的對(duì)應(yīng)方法,并返回一個(gè)包含父類中可能會(huì)對(duì)key指定屬性產(chǎn)生影響的屬性集合。
  • 重寫方法是在目標(biāo)對(duì)象類里,而不是觀察者對(duì)象的類里,這里要注意。

實(shí)現(xiàn)如下:

@implementation Student

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{

    if ([key isEqualToString:@"resume"]) {

        return [NSSet setWithObjects:@"name",@"age", nil];

    }

    return [super keyPathsForValuesAffectingValueForKey:key];

}

//或者重寫下面的方法
//+ (NSSet<NSString *> *)keyPathsForValuesAffectingResume{
//
//    return [NSSet setWithObjects:@"name",@"age", nil];
//
//}

@end
//ViewController.h
- (void)viewDidLoad {
    [super viewDidLoad];

    student = [[Student alloc] init];
    
    student.name = @"xds";
    student.age = 18;
    
    [student addObserver:self forKeyPath:@"resume" options:NSKeyValueObservingOptionNew context:nil];

    student.name = @"xdsxxxx";
    student.age = 22;

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);

}

輸出如下,發(fā)現(xiàn)當(dāng)name和age改變時(shí),會(huì)通知resume屬性也被改變了

2018-08-29 11:26:56.125679+0800 KVO[1170:91206] {
    kind = 1;
    new = "name = xdsxxxx,age = 18";
}
2018-08-29 11:26:56.125984+0800 KVO[1170:91206] {
    kind = 1;
    new = "name = xdsxxxx,age = 22";
}
9、集合屬性的監(jiān)聽(tīng)
  1. 對(duì)于集合屬性(這里指NSArray和NSSet,不包括NSDictionary)的KVO,我們需要知道:對(duì)于集合屬性,只有在賦值時(shí)會(huì)觸發(fā)KVO,改變集合屬性里的元素是不會(huì)觸發(fā)KVO的(比如添加、刪除、修改元素)

當(dāng)給集合對(duì)象賦值時(shí),是可以觸發(fā)KVO的。

//Student.h
@interface Student : NSObject
@property (copy, nonatomic) NSArray *classmates;
@end

- (void)viewDidLoad {

    [super viewDidLoad];

    student = [[Student alloc] init];
    
    student.name = @"xds";
    student.age = 18;
    
    [student addObserver:self forKeyPath:@"classmates" options:NSKeyValueObservingOptionNew context:nil];

    student.classmates = [NSArray array];

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{

    NSLog(@"%@",change);

}
@end

//輸出
2018-08-29 11:57:31.909262+0800 KVO[1230:110669] {
    kind = 1;
    new =     (
    );
}
  1. 我們可以通過(guò)使用KVC的集合代理對(duì)象(collection proxy object)來(lái)處理集合相關(guān)的操作,使集合對(duì)象內(nèi)部元素改變時(shí)也能觸發(fā)KVO。

直接操作:

有序集合對(duì)應(yīng)方法如下:

-countOf<Key>
//必須實(shí)現(xiàn),對(duì)應(yīng)于NSArray的基本方法count:
-objectIn<Key>AtIndex:
-<key>AtIndexes:
//這兩個(gè)必須實(shí)現(xiàn)一個(gè),對(duì)應(yīng)于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:
-get<Key>:range:
//不是必須實(shí)現(xiàn)的,但實(shí)現(xiàn)后可以提高性能,其對(duì)應(yīng)于 NSArray 方法 getObjects:range:

-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes:
//兩個(gè)必須實(shí)現(xiàn)一個(gè),類似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:
-removeObjectFrom<Key>AtIndex:
-remove<Key>AtIndexes:
//兩個(gè)必須實(shí)現(xiàn)一個(gè),類似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:
-replaceObjectIn<Key>AtIndex:withObject:
-replace<Key>AtIndexes:with<Key>:
//可選的,如果在此類操作上有性能問(wèn)題,就需要考慮實(shí)現(xiàn)之

無(wú)序集合對(duì)應(yīng)方法如下:

-countOf<Key>
//必須實(shí)現(xiàn),對(duì)應(yīng)于NSArray的基本方法count:
-objectIn<Key>AtIndex:
-<key>AtIndexes:
//這兩個(gè)必須實(shí)現(xiàn)一個(gè),對(duì)應(yīng)于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:
-get<Key>:range:
//不是必須實(shí)現(xiàn)的,但實(shí)現(xiàn)后可以提高性能,其對(duì)應(yīng)于 NSArray 方法 getObjects:range:

-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes:
//兩個(gè)必須實(shí)現(xiàn)一個(gè),類似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:
-removeObjectFrom<Key>AtIndex:
-remove<Key>AtIndexes:
//兩個(gè)必須實(shí)現(xiàn)一個(gè),類似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:
-replaceObjectIn<Key>AtIndex:withObject:
-replace<Key>AtIndexes:with<Key>:
//這兩個(gè)都是可選的,如果在此類操作上有性能問(wèn)題,就需要考慮實(shí)現(xiàn)之
  1. 在進(jìn)行可變集合對(duì)象操作時(shí),先調(diào)用下面方法通過(guò)key或者keyPath獲取集合對(duì)象,然后再對(duì)集合對(duì)象進(jìn)行add或remove等操作時(shí),就會(huì)觸發(fā)KVO的消息通知了。這種方式屬于間接操作,是實(shí)際開(kāi)發(fā)中最常用到的。

key方法:

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

keyPath方法:

- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
例子:
student = [[Student alloc] init];

student.name = @"xds";
student.age = 18;

[student addObserver:self forKeyPath:@"classmates" options:NSKeyValueObservingOptionNew context:nil];

student.classmates = [NSMutableArray array];

NSMutableArray *array = [student mutableArrayValueForKey:@"classmates"];

[array addObject:@"4"];

輸出:
2018-08-29 12:54:51.889282+0800 KVO[1404:146332] {
    kind = 1;
    new =     (
    );
}
2018-08-29 12:54:51.889866+0800 KVO[1404:146332] {
    indexes = "<_NSCachedIndexSet: 0x604000033f80>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
    kind = 2;
    new =     (
        4
    );
}

**通過(guò) - (NSMutableArray )mutableArrayValueForKey:(NSString )key; 這個(gè)方法,我們便可以將可變數(shù)組與強(qiáng)大的KVO結(jié)合在一起。KVO機(jī)制能在集合改變的時(shí)候把詳細(xì)的變化放進(jìn)change字典中。

  1. 如果我們想到手動(dòng)控制集合屬性消息的發(fā)送,則可以使用下面幾個(gè)方法,即:
-willChange:valuesAtIndexes:forKey:
-didChange:valuesAtIndexes:forKey:
或
-willChangeValueForKey:withSetMutation:usingObjects:
-didChangeValueForKey:withSetMutation:usingObjects:

10、監(jiān)聽(tīng)信息

如果我們想獲取一個(gè)對(duì)象上有哪些觀察者正在監(jiān)聽(tīng)其屬性的修改,則可以查看對(duì)象的observationInfo屬性,其聲明如下:

@property void *observationInfo

可以看到它是一個(gè)void類型指針(就是id類型),指向一個(gè)包含所有觀察者的一個(gè)標(biāo)識(shí)信息對(duì)象,這些信息包含了每個(gè)監(jiān)聽(tīng)的觀察者,注冊(cè)時(shí)設(shè)定的選項(xiàng)等等。我們還是用示例來(lái)看看。

使用如下:

id info = student.observationInfo;

NSLog(@"%@", [info description]);

11、小結(jié)

KVO作為Objective-C中兩個(gè)對(duì)象間通信機(jī)制中的一種,提供了一種非常強(qiáng)大的機(jī)制。在經(jīng)典的MVC架構(gòu)中,控制器需要確保視圖與模型的同步,當(dāng)model對(duì)象改變時(shí),視圖應(yīng)該隨之改變以反映模型的變化;當(dāng)用戶和控制器交互的時(shí)候,模型也應(yīng)該做出相應(yīng)的改變。而KVO便為我們提供了這樣一種同步機(jī)制:我們讓控制器去監(jiān)聽(tīng)一個(gè)model對(duì)象屬性的改變,并根據(jù)這種改變來(lái)更新我們的視圖。所有,有效地使用KVO,對(duì)我們應(yīng)用的開(kāi)發(fā)意義重大。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容