IOS底層(三) KVO底層實(shí)現(xiàn)原理

@[TOC](IOS底層(三) KVO底層實(shí)現(xiàn)原理 )

一草巡,KVO簡(jiǎn)述

KVO的全稱(chēng) Key-Value Observing,俗稱(chēng)“鍵值監(jiān)聽(tīng)”山憨,可以用于監(jiān)聽(tīng)某個(gè)對(duì)象屬性值的改變郁竟。
帶著問(wèn)題探索:

  1. iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO棚亩?(KVO的本質(zhì)是什么虏杰?)
答. 當(dāng)一個(gè)對(duì)象使用了KVO監(jiān)聽(tīng)纺阔,iOS系統(tǒng)會(huì)修改這個(gè)對(duì)象的isa指針笛钝,
改為指向一個(gè)全新的通過(guò)Runtime動(dòng)態(tài)創(chuàng)建的子類(lèi)愕宋,子類(lèi)擁有自己的set方法實(shí)現(xiàn)玻靡,
set方法實(shí)現(xiàn)內(nèi)部會(huì)順序調(diào)用willChangeValueForKey方法、原來(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)方法。

  1. 如何手動(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è)方法缺一不可瘟则。
  1. KVO 底層實(shí)現(xiàn)是什么?
  2. 修改成員變量的值會(huì)出發(fā) KVO 嗎?
  3. KVC 賦值會(huì)出發(fā) KVO 嗎?

二,KVC 簡(jiǎn)述

1. KVC定義

KVC: Key-value coding is a mechanism for indirectly accessing anobject’s attributes and relationships using string identifiers.

所謂鍵值編碼枝秤,并不是訪(fǎng)問(wèn)器方法的啟動(dòng)和實(shí)例變量的訪(fǎng)問(wèn)這種直接的方式醋拧,而是使用表示屬性的字符串來(lái)間接訪(fǎng)問(wèn)對(duì)象屬性值的一種結(jié)構(gòu)。

只要存在訪(fǎng)問(wèn)器方法淀弹、聲明屬性或?qū)嵗兞康ず荆涂梢詫⑵涿种付樽址畞?lái)訪(fǎng)問(wèn)。

之所以說(shuō)鍵值編碼的訪(fǎng)問(wèn)是接的:

  1. 可以在運(yùn)行中確定作為鍵的字符串

  2. 使用者無(wú)法知道實(shí)際訪(fǎng)問(wèn)屬性的方法

鍵值編碼必需的方法在非正式協(xié)議NSKeyValueCoding中聲明(頭文件Foundation/NSKeyValueCoding.h)薇溃。默認(rèn)在NSObject中實(shí)現(xiàn)。

2. 方法調(diào)用

下面就以下兩個(gè)方法的調(diào)用進(jìn)行說(shuō)明:

  •  (id)  valueForKey: (NSString *) key
    

返回表示屬性的鍵字符串所對(duì)應(yīng)的值。如果不能取得值邑时,則將引起接收器調(diào)用方法valueForUndefinedKey:。

  •  (void)setValue: (id) value  forKey: (NSString*) key
    

將鍵字符串key所對(duì)應(yīng)的屬性的值設(shè)置為value滤钱。不能設(shè)定屬性時(shí)铜靶,將引起接收器調(diào)用方法setValue:ForUndefinedKey:。

執(zhí)行時(shí)哩掺,有訪(fǎng)問(wèn)器的屬性會(huì)使用訪(fǎng)問(wèn)器,沒(méi)有訪(fǎng)問(wèn)器的屬性也可以設(shè)定值和訪(fǎng)問(wèn)。因?yàn)樯厦鎯蓚€(gè)方法均為實(shí)例方法誊稚,可以在方法體內(nèi)訪(fǎng)問(wèn)實(shí)例變量。

訪(fǎng)問(wèn)過(guò)程如下:

  1. 接收器中如果有key訪(fǎng)問(wèn)器(或getKey、isKey爷贫、_key、_getKey腾务、setKey)則使用它。

  2. 沒(méi)有訪(fǎng)問(wèn)器時(shí)叙凡,使用接收器的類(lèi)方法accessInstanceVariablesDirectly來(lái)查詢(xún)。返回YES時(shí),如果存在實(shí)例變量key(或_key燥撞、isKey、_isKey等)則返回或設(shè)置其值茶鉴。使用引用計(jì)數(shù)管理方式時(shí)伞插,實(shí)例變量如果為對(duì)象舀瓢,則舊值會(huì)被自動(dòng)釋放,新值被保存并代入。

  • (BOOL)accessInstanceVariablesDirectly

    通常定義為返回YES备图,可以在子類(lèi)中改變。該類(lèi)方法返回YES時(shí)盾似,使用鍵值編碼可以訪(fǎng)問(wèn)該類(lèi)的實(shí)例變量。返回NO時(shí)不可以訪(fǎng)問(wèn)。只要該方法返回YES,實(shí)例變量的可視屬性即使有@private修飾乾吻,也可以訪(fǎng)問(wèn)。

  1. 既沒(méi)有訪(fǎng)問(wèn)器也沒(méi)有實(shí)例變量時(shí)奢方,將引起接收器調(diào)用方法valueForUndefinedKey:或setValue:forUndefinedKey:。
  •   (id) valueForUndefinedKey: (NSStirng *) key
    

不能取得鍵字符串對(duì)應(yīng)的值時(shí),從方法valueForKey:中調(diào)用該方法忠聚。默認(rèn)情況下,該方法的執(zhí)行會(huì)觸發(fā)NSUndefinedKeyException。不過(guò)欢瞪,通過(guò)在子類(lèi)中修改定義啸盏,就可以返回其他對(duì)象。

  •   (void) setValue: (id) value  forUndefinedKey: (NSString *) key
    

