最不花里胡哨的KVO博客,一看就懂那種

hi,I’m shuhuan,我的技術(shù)博客風(fēng)格:簡(jiǎn)約、易理解,不花哨,花最少的時(shí)間掌握理解更多的知識(shí)點(diǎn)!

文章開(kāi)篇,先介紹下本篇博客主要介紹的內(nèi)容:

目錄

  • 什么是kvo
  • kvo的使用場(chǎng)景
  • kvo的使用方法
  • kvo的底層原理
  • kvo如何簡(jiǎn)單添加對(duì)多個(gè)屬性的監(jiān)聽(tīng)
  • kvo使用需要注意的坑
  • …… 持續(xù)更新
  1. 什么是KVO?
    kvo其實(shí)是我們iOS開(kāi)發(fā)中,蘋(píng)果提供給我們的一個(gè)特別重要而且常用的核心技術(shù),只是大多數(shù)簡(jiǎn)單業(yè)務(wù)開(kāi)發(fā)的時(shí)候可能并不常用,但是但凡涉及到代碼庫(kù)的封裝,或者工具類的封裝,稍微高層次點(diǎn)的開(kāi)發(fā),就會(huì)涉及到kvo,那么kvo是什么?

kvo 其實(shí)就是: k - v - o

看到這有木有想打我?
別著急,往下看,老鐵

K

k就是Key,我們開(kāi)發(fā)再常用不過(guò)的小可愛(ài),字典、hash、通知、CAAnimation等,到處都用,我們把它理解成一個(gè)“唯一標(biāo)識(shí)”,用一個(gè)標(biāo)識(shí),去替代它的身份,方便在它藏身的地方找到它

舉個(gè)栗子:
我們熟知的 “工號(hào)9527”的那個(gè)錄音笑話,您好,我是9527,很高興為您服務(wù)……
也就是說(shuō)這個(gè)9527,在客服的系統(tǒng)里,它是代表一個(gè)人,這個(gè)人比如叫:掃地僧,掃地僧是他的真實(shí)姓名,每個(gè)客服人員都有自己對(duì)應(yīng)的被投訴的記錄record,每被投訴一次,record就+1,以下要用到,但是他們對(duì)外服務(wù)并不使用自己的名字,多方面原因:方便客服系統(tǒng)管理、方便對(duì)外簡(jiǎn)易稱呼(客戶才不會(huì)動(dòng)腦筋去記住那么多客服的名字)、客服人員的隱私等等,那么這個(gè) 9527 就是 這個(gè)掃地僧的“ Key”,這個(gè)key要保證在整個(gè)客服系統(tǒng)里面是唯一的,不然客戶投訴,客服系統(tǒng)有兩個(gè)9527,那該找誰(shuí)呢,往下就不用我繼續(xù)說(shuō)了,key就是一個(gè)別名,但唯一,方便容器管理和查找。

V

v就是Value,就是值的意思,每個(gè)key對(duì)應(yīng)一個(gè)Value,Value可以根據(jù)需求和場(chǎng)景選擇類型:int、NSNumber、NSString、id等等
kvo模式里value就是我們某個(gè)類的某個(gè)屬性
以上的例子中,掃地僧的record,便是Value了,所以有如下對(duì)應(yīng)關(guān)系:
key: 9527 -- Value: record

O

o就是Observing(觀察者),好了,到這,基本就有kvo的概念的雛形了
那么這個(gè)O,便是我上面例子中說(shuō)的這個(gè)投訴的客戶,他要監(jiān)管或者能實(shí)時(shí)知曉這個(gè)工號(hào)(Key)為9527的員工掃地僧的record(Value),看看自己的投訴到底生效沒(méi),實(shí)時(shí)知曉這個(gè)詞用的簡(jiǎn)直太靈性了

KVO概念總結(jié):客戶投訴Key為:9527這位客服,他要“實(shí)時(shí)知曉”9527的value:record是否真的改變,record的改變,客服系統(tǒng)會(huì)實(shí)時(shí)通知到這位客戶系統(tǒng)對(duì)9527的record的判罰情況,這就是kvo的概念,一句話:observer對(duì)一個(gè)Key的Value的改變的實(shí)時(shí)監(jiān)聽(tīng),
也就是說(shuō):kvo是“鍵值監(jiān)聽(tīng)”,可以用于監(jiān)聽(tīng)某個(gè)對(duì)象屬性值的改變

