KVO原理分析

1、KVO簡介

KVO官方簡介

KVO是鍵值觀察Key-Value Observing的簡稱轨功,在iOS開發(fā)中趁舀,可使用KVO來監(jiān)聽一個對象屬性的變化。

2、KVO的使用

2.1對某個類添加監(jiān)聽

//content后面填NULL不要填nil,官方介紹的,OC是C的超集
[self.person addObserver:self forKeyPath:@"nick" options:(NSKeyValueObservingOptionNew) context:NULL];

1.self.person是LGPerson的實例對象
2.給LGPerson的實例對象self.person添加觀察者self,監(jiān)聽self.person的屬性nick的新值變化(NSKeyValueObservingOptionNew)

context的使用:

static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;

- (void)registerAsObserverForAccount:(Account*)account {
    [account addObserver:self
              forKeyPath:@"balance"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                 context:PersonAccountBalanceContext];
 
    [account addObserver:self
              forKeyPath:@"interestRate"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                  context:PersonAccountInterestRateContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
 
    if (context == PersonAccountBalanceContext) {
        // Do something with the balance…
 
    } else if (context == PersonAccountInterestRateContext) {
        // Do something with the interest rate…
 
    } else {
        // Any unrecognized context must belong to super
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                               context:context];
    }
}

2.2觀察者的回調(diào)方法焚刚。

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

當(dāng)觀察者的屬性變化時,會來到這個回調(diào)方法扇调,keyPath是被監(jiān)聽的屬性,object是被觀察的對象抢肛,change是數(shù)據(jù)的變化狼钮。

2.3移除觀察者

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

2.4數(shù)組類型的屬性監(jiān)聽。

self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
    // 5: 可變數(shù)組 KVO -- 
    [self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];

// KVO 建立在 KVC
    [[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"2"];

對象的屬性-數(shù)組捡絮,需要使用KVC的形式賦值才能監(jiān)聽

2.5多個因素綜合影響

// 4: 多個因素影響 - 下載進(jìn)度 = 當(dāng)前下載量 / 總量
    [self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];

#import "LGPerson.h"

@implementation LGPerson

// 下載進(jìn)度 -- writtenData/totalData

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

2.6KVO開關(guān)

// 自動開關(guān)
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    return YES;
}
//單個屬性設(shè)置關(guān)閉
+ (BOOL) automaticallyNotifiesObserversForNick:(NSString *)nick{
    return YES;
}


- (void)setNick:(NSString *)nick{
    [self willChangeValueForKey:@"nick"];
    _nick = nick;
    [self didChangeValueForKey:@"nick"];
}

當(dāng)上面的方法返回NO熬芜,整個類的監(jiān)聽都被關(guān)掉,反之福稳,返回YES涎拉,就被打開。

2.7使用集合類型字典等

/* Possible values in the NSKeyValueChangeKindKey entry in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information.
*/
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};

/* Possible kinds of set mutation for use with -willChangeValueForKey:withSetMutation:usingObjects: and -didChangeValueForKey:withSetMutation:usingObjects:. Their semantics correspond exactly to NSMutableSet's -unionSet:, -minusSet:, -intersectSet:, and -setSet: method, respectively.
*/
typedef NS_ENUM(NSUInteger, NSKeyValueSetMutationKind) {
    NSKeyValueUnionSetMutation = 1,
    NSKeyValueMinusSetMutation = 2,
    NSKeyValueIntersectSetMutation = 3,
    NSKeyValueSetSetMutation = 4
};

3的圆、KVO的底層原理

3.1添加監(jiān)聽的處理

實現(xiàn)以下方法打印類及其子類

#pragma mark - 遍歷類以及子類
- (void)printClasses:(Class)cls{
    
    // 注冊類的總數(shù)
    int count = objc_getClassList(NULL, 0);
    // 創(chuàng)建一個數(shù)組鼓拧, 其中包含給定對象
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    // 獲取所有已注冊的類
    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);
}

實現(xiàn)以下方法打印類的方法列表

#pragma mark - 遍歷方法-ivar-property
- (void)printClassAllMethod:(Class)cls{
    NSLog(@"*********************");
    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(@"%@-%p",NSStringFromSelector(sel),imp);
    }
    free(methodList);
}
image.png

通過監(jiān)聽前后對LGPerson及其子類的列表的打印,我們發(fā)現(xiàn)越妈,當(dāng)對季俩,LGPerson的對象監(jiān)聽后,系統(tǒng)自動生成了一個LGPerson的子類NSKVONotifying_LGPerson梅掠。再來看看NSKVONotifying_LGPerson的方法列表酌住。

image.png