不能設(shè)置鍵字符串key對(duì)應(yīng)的屬性值時(shí),從方法setValue:forKey中調(diào)用該方法。默認(rèn)情況下吧凉,該方法的執(zhí)行會(huì)觸發(fā)異常NSUndefinedKeyException。不過(guò),通過(guò)在子類(lèi)中修改定義傍妒,可以返回其他對(duì)象。

  1. 如果該返回值不是對(duì)象嗦玖,則返回被適當(dāng)?shù)膶?duì)象包裝的值;設(shè)置值時(shí)也應(yīng)先包裝成相應(yīng)的對(duì)象器瘪。

屬性為對(duì)象時(shí),該對(duì)象還可能持有屬性住拭。這時(shí)候可以用“.”連接表示鍵的字符串挽牢,這種表示方式稱(chēng)為鍵路徑趴俘。只要能找到對(duì)象磨淌,點(diǎn)和鍵多長(zhǎng)都沒(méi)有關(guān)系搪锣。

  •  (id) valueForKeyPath:(NSString *) keyPath
    

以點(diǎn)切分鍵路徑,并使用第一個(gè)鍵向接收器發(fā)送valueForKey:方法狗超。然后,再使用鍵路徑的下一個(gè)鍵渗稍,向得到的對(duì)象發(fā)送valueForKey:方法,如此反復(fù)操作,返回最后獲得的對(duì)象。

  •  (void)setValue: (id) value  forKeyPath:(NSString *) keyPath
    

與valueForKeyPath:方法一樣取出對(duì)象,這里只對(duì)路徑中的最后一個(gè)鍵調(diào)用setValue:forKey:方法,并設(shè)定屬性值為value。

3. KVC準(zhǔn)則

  1. 隨訪(fǎng)問(wèn)器方法而改變诵冒。

  2. 使用setValue:forKey:和鍵進(jìn)行改變凯肋。此時(shí)也可能不經(jīng)由訪(fǎng)問(wèn)器。

  3. 使用setValue:forKeyPath:和鍵路徑進(jìn)行改變汽馋。此時(shí)也可能不經(jīng)由訪(fǎng)問(wèn)器侮东。不僅僅是最終的監(jiān)視對(duì)象的屬性,當(dāng)路徑中的屬性發(fā)生變化時(shí)豹芯,也會(huì)被通知木缝。

三,KVO實(shí)現(xiàn)原理探索

  1. 首先需要了解KVO基本使用饺藤,KVO的全稱(chēng) Key-Value Observing,俗稱(chēng)“鍵值監(jiān)聽(tīng)”蔽挠,可以用于監(jiān)聽(tīng)某個(gè)對(duì)象屬性值的改變蚌铜。KVO:key-value observing,是在KVC基礎(chǔ)上實(shí)現(xiàn)的,當(dāng)某個(gè)對(duì)象的屬性發(fā)生改變時(shí)难捌,通知其它對(duì)象的機(jī)制。僅僅在以KVC準(zhǔn)則來(lái)訪(fǎng)問(wèn)訪(fǎng)問(wèn)器或?qū)嵗兞康那闆r下,才可以監(jiān)視屬性的變化辛燥。在方法內(nèi)直接改變實(shí)例變量的值時(shí)辉懒,就不能監(jiān)視了窃判。
- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p1 = [[Person alloc] init];
    Person *p2 = [[Person alloc] init];
    p1.age = 1;
    p1.age = 2;
    p2.age = 2;
    // self 監(jiān)聽(tīng) p1的 age屬性
    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;
}

上述代碼中可以看出,在添加監(jiān)聽(tīng)之后,age屬性的值在發(fā)生改變時(shí)募狂,就會(huì)通知到監(jiān)聽(tīng)者,執(zhí)行監(jiān)聽(tīng)者的observeValueForKeyPath方法。

1. 探尋KVO底層實(shí)現(xiàn)原理

通過(guò)上述代碼我們發(fā)現(xiàn)夺艰,一旦age屬性的值發(fā)生改變時(shí),就會(huì)通知到監(jiān)聽(tīng)者,并且我們知道賦值操作都是調(diào)用 set方法遍略,我們可以來(lái)到Person類(lèi)中重寫(xiě)age的set方法盹愚,觀(guān)察是否是KVO在set方法內(nèi)部做了一些操作來(lái)通知監(jiān)聽(tīng)者。
我們發(fā)現(xiàn)即使重寫(xiě)了set方法魔种,p1對(duì)象和p2對(duì)象調(diào)用同樣的set方法析二,但是我們發(fā)現(xiàn)p1除了調(diào)用set方法之外還會(huì)另外執(zhí)行監(jiān)聽(tīng)器的observeValueForKeyPath方法。
說(shuō)明KVO在運(yùn)行時(shí)獲取對(duì)p1對(duì)象做了一些改變节预。相當(dāng)于在程序運(yùn)行過(guò)程中叶摄,對(duì)p1對(duì)象做了一些變化,使得p1對(duì)象在調(diào)用setage方法的時(shí)候可能做了一些額外的操作安拟,所以問(wèn)題出在對(duì)象身上蛤吓,兩個(gè)對(duì)象在內(nèi)存中肯定不一樣,兩個(gè)對(duì)象可能本質(zhì)上并不一樣糠赦。接下來(lái)來(lái)探索KVO內(nèi)部是怎么實(shí)現(xiàn)的会傲。

2. KVO底層實(shí)現(xiàn)分析

  • 首先我們對(duì)上述代碼中添加監(jiān)聽(tīng)的地方打斷點(diǎn),看觀(guān)察一下愉棱,addObserver方法對(duì)p1對(duì)象做了什么處理唆铐?也就是說(shuō)p1對(duì)象在經(jīng)過(guò)addObserver方法之后發(fā)生了什么改變,我們通過(guò)打印isa指針如下圖所示
