iOS KVO 詳解

什么是KVO偎蘸?

KVO 全稱 Key Value Observing庄蹋,是蘋果提供的一套事件通知機(jī)制。允許對(duì)象監(jiān)聽(tīng)另一個(gè)對(duì)象特定屬性的改變迷雪,并在改變時(shí)接收到事件限书。由于 KVO 的實(shí)現(xiàn)機(jī)制,只針對(duì)屬性才會(huì)發(fā)生作用章咧,一般繼承自 NSObject 的對(duì)象都默認(rèn)支持 KVO倦西。

KVO 可以監(jiān)聽(tīng)單個(gè)屬性的變化,也可以監(jiān)聽(tīng)集合對(duì)象的變化慧邮。通過(guò) KVCmutableArrayValueForKey: 等方法獲得代理對(duì)象调限,當(dāng)代理對(duì)象的內(nèi)部對(duì)象發(fā)生改變時(shí)舟陆,會(huì)回調(diào) KVO 監(jiān)聽(tīng)的方法。集合對(duì)象包含 NSArrayNSSet耻矮。

KVO基本使用

  • 使用KVO大致分為三個(gè)步驟:
    1. 通過(guò)addObserver:forKeyPath:options:context:方法注冊(cè)觀察者秦躯,觀察者可以接收keyPath屬性的變化事件
    2. 在觀察者中實(shí)現(xiàn)observeValueForKeyPath:ofObject:change:context:方法,當(dāng)keyPath屬性發(fā)生改變后裆装,KVO會(huì)回調(diào)這個(gè)方法來(lái)通知觀察者
    3. 當(dāng)觀察者不需要監(jiān)聽(tīng)時(shí)踱承,可以調(diào)用removeObserver:forKeyPath:方法將KVO移除。需要注意的是哨免,調(diào)用removeObserver需要在觀察者消失之前茎活,否則會(huì)導(dǎo)致Crash

注冊(cè)觀察者

 /*
@observer:就是觀察者策精,是誰(shuí)想要觀測(cè)對(duì)象的值的改變湿镀。
@keyPath:就是想要觀察的對(duì)象屬性雇初。
@options:options一般選擇NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld朝捆,這樣當(dāng)屬性值發(fā)生改變時(shí)我們可以同時(shí)獲得舊值和新值沐兰,如果我們只填NSKeyValueObservingOptionNew則屬性發(fā)生改變時(shí)只會(huì)獲得新值况既。
@context:想要攜帶的其他信息罪针,比如一個(gè)字符串或者字典什么的啤它。
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

監(jiān)聽(tīng)回調(diào)

/*
@keyPath:觀察的屬性
@object:觀察的是哪個(gè)對(duì)象的屬性
@change:這是一個(gè)字典類型的值普办,通過(guò)鍵值對(duì)顯示新的屬性值和舊的屬性值
@context:上面添加觀察者時(shí)攜帶的信息
*/
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

調(diào)用方式

自動(dòng)調(diào)用

  • 調(diào)用KVO屬性對(duì)象時(shí)工扎,不僅可以通過(guò)點(diǎn)語(yǔ)法和set語(yǔ)法進(jìn)行調(diào)用,還可以使用KVC方法
//通過(guò)屬性的點(diǎn)語(yǔ)法間接調(diào)用
objc.name = @"";

// 直接調(diào)用set方法
[objc setName:@"Savings"];

// 使用KVC的setValue:forKey:方法
[objc setValue:@"Savings" forKey:@"name"];

// 使用KVC的setValue:forKeyPath:方法
[objc setValue:@"Savings" forKeyPath:@"account.name"];

手動(dòng)調(diào)用

  • KVO 在屬性發(fā)生改變時(shí)的調(diào)用是自動(dòng)的衔蹲,如果想要手動(dòng)控制這個(gè)調(diào)用時(shí)機(jī)肢娘,或想自己實(shí)現(xiàn) KVO 屬性的調(diào)用,則可以通過(guò) KVO 提供的方法進(jìn)行調(diào)用舆驶。

    1. 第一步我們需要認(rèn)識(shí)下面這個(gè)方法橱健,如果想要手動(dòng)調(diào)用或自己實(shí)現(xiàn)KVO需要重寫該方法該方法返回YES表示可以調(diào)用,返回NO則表示不可以調(diào)用贞远。
    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
        BOOL automatic = NO;
        if ([theKey isEqualToString:@"name"]) {
            automatic = NO;//對(duì)該key禁用系統(tǒng)自動(dòng)通知畴博,若要直接禁用該類的KVO則直接返回NO;
        }
        else {
            automatic = [super automaticallyNotifiesObserversForKey:theKey];
        }
        return automatic;
    }
    
    
  1. 第二步我們需要重寫setter方法