注:我以上舉的例子可能不太完全恰當(dāng),只是當(dāng)時(shí)腦子第一時(shí)間想到9527的笑話,想著讓讀者更加容易理解,所以,較真的寶貝們不要和我較真就好了,謝謝理解!

  1. KVO的使用場(chǎng)景
    說(shuō)起使用場(chǎng)景,這個(gè)味道簡(jiǎn)直太濃太上頭了,那么既然kvo這么神奇,那么我們?cè)陂_(kāi)發(fā)中,什么時(shí)候才會(huì)使用kvo呢?
    這個(gè)主要看自己開(kāi)發(fā)中的業(yè)務(wù)場(chǎng)景,比如我們要在某個(gè)類A,去監(jiān)聽(tīng)引用進(jìn)來(lái)的類B的某個(gè)屬性值的變化,這個(gè)屬性的變化改變后我們需要A類做出一些反應(yīng)。例如Model類B的age屬性,我們初始化在類A的lable上顯示了這個(gè)age為10,結(jié)果這個(gè)age在某些時(shí)候改變了,那么用KVO監(jiān)聽(tīng)這個(gè)age的變化,A作為監(jiān)聽(tīng)者observer,就可以隨時(shí)知道age的改變從而讓A類的那個(gè)lable快速直接做出變化,而不用去A.lable.text =xxx重新賦值
    比如:監(jiān)聽(tīng)scrollView的contentOffset屬性,來(lái)完成用戶滾動(dòng)時(shí)動(dòng)態(tài)改變某些控件的屬性實(shí)現(xiàn)效果,包括漸變導(dǎo)航欄、下拉刷新控件等效果。
    除此之外KVO還可以監(jiān)聽(tīng)更多業(yè)務(wù)場(chǎng)景,在這里就不多說(shuō)

  2. KVO的使用

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p1 = [[Person alloc] init];
    p1.age = 2;
    // self 監(jiān)聽(tīng) p1的 age屬性,這里的self我們就可以理解為上述例子中的“客戶”
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;

    [p1 addObserver:self forKeyPath:@"age" options:options context:nil];
    p1.age = 10;
    [p1 removeObserver:self forKeyPath:@"age"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"監(jiān)聽(tīng)到%@的%@改變了%@", object, keyPath,change);
}

// 打印內(nèi)容
監(jiān)聽(tīng)到<Person: 0x604000205460>的age改變了{(lán)
    kind = 1;
    new = 10;
    old = 2;
}

上面代碼中可以看到,在給p1對(duì)象添加監(jiān)聽(tīng)之后,age屬性的值在發(fā)生改變時(shí),就會(huì)通知到監(jiān)聽(tīng)者,執(zhí)行監(jiān)聽(tīng)者的observeValueForKeyPath方法,當(dāng)然這里我們是在同一個(gè)類里做的改變值演示,節(jié)目效果目的,真正使用kvo多數(shù)情況是跨類改變和監(jiān)聽(tīng)的

參數(shù)和方法概念介紹:

  • NSKeyValueObservingOptions:監(jiān)聽(tīng)策略,以上代碼為新值和舊值都監(jiān)聽(tīng)
  • removeObserver: 記得監(jiān)聽(tīng)完事一定要移除
  • (void)observeValueForKeyPath: 值改變的回調(diào)方法
  • change:監(jiān)聽(tīng)對(duì)象的具體值情況