addObserver對(duì)p1對(duì)象的處理

通過(guò)上圖我們發(fā)現(xiàn)奔滑,p1對(duì)象執(zhí)行過(guò)addObserver操作之后艾岂,p1對(duì)象的isa指針由之前的指向類(lèi)對(duì)象Person變?yōu)橹赶騈SKVONotifyin_Person類(lèi)對(duì)象,而p2對(duì)象沒(méi)有任何改變朋其。也就是說(shuō)一旦p1對(duì)象添加了KVO監(jiān)聽(tīng)以后王浴,其isa指針就會(huì)發(fā)生變化,因此set方法的執(zhí)行效果就不一樣了梅猿。

那么我們先來(lái)觀(guān)察p2對(duì)象在內(nèi)容中是如何存儲(chǔ)的氓辣,然后對(duì)比p2來(lái)觀(guān)察p1。
首先我們知道袱蚓,p2在調(diào)用setage方法的時(shí)候钞啸,首先會(huì)通過(guò)p2對(duì)象中的isa指針找到Person類(lèi)對(duì)象,然后在類(lèi)對(duì)象中找到setage方法。然后找到方法對(duì)應(yīng)的實(shí)現(xiàn)体斩。如下圖所示

未使用KVO監(jiān)聽(tīng)的對(duì)象放大實(shí)現(xiàn)路徑

但是剛才我們發(fā)現(xiàn)p1對(duì)象的isa指針在經(jīng)過(guò)KVO監(jiān)聽(tīng)之后已經(jīng)指向了NSKVONotifyin_Person類(lèi)對(duì)象梭稚,NSKVONotifyin_Person其實(shí)是Person的子類(lèi),那么也就是說(shuō)其superclass指針是指向Person類(lèi)對(duì)象的絮吵,NSKVONotifyin_Person是runtime在運(yùn)行時(shí)生成的弧烤。那么p1對(duì)象在調(diào)用setage方法的時(shí)候,肯定會(huì)根據(jù)p1的isa找到NSKVONotifyin_Person蹬敲,在NSKVONotifyin_Person中找setage的方法及實(shí)現(xiàn)暇昂。
經(jīng)過(guò)查閱資料我們可以了解到。
NSKVONotifyin_Person中的setage方法中其實(shí)調(diào)用了 Fundation框架中C語(yǔ)言函數(shù) _NSsetIntValueAndNotify伴嗡,_NSsetIntValueAndNotify內(nèi)部做的操作相當(dāng)于急波,首先調(diào)用willChangeValueForKey 將要改變方法,之后調(diào)用父類(lèi)的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方法中渣淤。

  • 那么如何驗(yàn)證KVO真的如上面所講的方式實(shí)現(xiàn)赏寇?

首先經(jīng)過(guò)之前打斷點(diǎn)打印isa指針,我們已經(jīng)驗(yàn)證了价认,在執(zhí)行添加監(jiān)聽(tīng)的方法時(shí)嗅定,會(huì)將isa指針指向一個(gè)通過(guò)runtime創(chuàng)建的Person的子類(lèi)NSKVONotifyin_Person。
另外我們可以通過(guò)打印方法實(shí)現(xiàn)的地址來(lái)看一下p1和p2的setage的方法實(shí)現(xiàn)的地址在添加KVO前后有什么變化用踩。

// 通過(guò)methodForSelector找到方法實(shí)現(xiàn)的地址
NSLog(@"添加KVO監(jiān)聽(tīng)之前 - p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);
    
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p1 addObserver:self forKeyPath:@"age" options:options context:nil];

NSLog(@"添加KVO監(jiān)聽(tīng)之后 - p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);
setage的方法實(shí)現(xiàn)的地址在添加KVO前后的變化

我們發(fā)現(xiàn)在添加KVO監(jiān)聽(tīng)之前渠退,p1和p2的setAge方法實(shí)現(xiàn)的地址相同,而經(jīng)過(guò)KVO監(jiān)聽(tīng)之后脐彩,p1的setAge方法實(shí)現(xiàn)的地址發(fā)生了變化碎乃,我們通過(guò)打印方法實(shí)現(xiàn)來(lái)看一下前后的變化發(fā)現(xiàn),確實(shí)如我們上面所講的一樣惠奸,p1的setAge方法的實(shí)現(xiàn)由Person類(lèi)方法中的setAge方法轉(zhuǎn)換為了C語(yǔ)言的Foundation框架的_NSsetIntValueAndNotify函數(shù)梅誓。
Foundation框架中會(huì)根據(jù)屬性的類(lèi)型,調(diào)用不同的方法佛南。例如我們之前定義的int類(lèi)型的age屬性梗掰,那么我們看到Foundation框架中調(diào)用的_NSsetIntValueAndNotify函數(shù)。那么我們把a(bǔ)ge的屬性類(lèi)型變?yōu)閐ouble重新打印一遍

_NSSetDoubleValueAndNotify函數(shù)

我們發(fā)現(xiàn)調(diào)用的函數(shù)變?yōu)榱薩NSSetDoubleValueAndNotify嗅回,那么這說(shuō)明Foundation框架中有許多此類(lèi)型的函數(shù)及穗,通過(guò)屬性的不同類(lèi)型調(diào)用不同的函數(shù)。
那么我們可以推測(cè)Foundation框架中還有很多例如_NSSetBoolValueAndNotify绵载、_NSSetCharValueAndNotify埂陆、_NSSetFloatValueAndNotify苛白、_NSSetLongValueAndNotify等等函數(shù)。

我們可以找到Foundation框架文件焚虱,通過(guò)命令行查詢(xún)關(guān)鍵字找到相關(guān)函數(shù)