- (void)setName:(NSString *)name {
    if (name != _name) {
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }
}

移除觀察者

//需要在不使用的時(shí)候,移除監(jiān)聽(tīng)
- (void)dealloc{
    [self removeObserver:self forKeyPath:@"age"];
}

Crash

觀察者未實(shí)現(xiàn)監(jiān)聽(tīng)方法

  • 若觀察者對(duì)象 -observeValueForKeyPath:ofObject:change:context: 未實(shí)現(xiàn)蓝仲,將會(huì) Crash

    Crash:Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘<ViewController: 0x7f9943d06710>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled

未及時(shí)移除觀察者

Crash: Thread 1: EXC_BAD_ACCESS (code=1, address=0x105e0fee02c0)

//觀察者ObserverPersonChage
@interface ObserverPersonChage : NSObject
  //實(shí)現(xiàn)observeValueForKeyPath: ofObject: change: context:
@end

//ViewController
- (void)addObserver
{
    self.observerPersonChange = [[ObserverPersonChage alloc] init];
    [self.person1 addObserver:self.observerPersonChange forKeyPath:@"age" options:option context:@"age chage"];
    [self.person1 addObserver:self.observerPersonChange forKeyPath:@"name" options:option context:@"name change"];
}

//點(diǎn)擊按鈕將觀察者置為nil俱病,即銷毀
- (IBAction)clearObserverPersonChange:(id)sender {
    self.observerPersonChange = nil;
}

//點(diǎn)擊改變person1屬性值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.person1.age = 29;
    self.person1.name = @"hengcong";
}

  1. 假如在當(dāng)前 ViewController 中,注冊(cè)了觀察者袱结,點(diǎn)擊屏幕亮隙,改變被觀察對(duì)象 person1 的屬性值。
  2. 點(diǎn)擊對(duì)應(yīng)按鈕垢夹,銷毀觀察者溢吻,此時(shí) self.observerPersonChange 為 nil。
  3. 再次點(diǎn)擊屏幕,此時(shí) Crash促王;

多次移除觀察者

Cannot remove an observer for the key path “age” from because it is not registered as an observer.

實(shí)際應(yīng)用

KVO主要用來(lái)做鍵值觀察操作犀盟,想要一個(gè)值發(fā)生改變后通知另一個(gè)對(duì)象,則用KVO實(shí)現(xiàn)最為合適蝇狼。斯坦福大學(xué)的iOS教程中有一個(gè)很經(jīng)典的案例阅畴,通過(guò)KVOModelController之間進(jìn)行通信。

image

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

KVO是通過(guò)isa 混寫(isa-swizzling)技術(shù)實(shí)現(xiàn)的(是不是一臉懵逼迅耘?我第一次見(jiàn)和你一樣贱枣,你現(xiàn)在只需要知道這個(gè)技術(shù)就行了,下面我會(huì)圖文并茂的給你講解到底是怎么回事颤专。)纽哥。在運(yùn)行時(shí)根據(jù)原類創(chuàng)建一個(gè)中間類,這個(gè)中間類是原類的子類栖秕,并動(dòng)態(tài)修改當(dāng)前對(duì)象的isa指向中間類春塌。并且將class方法重寫,返回原類的Class累魔。所以蘋果建議在開(kāi)發(fā)中不應(yīng)該依賴isa指針摔笤,而是通過(guò)class實(shí)例方法來(lái)獲取對(duì)象類型。

測(cè)試代碼

NSKeyValueObservingOptions option = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;