從上圖可以得知店归,NSKVONotifying_LGPerson重寫了父類的setNickName方法。

note:注意只有監(jiān)聽屬性才有生成set方法酪我,監(jiān)聽成員變量是不會生成的消痛,比如監(jiān)聽name.

[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject{
    @public
    NSString *name;
}
@property (nonatomic, copy) NSString *nickName;


- (void)sayHello;
- (void)sayLove;

@end

再來看看self.person 的isa指向。


image.png

可以看到self.person的isa指向都哭,有LGPerson變成了NSKVONotifying_LGPerson.
通過上圖可以分析得出:當(dāng)對一個對象的屬性進(jìn)行監(jiān)聽的時候秩伞,會生成對前對象所屬類的子類NSKVONotifying_XXX,并將這個對象的isa指向這個派生類。在這個派生類中质涛,重寫當(dāng)前屬性的setter方法稠歉。還重寫了class方法,[self.person class];返回的是LGPerson汇陆。還實現(xiàn)了一個isKVO的標(biāo)記怒炸,標(biāo)記當(dāng)前類實現(xiàn)監(jiān)聽的派生類。

3.2監(jiān)聽的銷毀

image.png

1毡代、從上圖可知阅羹,銷毀監(jiān)聽后,派生類NSKVONotifying_LGPerson依然存在教寂,這樣下次監(jiān)聽不需要重復(fù)創(chuàng)建了捏鱼。
2、銷毀后酪耕,對象的isa指針又指回了LGPerson.
對象銷毀后导梆,isa指回來,保證不出現(xiàn)混亂迂烁。

謹(jǐn)記:一定要移除觀察看尼,在使用RAC的時候要手動接受(dispose)并在dealloc中釋放掉。
如果觀察沒有移除會出現(xiàn)野指針的情況盟步。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末藏斩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子却盘,更是在濱河造成了極大的恐慌狰域,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黄橘,死亡現(xiàn)場離奇詭異兆览,居然都是意外死亡,警方通過查閱死者的電腦和手機旬陡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門拓颓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人描孟,你說我怎么就攤上這事驶睦∨樽螅” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵场航,是天一觀的道長缠导。 經(jīng)常有香客問我,道長溉痢,這世上最難降的妖魔是什么僻造? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮孩饼,結(jié)果婚禮上髓削,老公的妹妹穿的比我還像新娘。我一直安慰自己镀娶,他們只是感情好立膛,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梯码,像睡著了一般宝泵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上轩娶,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天儿奶,我揣著相機與錄音,去河邊找鬼鳄抒。 笑死闯捎,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的许溅。 我是一名探鬼主播隙券,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼闹司!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沐飘,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤游桩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耐朴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體借卧,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年筛峭,在試婚紗的時候發(fā)現(xiàn)自己被綠了铐刘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡影晓,死狀恐怖镰吵,靈堂內(nèi)的尸體忽然破棺而出檩禾,到底是詐尸還是另有隱情,我是刑警寧澤疤祭,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布盼产,位于F島的核電站,受9級特大地震影響勺馆,放射性物質(zhì)發(fā)生泄漏戏售。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一草穆、第九天 我趴在偏房一處隱蔽的房頂上張望灌灾。 院中可真熱鬧,春花似錦悲柱、人聲如沸锋喜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跑芳。三九已至,卻和暖如春直颅,著一層夾襖步出監(jiān)牢的瞬間博个,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工功偿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盆佣,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓械荷,卻偏偏與公主長得像共耍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吨瞎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

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

  • 一痹兜、KVO底層實現(xiàn)原理 示例代碼: KVO 的實現(xiàn)過程實際上是利用了 OC 的 runtime 機制,當(dāng)一個實例對...
    奉灬孝閱讀 327評論 0 1
  • KVO 全稱:Key-Value observing, 鍵值觀察 崖叫,KVO是一種機制遗淳,它允許將其他對象的指定屬...
    ChenL閱讀 526評論 0 0
  • 前言 上一篇文章學(xué)習(xí)了KVC的原理(鍵值編碼),KVC是由NSKeyValueCoding非正式協(xié)議啟用的一種機制...
    冼同學(xué)閱讀 647評論 0 3
  • KVO的使用 KVO使用的三部曲:添加觀察者心傀、接受回調(diào)屈暗、移除觀察者;1、為什么要移除觀察者呢养叛?如果不移除會造成什么...
    半邊楓葉閱讀 777評論 0 5
  • 概述 KVO全程KeyValueObserving种呐,是蘋果提供的一套鍵值觀察機制,它可以在對象指定屬性發(fā)生改變時接...
    Irino閱讀 248評論 0 1