相關(guān)函數(shù)
  • NSKVONotifyin_Person內(nèi)部結(jié)構(gòu)是怎樣的丸氛?

首先我們知道诬滩,NSKVONotifyin_Person作為Person的子類(lèi)饲齐,其superclass指針指向Person類(lèi)衅码,并且NSKVONotifyin_Person內(nèi)部一定對(duì)setAge方法做了單獨(dú)的實(shí)現(xiàn),那么NSKVONotifyin_Person同Person類(lèi)的差別可能就在于其內(nèi)存儲(chǔ)的對(duì)象方法及實(shí)現(xiàn)不同谍咆。
我們通過(guò)runtime分別打印Person類(lèi)對(duì)象和NSKVONotifyin_Person類(lèi)對(duì)象內(nèi)存儲(chǔ)的對(duì)象方法

- (void)viewDidLoad {
    [super viewDidLoad];

    Person *p1 = [[Person alloc] init];
    p1.age = 1.0;
    Person *p2 = [[Person alloc] init];
    p1.age = 2.0;
    // self 監(jiān)聽(tīng) p1的 age屬性
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [p1 addObserver:self forKeyPath:@"age" options:options context:nil];

    [self printMethods: object_getClass(p2)];
    [self printMethods: object_getClass(p1)];

    [p1 removeObserver:self forKeyPath:@"age"];
}

- (void) printMethods:(Class)cls
{
    unsigned int count ;
    Method *methods = class_copyMethodList(cls, &count);
    NSMutableString *methodNames = [NSMutableString string];
    [methodNames appendFormat:@"%@ - ", cls];
    
    for (int i = 0 ; i < count; i++) {
        Method method = methods[I];
        NSString *methodName  = NSStringFromSelector(method_getName(method));
        
        [methodNames appendString: methodName];
        [methodNames appendString:@" "];
        
    }
    
    NSLog(@"%@",methodNames);
    free(methods);
}

上述打印內(nèi)容如下:


NSKVONotifyin_Person內(nèi)存儲(chǔ)的對(duì)象方法

通過(guò)上述代碼我們發(fā)現(xiàn)NSKVONotifyin_Person中有4個(gè)對(duì)象方法。分別為setAge: class dealloc _isKVOA私股,那么至此我們可以畫(huà)出NSKVONotifyin_Person的內(nèi)存結(jié)構(gòu)以及方法調(diào)用順序摹察。

NSKVONotifyin_Person的內(nèi)存結(jié)構(gòu)以及方法調(diào)用順序

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

NSLog(@"%@,%@",[p1 class],[p2 class]);
// 打印結(jié)果 Person,Person

如果NSKVONotifyin_Person不重寫(xiě)class方法峭状,那么當(dāng)對(duì)象要調(diào)用class對(duì)象方法的時(shí)候就會(huì)一直向上找來(lái)到nsobject克滴,而nsobect的class的實(shí)現(xiàn)大致為返回自己isa指向的類(lèi),返回p1的isa指向的類(lèi)那么打印出來(lái)的類(lèi)就是NSKVONotifyin_Person优床,但是apple不希望將NSKVONotifyin_Person類(lèi)暴露出來(lái)劝赔,并且不希望我們知道NSKVONotifyin_Person內(nèi)部實(shí)現(xiàn),所以在內(nèi)部重寫(xiě)了class類(lèi)胆敞,直接返回Person類(lèi)着帽,所以外界在調(diào)用p1的class對(duì)象方法時(shí),是Person類(lèi)移层。這樣p1給外界的感覺(jué)p1還是Person類(lèi)仍翰,并不知道NSKVONotifyin_Person子類(lèi)的存在。

那么我們可以猜測(cè)NSKVONotifyin_Person內(nèi)重寫(xiě)的class內(nèi)部實(shí)現(xiàn)大致為:

- (Class) class {
     // 得到類(lèi)對(duì)象观话,在找到類(lèi)對(duì)象父類(lèi)
     return class_getSuperclass(object_getClass(self));
}
  • 驗(yàn)證didChangeValueForKey:內(nèi)部會(huì)調(diào)用observer的observeValueForKeyPath:ofObject:change:context:方法.

我們?cè)赑erson類(lèi)中重寫(xiě)willChangeValueForKey:和didChangeValueForKey:方法予借,模擬他們的實(shí)現(xiàn)。

- (void)setAge:(int)age
{
    NSLog(@"setAge:");
    _age = age;
}
- (void)willChangeValueForKey:(NSString *)key
{
    NSLog(@"willChangeValueForKey: - begin");
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey: - end");
}
- (void)didChangeValueForKey:(NSString *)key
{
    NSLog(@"didChangeValueForKey: - begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey: - end");
}

再次運(yùn)行來(lái)查看didChangeValueForKey的方法內(nèi)運(yùn)行過(guò)程匪燕,通過(guò)打印內(nèi)容可以看到蕾羊,確實(shí)在didChangeValueForKey方法內(nèi)部已經(jīng)調(diào)用了observer的observeValueForKeyPath:ofObject:change:context:方法。


didChangeValueForKey內(nèi)運(yùn)行順序

四帽驯,KVO底層原理

在這里插入圖片描述
  1. 首先創(chuàng)建一個(gè) Person 類(lèi) 內(nèi)部有個(gè) name 屬性,然后 創(chuàng)建p1 和 p2兩個(gè)實(shí)例對(duì)象,其中p1添加了kvo監(jiān)聽(tīng),p2沒(méi)有添加 kvo 監(jiān)聽(tīng),然后重寫(xiě)了 observeValueForKeyPath 方法 監(jiān)聽(tīng)Person.name 屬性發(fā)生改變時(shí)候的通知.
在這里插入圖片描述