NSLog(@"person1添加KVO監(jiān)聽(tīng)對(duì)象之前-類對(duì)象 -%@", object_getClass(self.person1));
NSLog(@"person1添加KVO監(jiān)聽(tīng)之前-方法實(shí)現(xiàn) -%p", [self.person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person1添加KVO監(jiān)聽(tīng)之前-元類對(duì)象 -%@", object_getClass(object_getClass(self.person1)));

[self.person1 addObserver:self forKeyPath:@"age" options:option context:@"age chage"];

NSLog(@"person1添加KVO監(jiān)聽(tīng)對(duì)象之后-類對(duì)象 -%@", object_getClass(self.person1));
NSLog(@"person1添加KVO監(jiān)聽(tīng)之后-方法實(shí)現(xiàn) -%p", [self.person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person1添加KVO監(jiān)聽(tīng)之后-元類對(duì)象 -%@", object_getClass(object_getClass(self.person1)));

//打印結(jié)果
KVO-test[1214:513029] person1添加KVO監(jiān)聽(tīng)對(duì)象之前-類對(duì)象 -Person
KVO-test[1214:513029] person1添加KVO監(jiān)聽(tīng)之前-方法實(shí)現(xiàn) -0x100411470
KVO-test[1214:513029] person1添加KVO監(jiān)聽(tīng)之前-元類對(duì)象 -Person

KVO-test[1214:513029] person1添加KVO監(jiān)聽(tīng)對(duì)象之后-類對(duì)象 -NSKVONotifying_Person
KVO-test[1214:513029] person1添加KVO監(jiān)聽(tīng)之后-方法實(shí)現(xiàn) -0x10076c844
KVO-test[1214:513029] person1添加KVO監(jiān)聽(tīng)之后-元類對(duì)象 -NSKVONotifying_Person

//通過(guò)地址查找方法
(lldb) p (IMP)0x10f24b470
(IMP) $0 = 0x000000010f24b470 (KVO-test`-[Person setAge:] at Person.h:15)
(lldb) p (IMP)0x10f5a6844
(IMP) $1 = 0x000000010f5a6844 (Foundation`_NSSetLongLongValueAndNotify)

  • 通過(guò)測(cè)試代碼垦写,我們添加KVO前后發(fā)生以下變化
    1. person指向的類對(duì)象元類對(duì)象,以及 setAge: 均發(fā)生了變化彰触;
    2. 添加KVO后梯投,person 中的 isa 指向了 NSKVONotifying_Person 類對(duì)象;
    3. 添加 KVO 之后况毅,setAge: 的實(shí)現(xiàn)調(diào)用的是:Foundation 中 _NSSetLongLongValueAndNotify 方法分蓖;

發(fā)現(xiàn)中間對(duì)象

從上述測(cè)試代碼的結(jié)果我們發(fā)現(xiàn),person 中的 isa 從開(kāi)始指向Person類對(duì)象尔许,變成指向了 NSKVONotifying_Person 類對(duì)象

  • KVO會(huì)在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建一個(gè)新類么鹤,將對(duì)象的isa指向新創(chuàng)建的類,新類是原類的子類味廊,命名規(guī)則是NSKVONotifying_xxx的格式蒸甜。

    1. 未使用KVO監(jiān)聽(tīng)對(duì)象是,對(duì)象和類對(duì)象之間的關(guān)系如下
image
  1. 使用KVO監(jiān)聽(tīng)對(duì)象后余佛,對(duì)象和類對(duì)象之間會(huì)添加一個(gè)中間對(duì)象
image
NSKVONotifying_Person類內(nèi)部實(shí)現(xiàn)

我們從上面兩張圖很清楚的看到添加KVO之前和KVO之后的變化柠新,下面我們剖析一下這個(gè)中間類NSKVONotifying_Person(這里是*通配符,它代表數(shù)據(jù)類型,例如:int辉巡, longlong)

- (void)setAge:(int)age{
    _NSSet*ValueAndNotify();//這個(gè)方法調(diào)用順序是什么恨憎,它是在調(diào)用何處方法,都在setter方法改變中詳解
}

- (Class)class {
    return [LDPerson class];
}

- (void)dealloc {
    // 收尾工作
}

- (BOOL)_isKVOA {
    return YES;
}

  • isa混寫之后如何調(diào)用方法
    1. 調(diào)用監(jiān)聽(tīng)的屬性設(shè)置方法郊楣,如 setAge:憔恳,都會(huì)先調(diào)用 NSKVONotify_Person 對(duì)應(yīng)的屬性設(shè)置方法瓤荔;
    2. 調(diào)用非監(jiān)聽(tīng)屬性設(shè)置方法,如 test钥组,會(huì)通過(guò) NSKVONotify_Personsuperclass茉贡,找到 Person 類對(duì)象,再調(diào)用其 [Person test] 方法
  • 為什么重寫class方法
    • 如果沒(méi)有重寫class方法,當(dāng)該對(duì)象調(diào)用class方法時(shí),會(huì)在自己的方法緩存列表,方法列表,父類緩存,方法列表一直向上去查找該方法,因?yàn)?code>class方法是NSObject中的方法,如果不重寫最終可能會(huì)返回NSKVONotifying_Person,就會(huì)將該類暴露出來(lái),也給開(kāi)發(fā)者造成困擾,寫的是Person,添加KVO之后class方法返回怎么是另一個(gè)類者铜。
  • _isKVOA有什么作用
    • 這個(gè)方法可以當(dāng)做使用了KVO的一個(gè)標(biāo)記腔丧,系統(tǒng)可能也是這么用的。如果我們想判斷當(dāng)前類是否是KVO動(dòng)態(tài)生成的類作烟,就可以從方法列表中搜索這個(gè)方法愉粤。
setter實(shí)現(xiàn)不同
  • 在測(cè)試代碼中,我們已經(jīng)通過(guò)地址查找添加KVO前后調(diào)用的方法

  • //通過(guò)地址查找方法
    //添加KVO之前
    (lldb) p (IMP)0x10f24b470
    (IMP) $0 = 0x000000010f24b470 (KVO-test`-[Person setAge:] at Person.h:15)
    //添加KVO之后
    (lldb) p (IMP)0x10f5a6844
    (IMP) $1 = 0x000000010f5a6844 (Foundation`_NSSetLongLongValueAndNotify)
    
    
    • 0x10f24b470這個(gè)地址的setAge:實(shí)現(xiàn)是調(diào)用Person類的setAge:方法拿撩,并且是在Person.h的第15行衣厘。
    • 0x10f5a6844這個(gè)地址的setAge:實(shí)現(xiàn)是調(diào)用_NSSetIntValueAndNotify這樣一個(gè)C函數(shù)。

KVO內(nèi)部調(diào)用流程

  • 由于我們無(wú)法去窺探_NSSetIntValueAndNotify的真實(shí)結(jié)構(gòu)压恒,也無(wú)法去重寫NSKVONotifying_Person這個(gè)類影暴,所以我們只能利用它的父類Person類來(lái)分析其執(zhí)行過(guò)程。

    - (void)setAge:(int)age{
        _age = age;
        NSLog(@"setAge:");
    }
    
    - (void)willChangeValueForKey:(NSString *)key{
        [super willChangeValueForKey:key];
        NSLog(@"willChangeValueForKey");
    }
    
    - (void)didChangeValueForKey:(NSString *)key{
        NSLog(@"didChangeValueForKey - begin");
        [super didChangeValueForKey:key];
        NSLog(@"didChangeValueForKey - end");
    }
    @end
    
    //打印結(jié)果
    KVO-test[1457:637227] willChangeValueForKey
    KVO-test[1457:637227] setAge:
    KVO-test[1457:637227] didChangeValueForKey - begin
    KVO-test[1457:637227] didChangeValueForKey - end
    KVO-test[1457:637227] willChangeValueForKey
    KVO-test[1457:637227] didChangeValueForKey - begin
    KVO-test[1457:637227] didChangeValueForKey - end
    
    

<article class="_2rhmJa">

    • 通過(guò)打印結(jié)果探赫,我們可以清晰看到
      1. 首先調(diào)用willChangeValueForKey:方法型宙。
      2. 然后調(diào)用setAge:方法真正的改變屬性的值。
      3. 開(kāi)始調(diào)用didChangeValueForKey:這個(gè)方法伦吠,調(diào)用[super didChangeValueForKey:key]時(shí)會(huì)通知監(jiān)聽(tīng)者屬性值已經(jīng)改變妆兑,然后監(jiān)聽(tīng)者執(zhí)行自己的- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context這個(gè)方法。
  • 下面我用一張圖來(lái)展示KVO執(zhí)行流程

    image

KVO擴(kuò)展

1.KVC 與 KVO 的不同毛仪?

  • KVC(鍵值編碼)搁嗓,即 Key-Value Coding,一個(gè)非正式的 Protocol箱靴,使用字符串(鍵)訪問(wèn)一個(gè)對(duì)象實(shí)例變量的機(jī)制腺逛。而不是通過(guò)調(diào)用 Setter、Getter 方法等顯式的存取方式去訪問(wèn)衡怀。
  • KVO(鍵值監(jiān)聽(tīng))棍矛,即 Key-Value Observing,它提供一種機(jī)制,當(dāng)指定的對(duì)象的屬性被修改后,對(duì)象就會(huì)接受到通知狈癞,前提是執(zhí)行了 setter 方法茄靠、或者使用了 KVC 賦值。

2.和 notification(通知)的區(qū)別蝶桶?

  • KVONSNotificationCenter 都是 iOS 中觀察者模式的一種實(shí)現(xiàn)慨绳。區(qū)別在于,相對(duì)于被觀察者和觀察者之間的關(guān)系,KVO 是一對(duì)一的脐雪,而不是一對(duì)多的厌小。KVO 對(duì)被監(jiān)聽(tīng)對(duì)象無(wú)侵入性,不需要修改其內(nèi)部代碼即可實(shí)現(xiàn)監(jiān)聽(tīng)战秋。
  • notification 的優(yōu)點(diǎn)是監(jiān)聽(tīng)不局限于屬性的變化璧亚,還可以對(duì)多種多樣的狀態(tài)變化進(jìn)行監(jiān)聽(tīng),監(jiān)聽(tīng)范圍廣脂信,例如鍵盤癣蟋、前后臺(tái)等系統(tǒng)通知的使用也更顯靈活方便。

</article>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狰闪,一起剝皮案震驚了整個(gè)濱河市疯搅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌埋泵,老刑警劉巖幔欧,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異丽声,居然都是意外死亡礁蔗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門雁社,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浴井,“玉大人,你說(shuō)我怎么就攤上這事歧胁∽趟牵” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵喊巍,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我箍鼓,道長(zhǎng)崭参,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任款咖,我火速辦了婚禮何暮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘铐殃。我一直安慰自己海洼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布富腊。 她就那樣靜靜地躺著坏逢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上是整,一...
    開(kāi)封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天肖揣,我揣著相機(jī)與錄音,去河邊找鬼浮入。 笑死龙优,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的事秀。 我是一名探鬼主播彤断,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼易迹!你這毒婦竟也來(lái)了宰衙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赴蝇,失蹤者是張志新(化名)和其女友劉穎菩浙,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體句伶,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡劲蜻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了考余。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片先嬉。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖楚堤,靈堂內(nèi)的尸體忽然破棺而出疫蔓,到底是詐尸還是另有隱情,我是刑警寧澤身冬,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布衅胀,位于F島的核電站,受9級(jí)特大地震影響酥筝,放射性物質(zhì)發(fā)生泄漏滚躯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一嘿歌、第九天 我趴在偏房一處隱蔽的房頂上張望掸掏。 院中可真熱鬧,春花似錦宙帝、人聲如沸丧凤。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)愿待。三九已至浩螺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呼盆,已是汗流浹背年扩。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留访圃,地道東北人厨幻。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像腿时,于是被迫代替她去往敵國(guó)和親况脆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(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
  • 面試問(wèn)題: · iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO徽鼎? · 如何手動(dòng)觸發(fā)KVO盛末? 我們通過(guò)以下幾個(gè)點(diǎn)來(lái)尋找這兩個(gè)...
    高思陽(yáng)閱讀 239評(píng)論 0 1
  • 什么是KVO? KVO 全稱 Key Value Observing否淤,是蘋果提供的一套事件通知機(jī)制悄但。允許對(duì)象監(jiān)聽(tīng)另...
    祀夢(mèng)_閱讀 487評(píng)論 0 4
  • KVO的全稱是Key-Value Observing,俗稱鍵值監(jiān)聽(tīng)石抡,可以用于監(jiān)聽(tīng)某個(gè)對(duì)象屬性值的改變檐嚣。下面我們來(lái)了...
    Goose的小黃花閱讀 467評(píng)論 0 2
  • 一.水平垂直居中 1.水平居中 ①margin:0 auto; 2.垂直居中 ①li...
    無(wú)言無(wú)意閱讀 162評(píng)論 0 1