4.KVO的底層原理

  • 簡(jiǎn)述原理:上述p1對(duì)象添加addObserver操作之后,p1對(duì)象的isa指針由之前的指向類對(duì)象Person變?yōu)橹赶蛞粋€(gè)通過(guò)Runtime動(dòng)態(tài)創(chuàng)建的子類NSKVONotifyin_Person類對(duì)象,該子類擁有(重寫(xiě))自己的set方法實(shí)現(xiàn),set方法實(shí)現(xiàn)內(nèi)部會(huì)順序調(diào)用willChangeValueForKey方法、然后調(diào)用原來(lái)(父類)的setter方法實(shí)現(xiàn)、didChangeValueForKey方法,而didChangeValueForKey方法內(nèi)部又會(huì)調(diào)用監(jiān)聽(tīng)器的observeValueForKeyPath:ofObject:change:context:監(jiān)聽(tīng)方法。也就是說(shuō)一旦p1對(duì)象添加了KVO監(jiān)聽(tīng)以后,其isa指針就會(huì)發(fā)生變化,因此set方法的執(zhí)行效果就不一樣了。

  • NSKVONotifyin_Person內(nèi)部調(diào)用邏輯
    NSKVONotifyin_Person中的setage方法中其實(shí)調(diào)用了 Fundation框架中C語(yǔ)言函數(shù) _NSsetIntValueAndNotify,_NSsetIntValueAndNotify內(nèi)部做的操作相當(dāng)于,首先調(diào)用willChangeValueForKey 將要改變方法,之后調(diào)用父類的setage方法對(duì)成員變量賦值,最后調(diào)用didChangeValueForKey已經(jīng)改變方法。didChangeValueForKey中會(huì)調(diào)用監(jiān)聽(tīng)器的監(jiān)聽(tīng)方法,最終來(lái)到監(jiān)聽(tīng)者的observeValueForKeyPath方法中。

  • NSKVONotifyin_Person類重寫(xiě)方法的目的和實(shí)現(xiàn)

重寫(xiě)的方法有:
+(Class)class
-(void)setAge:(int)age:

這里NSKVONotifyin_Person重寫(xiě)class方法是為了隱藏NSKVONotifyin_Person。不被外界所看到。我們?cè)趐1添加過(guò)KVO監(jiān)聽(tīng)之后,打印p1對(duì)象的class可以發(fā)現(xiàn)返回Person

如何手動(dòng)觸發(fā)KVO?
被監(jiān)聽(tīng)的屬性的值被修改時(shí),就會(huì)自動(dòng)觸發(fā)KVO。如果想要手動(dòng)觸發(fā)KVO,則需要我們自己調(diào)用willChangeValueForKey和didChangeValueForKey方法即可在不改變屬性值的情況下手動(dòng)觸發(fā)KVO,并且這兩個(gè)方法缺一不可。

5.kvo如何添加多個(gè)屬性的監(jiān)聽(tīng)
思考下,如果我們要同時(shí)對(duì)多個(gè)屬性進(jìn)行監(jiān)聽(tīng),那么我們就需要添加N多個(gè)addObserver么?
例如我們涉及到屬性嵌套,Person類里面有個(gè)Dog類屬性,Dog類有age和level屬性需要監(jiān)聽(tīng)
當(dāng)然不用!這就是我這節(jié)要講的內(nèi)容,往下看:

場(chǎng)景:Person類繼承NSObject,Dog類集成Person類,有name、age、level等屬性,在VC中添加對(duì)person.dog的屬性kvo觀察,如果dog的屬性太多,無(wú)需多次添加addobserver進(jìn)行重復(fù)代碼,此時(shí)可調(diào)用一個(gè)函數(shù):

-  (NSSet<NSString  *>  *)keyPathForvaluesAffectingValueForKey:(NSString  *)key{
    NSSet * keyPaths =[super keyPathForvaluesAffectingValueForKey:key];
    if ([key isEqualToString:@"dog"]) {
        keyPaths =[[NSSet alloc]initWithObjects:@"_dog.age",@"_dog.level", nil];
    }
    return keyPaths;
}

在這個(gè)函數(shù)里面做屬性的判斷和處理,返回一個(gè)NSSet集合,在監(jiān)聽(tīng)的地方,直接對(duì)“dog”監(jiān)聽(tīng)就可以了

6.KVO使用需要注意什么

1.使用完了記得要移除,不然會(huì)引起crash
2.key的容錯(cuò)處理
如果用“key”的形式寫(xiě)key很容易出錯(cuò),這里給大家說(shuō)一個(gè)小技巧:
我們?cè)趯?xiě)keypath的時(shí)候使用下面的方法去寫(xiě),能規(guī)避一些錯(cuò)誤
NSStringFromSelector(@selector(age))

個(gè)人宣傳廣告位:

Github:shLuckySeven

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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