從本質(zhì)上來(lái)看 Person 給name賦值的時(shí)候 調(diào)用的是 setName 方法 ,無(wú)論 p1還是p2 調(diào)用的 setter 方法都是一樣的,為什么 p1改變 name 屬性值就能有通知, p2確沒(méi)有,調(diào)用的 都是同一個(gè) setName:(NSString *)name 方法,區(qū)別怎么那么大?

  1. 接下來(lái)打印下p1和p2的內(nèi)存地址 看看p1和p2內(nèi)存地址能不能一探究竟.
在這里插入圖片描述
  1. 從 p1和 p2內(nèi)存地址上也看不出來(lái)什么東東.接著打印 p1和 p2 的 class 信息


    在這里插入圖片描述
  2. 打印 object_getClass 試試看龟再,我們都知道object_getClass(id) 才會(huì)返回這個(gè)實(shí)例對(duì)象的真實(shí) class 類(lèi)型


    在這里插入圖片描述
  3. 打印 setName 方法實(shí)現(xiàn)IMP指針有沒(méi)有發(fā)生改變,我們知道同一個(gè)方法的實(shí)現(xiàn) IMP 地址是不變的.


    在這里插入圖片描述
  4. 這里連 setName方法都不一樣了 , 為了一探究竟 對(duì)上邊的 NSKVONotifying_Person 和 添加 KVO 之后的 imp 指針進(jìn)行進(jìn)一步研究.

  • 首先 在 lldb 上輸入 imp1和 imp2


    在這里插入圖片描述

    發(fā)生了 imp1 方法實(shí)現(xiàn)在 Foundation 框架里的 _NSSetObjectValueAndNotify 函數(shù)中 ,而 imp2 則調(diào)用了 Person setName 方法


    在這里插入圖片描述

    也就是說(shuō)添加了 KVO 之后 p1 修改 name 值之后 不再調(diào)用 Person 的 setName方法 ,而 p2沒(méi)有添加 kvo 監(jiān)聽(tīng) 依然正常調(diào)用 setName:方法 ,由此可以得出 p1 添加完 KVO 監(jiān)聽(tīng)后 系統(tǒng)修改了默認(rèn)方法實(shí)現(xiàn),那么既然沒(méi)有調(diào)用 setName: 方法 為什么 p1.name 的值也發(fā)生了改變?
  1. 接下來(lái)我們準(zhǔn)備對(duì)剛才 NSKVONotifying_Person 類(lèi)進(jìn)行下一步研究, NSKVONotifying_Person 和 Person 有沒(méi)有內(nèi)在的聯(lián)系呢? 研究一下NSKVONotifying_Person和 Person 之間的聯(lián)系時(shí)什么?


    在這里插入圖片描述

    通過(guò)打印 NSKVONotifying_Person 的 superclass 和 Person 的 superclass 可以得出, NSKVONotifying_Person是一個(gè) Person 子類(lèi),那么為什么蘋(píng)果會(huì)動(dòng)態(tài)創(chuàng)建這么一個(gè) 子類(lèi)呢? NSKVONotifying_Person 這個(gè)子類(lèi) 跟 Person 內(nèi)部有哪些不同呢 ?

這個(gè)時(shí)候 我們?nèi)ポ敵鱿?Person 和 NSKVONotifying_Person 內(nèi)部的方法列表 和 屬性列表 ,看看NSKVONotifying_Person 子類(lèi)都添加了那些方法和屬性.

- (void)viewDidLoad {
    [super viewDidLoad];

    
    Person *p1 = [[Person alloc] init];
    Person *p2 = [[Person alloc] init];
    
    id cls1 = object_getClass(p1);
    id cls2 = object_getClass(p2);
    NSLog(@"添加 KVO 之前: cls1 = %@  cls2 = %@ ",cls1,cls2);
    
    [p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
     cls1 = object_getClass(p1);
     cls2 = object_getClass(p2);
    
    
    NSString *methodList1 = [self printPersonMethods:cls1];
    NSString *methodList2 = [self printPersonMethods:cls2];

    NSLog(@"%@",methodList1);
    NSLog(@"%@",methodList2);

    
//  NSLog(@"添加 KVO 之后: cls1 = %@  cls2 = %@ ",cls1,cls2);
    
//  id super_cls1 = class_getSuperclass(cls1);
//  id super_cls2 = class_getSuperclass(cls2);
//
//  NSLog(@"super_cls1 = %@ ,super_cls2 = %@",super_cls1,super_cls2);
//
//  p1.name = @"dzb";
//  p2.name = @"123";

}

- (NSString *) printPersonMethods:(id)obj {
    
    unsigned int count = 0;
    Method *methods = class_copyMethodList([obj class],&count);
    NSMutableString *methodList = [NSMutableString string];
    [methodList appendString:@"[\n"];
    for (int i = 0; i<count; i++) {
        Method method = methods[I];
        SEL sel = method_getName(method);
        [methodList appendFormat:@"%@",NSStringFromSelector(sel)];
        [methodList appendString:@"\n"];
    }
    
    [methodList appendFormat:@"]"];
    
    free(methods);
    
    return methodList;
}

輸出結(jié)果如下:


在這里插入圖片描述

從輸出結(jié)果可以看出來(lái) NSKVONotifying_Person 內(nèi)部也有一個(gè) setName:方法 還重寫(xiě)了 class 和 dealloc 方法 , _isKVOA, 那么我們可以大致的得出, p1添加 kVO 后 runtime 動(dòng)態(tài)的生成了一個(gè) NSKVONotifying_Person子類(lèi) 并重寫(xiě)了 setName 方法 ,那么 setName 內(nèi)部一定是做了一些事情,才會(huì)觸發(fā) observeValueForKeyPath 監(jiān)聽(tīng)方法.

  1. 繼續(xù)探究 NSKVONotifying_Person 子類(lèi) 重寫(xiě) setName 都做了什么?
    其實(shí) setName 方法內(nèi)部 是調(diào)用了 Foundation 的 _NSSetObjectValueAndNotify 函數(shù) ,在 _NSSetObjectValueAndNotify 內(nèi)部:
    1. 首先會(huì)調(diào)用 willChangeValueForKey
    1. 然后給 name 屬性賦值
    1. 最后調(diào)用 didChangeValueForKey
    1. 最后調(diào)用 observer 的 observeValueForKeyPath 去告訴監(jiān)聽(tīng)器屬性值發(fā)生了改變 .
在這里插入圖片描述
  1. 由于蘋(píng)果 Foundation 框架是不開(kāi)源的 ,所以我們依然可以通過(guò)重寫(xiě)Person 的 willChangeValueForKey 和 didChangeValueForKey 驗(yàn)證我們的猜想 .


    在這里插入圖片描述

首先當(dāng)我們改變p1.name 的值時(shí) 并不是首先執(zhí)行的 setName: 這個(gè)方法 ,而是先調(diào)用了 willChangeValueForKey 其次 調(diào)用父類(lèi)的 setter 方法 對(duì)屬性賦值 ,然后再調(diào)用 didChangeValueForKey 方法 ,并在 didChangeValueForKey 內(nèi)部 調(diào)用監(jiān)聽(tīng)器的 observeValueForKeyPath方法 告訴外界 屬性值發(fā)生了改變.

在這里插入圖片描述
在這里插入圖片描述

至于重寫(xiě)了 dealloc 和 class 方法 是為了做一些 KVO 釋放內(nèi)存 和 隱藏外界對(duì)于 NSKVONotifying_Person 子類(lèi)的存在

在這里插入圖片描述

10.這就是我們調(diào)用 [p1 class] 和 [p2 class]結(jié)果都顯示 Person 類(lèi) ,讓我們誤以為 Person 沒(méi)有發(fā)生變化

  • KVC 對(duì)屬性賦值時(shí)候 是會(huì)在這個(gè)類(lèi)里邊 去查找 _age isAge setAge setIsAge 等方法的 ,最終會(huì)調(diào)用屬性的 setter 方法 ,那么如果添加了 KVO 還是會(huì)被觸發(fā)的 .
    相反 設(shè)置成員變量 _age 由于不會(huì)觸發(fā) setter 方法 ,因此不會(huì)去觸發(fā) KVO 相關(guān)的代碼 .

五尼变,KVO底層實(shí)現(xiàn)代碼

1. 通過(guò)代碼來(lái)自己實(shí)現(xiàn)KVO監(jiān)聽(tīng)

  1. ViewController調(diào)用實(shí)現(xiàn)
#import "ViewController.h"
#import "Person.h"
#import "NSObject+KCKVO.h"
#import "Dog.h"

@interface ViewController ()
@property (nonatomic, strong) Person *p;
@end


// 分類(lèi)
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.p = [[Person alloc] init];
    [self.p lg_addObserver:self forKeyPath:@"name"];
    self.p.name  = @"kongyulu";
}


#pragma mark - value 回調(diào)
- (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object newValue:(id)newValue{
    NSLog(@"lg_observeValueForKeyPath - %@",newValue);
}

#pragma mark - dealloc
- (void)dealloc{

}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.p.name = [NSString stringWithFormat:@"%@+",self.p.name];
}

@end

  1. 定義兩個(gè)類(lèi)
  • 定義Person類(lèi)
#import <Foundation/Foundation.h>

@interface Person : NSObject{
    @public
    NSString *girl;
}

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

@property (nonatomic, strong) NSMutableArray *mArray;

+ (instancetype)shared;

@end



#import "Person.h"

@implementation Person
- (void)setName:(NSString *)name{
    NSLog(@"設(shè)置方法 ");
}
- (void)dealloc{
    NSLog(@"父走了");
}

@end

  • 定義Dog類(lèi)
#import <Foundation/Foundation.h>
#import "Person.h"

@interface Dog : Person

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

@end

#import "Dog.h"

@implementation Dog
- (void)dealloc{
    NSLog(@"兒子走了");
}
@end
  1. 定義NSObject的一個(gè)實(shí)現(xiàn)KVO監(jiān)聽(tīng)的分類(lèi)NSObject+KCKVO
  • 頭文件
#import <Foundation/Foundation.h>

typedef void(^KCKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);

@interface NSObject (KCKVO)

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end

  • 實(shí)現(xiàn)類(lèi)
#import "NSObject+KCKVO.h"
#import <objc/message.h>

static NSString *const kKCKVOPrefix = @"KCKVO_";
static NSString *const kKCKVOAssiociateKey = @"kKCKVO_AssiociateKey";

@implementation NSObject (KCKVO)

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    // 1: 是否有setter方法
    // id superClassName = object_getClassName(self); // person
    // setName
    NSString *setterMethodName = setterForGetter(keyPath); // setName:
    SEL setterSel = NSSelectorFromString(setterMethodName);
    // method
    Method method = class_getInstanceMethod([self class], setterSel);// runtime 1900009931
    if (!method) {
        @throw [[NSException alloc] initWithName:NSExtensionItemAttachmentsKey reason:@"沒(méi)有setter方法" userInfo:nil];
    }
    
    //2: 動(dòng)態(tài)生成子類(lèi)
    Class childClass = [self creatChildClassWithKeypath:keyPath];
    if (!childClass) {
        NSLog(@"創(chuàng)建失敗");
    }
    // 3.0 消息轉(zhuǎn)發(fā)
    // observer
    // 關(guān)聯(lián)對(duì)象
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kKCKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}




#pragma mark - 動(dòng)態(tài)創(chuàng)建子類(lèi)

- (Class)creatChildClassWithKeypath:(NSString *)keyPath{
    
    NSString *oldClassName   = NSStringFromClass([self class]);//person
    NSString *childClassName = [NSString stringWithFormat:@"%@%@",kKCKVOPrefix,oldClassName];
    
    //2: 動(dòng)態(tài)生成子類(lèi)
    //2.1 申請(qǐng)類(lèi)
    Class childClass = objc_allocateClassPair([self class], childClassName.UTF8String, 0);
    //2.2 注冊(cè)類(lèi)
    objc_registerClassPair(childClass);
    //2.3 添class
    SEL classSel = NSSelectorFromString(@"class");
    Method classMethod = class_getClassMethod([self class], classSel);
    const char *classType = method_getTypeEncoding(classMethod);
    class_addMethod(childClass, classSel, (IMP)lg_Class, classType);
    //2.4 setter : setName:
    SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getClassMethod([self class], setterSel);
    const char *setterType = method_getTypeEncoding(setterMethod);
    class_addMethod(childClass, setterSel, (IMP)lg_setter, setterType);
    //2.5 isa 指向
    object_setClass(self, childClass);
    return childClass;
}

/**
 判斷是否存在該方法
 */
- (BOOL)hasSeletor:(SEL)selector{
    
    Class observedClass = object_getClass(self);
    unsigned int methodCount = 0;
    //得到一堆方法的名字列表  //class_copyIvarList 實(shí)例變量  //class_copyPropertyList 得到所有屬性名字
    Method *methodList = class_copyMethodList(observedClass, &methodCount);
    
    for (int i = 0; i<methodCount; i++) {
        SEL sel = method_getName(methodList[I]);
        if (selector == sel) {
            free(methodList);
            return YES;
        }
    }
    free(methodList);
    return NO;
}

#pragma mark - 函數(shù)區(qū)域
static Class lg_Class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}

static void lg_setter(id self,SEL _cmd,id value){

    NSLog(@"lg_setter - %@",value);
    
    id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kKCKVOAssiociateKey));
    SEL handlSEL = @selector(lg_observeValueForKeyPath: ofObject:newValue:);
    NSString *keypath = getterForSetter(NSStringFromSelector(_cmd));
    objc_msgSend(observer,handlSEL,keypath,self,value);
    
//    [observer performSelector:@selector(lg_observeValueForKeyPath: ofObject:newValue:) withObject:self afterDelay:0];
}

#pragma mark - 從get方法獲取set方法的名稱(chēng) name ===>>> setName:
static NSString  * setterForGetter(NSString *getter){
    
    if (getter.length <= 0) { return nil; }
    
    NSString *firstString = [[getter substringToIndex:1] uppercaseString];
    NSString *leaveString = [getter substringFromIndex:1];
    
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}

#pragma mark - 從set方法獲取getter方法的名稱(chēng) setName:===> name
static NSString * getterForSetter(NSString *setter){
    
    if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
    
    NSRange range = NSMakeRange(3, setter.length-4);
    NSString *getter = [setter substringWithRange:range];
    NSString *firstString = [[getter substringToIndex:1] lowercaseString];
    getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
    
    return getter;
}

@end

  1. 運(yùn)行打印結(jié)果:


    在這里插入圖片描述

2. 通過(guò) runtime 動(dòng)態(tài)創(chuàng)建子類(lèi)方式去實(shí)現(xiàn)

  1. 動(dòng)態(tài)創(chuàng)建一個(gè) NSKVONotifying_Person 子類(lèi)

/**
 運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建子類(lèi)

 @param super_cls 父類(lèi)
 @return 返回子類(lèi)
 */
- (Class) registerSubClassWithSuperClass:(Class)super_cls  {
    ///動(dòng)態(tài)的創(chuàng)建 子類(lèi)
    NSString *clsName = [NSString stringWithFormat:@"NSKVONotifying_%@",super_cls];
    ///一個(gè) NSObject 默認(rèn)分配16個(gè)字節(jié)內(nèi)存
    Class sub_cls = objc_allocateClassPair(super_cls,clsName.UTF8String,16);
    ///注冊(cè)一個(gè)子類(lèi)
    objc_registerClassPair(sub_cls);
    ///將父類(lèi) isa 指針指向 子類(lèi)
    object_setClass(self, sub_cls);
    return sub_cls;
}

  1. 動(dòng)態(tài)的給這個(gè)子類(lèi) 動(dòng)態(tài)添加方法 setter 方法 didChangeValueForKey方法 class 方法實(shí)現(xiàn)
///動(dòng)態(tài)創(chuàng)建子類(lèi)  NSKVONotifying_xxx
    Class sub_cls = [self registerSubClassWithSuperClass:super_cls];

    ///給子類(lèi)動(dòng)態(tài)的添加 class setter  didChangeValueForKey 實(shí)現(xiàn)
    Method class_method = class_getInstanceMethod(super_cls, @selector(class));
    Method changeValue_method = class_getInstanceMethod(super_cls, @selector(didChangeValueForKey:));

    class_addMethod(sub_cls, @selector(class), (IMP)kvo_class,method_getTypeEncoding(class_method));
    ///給子類(lèi)動(dòng)態(tài)的添加 didChangeValueForKey
    class_addMethod(sub_cls, @selector(didChangeValueForKey:), (IMP)didChangeValue,method_getTypeEncoding(changeValue_method));
    ///動(dòng)態(tài)的給子類(lèi)添加 setter 方法
    class_addMethod(sub_cls, setterSel, (IMP)kvo_setter,method_getTypeEncoding(method));

    ///將觀(guān)察者對(duì)象跟當(dāng)前實(shí)例 self 關(guān)聯(lián)起來(lái)
    objc_setAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedObservers), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);


  1. 重寫(xiě) class 方法實(shí)現(xiàn)

/**
 自實(shí)現(xiàn) class 方法

 @param self 當(dāng)前類(lèi)實(shí)現(xiàn)
 @param _cmd  class
 @return  返回父類(lèi) Class 外界不會(huì)知道 NSKVONotifying_子類(lèi)存在
 */
static Class kvo_class(id self,SEL _cmd) {
    return class_getSuperclass(object_getClass(self));
}

  1. 重寫(xiě) setter 方法實(shí)現(xiàn)

/**
 自實(shí)現(xiàn) setter 方法

 @param self 當(dāng)前類(lèi)實(shí)現(xiàn)
 @param _cmd  setter
 @param newValue  賦值
 */
static void kvo_setter(id self,SEL _cmd,id newValue) {

    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = getterForSetter(setterName);

    ///將要改變屬性的值
    [self willChangeValueForKey:getterName];

    ///調(diào)用 super setter 方法
    struct objc_super suer_cls = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };

    ///存儲(chǔ)舊值
    objc_setAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedOldValue),[self valueForKey:getterName], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    ///調(diào)用父類(lèi) setter 方法 設(shè)置新值
    objc_msgSendSuper(&suer_cls,_cmd,newValue);
    ///改變監(jiān)聽(tīng)屬性值后 調(diào)用 didChangeValueForKey 并在內(nèi)部 調(diào)用
    [self didChangeValueForKey:getterName];

};

  1. 重寫(xiě) didChangeValueForKey 方法實(shí)現(xiàn)
/**
 didChangeValueForkey 實(shí)現(xiàn)方法 , 當(dāng)根據(jù) SEL (didChangeValueForkey:) 會(huì)找到方法 IMP 實(shí)現(xiàn)
 */
static void didChangeValue(id self,SEL _cmd,NSString *key) {

    id newValue = [self valueForKey:key];
    id observer = objc_getAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedObservers));
    id oldValue = objc_getAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedOldValue));

    NSMutableDictionary *change = [NSMutableDictionary dictionary];
    if (oldValue) {
        change[@"oldValue"] = oldValue;
    } else {
        change[@"oldValue"] = [NSNull null];
    }
    if (newValue) {
        change[@"newValue"] = newValue;
    } else {
        change[@"newValue"] = newValue;
    }

    [observer observeValueForKeyPath:key ofObject:self change:change context:NULL];

}


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末利凑,一起剝皮案震驚了整個(gè)濱河市浆劲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哀澈,老刑警劉巖牌借,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異割按,居然都是意外死亡膨报,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)适荣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)现柠,“玉大人,你說(shuō)我怎么就攤上這事弛矛」环裕” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵丈氓,是天一觀(guān)的道長(zhǎng)周循。 經(jīng)常有香客問(wèn)我,道長(zhǎng)万俗,這世上最難降的妖魔是什么湾笛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮闰歪,結(jié)果婚禮上迄本,老公的妹妹穿的比我還像新娘。我一直安慰自己课竣,他們只是感情好嘉赎,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著于樟,像睡著了一般公条。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上迂曲,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天靶橱,我揣著相機(jī)與錄音,去河邊找鬼路捧。 笑死关霸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的杰扫。 我是一名探鬼主播队寇,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼章姓!你這毒婦竟也來(lái)了佳遣?” 一聲冷哼從身側(cè)響起识埋,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎零渐,沒(méi)想到半個(gè)月后窒舟,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诵盼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年惠豺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片风宁。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耕腾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杀糯,到底是詐尸還是另有隱情,我是刑警寧澤苍苞,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布固翰,位于F島的核電站,受9級(jí)特大地震影響羹呵,放射性物質(zhì)發(fā)生泄漏骂际。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一冈欢、第九天 我趴在偏房一處隱蔽的房頂上張望歉铝。 院中可真熱鬧,春花似錦凑耻、人聲如沸太示。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)类缤。三九已至,卻和暖如春邻吭,著一層夾襖步出監(jiān)牢的瞬間餐弱,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工囱晴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膏蚓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓畸写,卻偏偏與公主長(zhǎng)得像驮瞧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子枯芬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

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

  • 問(wèn)題 iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO剧董?(KVO的本質(zhì)是什么幢尚?) 如何手動(dòng)觸發(fā)KVO ? 首先需要了解KVO...
    hjltony閱讀 579評(píng)論 0 2
  • 對(duì)小碼哥底層班視頻學(xué)習(xí)的總結(jié)與記錄翅楼。面試題部分尉剩,通過(guò)對(duì)面試題的分析探索問(wèn)題的本質(zhì)內(nèi)容。 問(wèn)題iOS用什么方式實(shí)現(xiàn)對(duì)...
    xx_cc閱讀 10,786評(píng)論 26 65
  • 前言 KVO作為iOS一個(gè)設(shè)計(jì)模式毅臊,監(jiān)聽(tīng)對(duì)象屬性變化理茎。通過(guò)屬性變化來(lái)做出一些處理。那么KVO底層原理是什么管嬉?相信大...
    楓葉無(wú)處漂泊閱讀 810評(píng)論 0 2
  • 面試問(wèn)題: · iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO皂林? · 如何手動(dòng)觸發(fā)KVO? 我們通過(guò)以下幾個(gè)點(diǎn)來(lái)尋找這兩個(gè)...
    高思陽(yáng)閱讀 240評(píng)論 0 1
  • 泡面蚯撩,以其食用便捷性础倍、價(jià)格平價(jià)性和味道的豐富性,自出世以來(lái)胎挎,便廣受歡迎」灯簦現(xiàn)今,更是眾多宅男宅女月末吃土的最佳選擇犹菇。...
    Log君閱讀 639評(píng)論 0 1