kvo 實踐使用總結

上半年有段時間做了一個項目,項目中聊天界面用到了音頻播放,涉及到進度條,當時做android時候處理的不太好,由于item復用導致進度條會被多個信息實體引用控制,雖然最后繞啊繞,也解決了,但是費了老大勁。所以做ios時候,就使用了kvo以盡量實現解耦。

使用kvo過程中,也是經歷了一些坑。

本篇文章,學完第一二節,結合自己實踐就能使用了。后面的章節,可以作為自己的拔高,嘿嘿

備注:寫該篇文章也借鑒參考了許多大牛的文章,結合自己的實踐,總結了一下。大牛勿噴。嘿嘿

一、KVO是什么?

  • KVO 是 Objective-C 對觀察者設計模式的一種實現。【另外一種是:通知機制(notification)】;
  • KVO提供一種機制,指定一個被觀察對象(例如A類),當對象某個屬性(例如A中的字符串name)發生更改時,監聽對象會獲得通知,并作出相應處理;【且不需要給被觀察的對象添加任何額外代碼,就能使用KVO機制】
    在MVC設計架構下的項目,KVO機制很適合實現mode模型和view視圖之間的通訊。

例如:代碼中,在模型類A創建屬性數據,在控制器中創建觀察者,一旦屬性數據發生改變就收到觀察者收到通知,通過KVO再在控制器使用回調方法處理實現視圖B的更新;

KVC與KVO的不同

KVC(鍵值編碼),即Key-Value Coding,一個非正式的Protocol,使用字符串(鍵)訪問一個對象實例變量的機制。而不是通過調用Setter、Getter方法等顯式的存取方式去訪問。
KVO(鍵值監聽),即Key-Value Observing,它提供一種機制,當指定的對象的屬性被修改后,對象就會接受到通知,前提是執行了setter方法、或者使用了KVC賦值。

和notification(通知)的區別

notification比KVO多了發送通知的一步。
兩者都是一對多,但是對象之間直接的交互,notification明顯得多,需要notificationCenter來做為中間交互。而KVO如我們介紹的,設置觀察者->處理屬性變化,至于中間通知這一環,則隱秘多了,只留一句“交由系統通知”,具體的可參照以上實現過程的剖析。

notification的優點是監聽不局限于屬性的變化,還可以對多種多樣的狀態變化進行監聽,監聽范圍廣,例如鍵盤、前后臺等系統通知的使用也更顯靈活方便。

與delegate的不同

和delegate一樣,KVO和NSNotification的作用都是類與類之間的通信。但是與delegate不同的是:
這兩個都是負責發送接收通知,剩下的事情由系統處理,所以不用返回值;而delegate 則需要通信的對象通過變量(代理)聯系;
delegate一般是一對一,而這兩個可以一對多。

二、kvo簡單使用

1:注冊觀察者,實施監聽;

  • 被觀察對象必須能支持kvc機制——所有NSObject的子類都支持這個機制
  • 必須用 被觀察對象 的 addObserver:forKeyPath:options:context: 方法注冊觀察者
  • 觀察者 必須實現 observeValueForKeyPath:ofObject:change:context: 方法
[self.model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

- observer 指觀察者

- keyPath 表示被觀察者的屬性
 
- options 決定了提供給觀察者change字典中的具體信息有哪些。 【見options解析】

- context 這個參數可以是一個 C指針,也可以是一個 對象引用,它可以作為這個context的唯一標識,也可以提供一些數據給觀察者。因為你傳進去是啥,回調時候還是回傳的還是啥

:options解析

typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
    表示監聽對象的新值(變化后的值),change字典中會包含有該key的鍵值對,通過該key,就可以取到屬性變化后的值
    NSKeyValueObservingOptionNew = 0x01,
    
    表示監聽對象的舊值(變化前的值),change字典中會包含有該key的鍵值對,通過該key,就可以取到屬性變化前的值
    NSKeyValueObservingOptionOld = 0x02,
    
    在注冊觀察者的方法return的時候就會發出一次通知。比如:在viewDidLoad中注冊的監聽,那viewDidLoad方法運行完,通知就發出去了
    NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04,
    
    會在值發生改變前發出一次通知,當然改變后的通知依舊還會發出,也就是每次change都會有兩個通知
    NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08
};
  • 注冊監聽,options入參是個枚舉,該入參跟監聽回調中的change呼應。。并且,以上options入參時候是可以用 | 或運算進行多選的。
    • 例如:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld。。那在change字典中就會包含屬性變化前后的值。。
    • 注意:通過多options監聽屬性的時候,例如上,并不是回到一次老值,再回調一次新值,,而是新老值都是在change字典中的。。

2:監聽回調

  • 觀察者實現方法都一樣:observeValueForKeyPath:ofObject:change:context: 就這一個方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    - keyPath:你所觀察對象的屬性
    - object:你所觀察的對象
    - change:你所觀察對象屬性值的變化
}

:change解析

NSKeyValueChangeKey枚舉

監聽回調中,通過key獲取監聽屬性的變化值。如下枚舉:

FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeKindKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeOldKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeIndexesKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey
  • NSKeyValueChangeKindKey

    • 這個key包含的value是一個 NSNumber 里面是一個 int
      (有點繞:value是[NSNumber numberWithInt:xxx]),
      與之對應的是 NSKeyValueChange 的枚舉
  • NSKeyValueChangeNewKey

    • 跟options中的對應
  • NSKeyValueChangeOldKey

    • 跟options中的對應
  • NSKeyValueChangeIndexesKey

    • 當 NSKeyValueChangeKindKey 的結果是 NSKeyValueChangeInsertion,
      NSKeyValueChangeRemoval 或 NSKeyValueChangeReplacement 的時候,
      這個key的value是一個NSIndexSet,包含了發生insert,remove,replace的對象的索引集合
  • NSKeyValueChangeNotificationIsPriorKey

    • 這個key包含了一個 NSNumber,里面是一個布爾值,如果在注冊時 options 中有
      NSKeyValueObservingOptionPrior,那么在前一個通知中的 change 中就會
      有這個key的value, 我們可以這樣來判斷是不是在改變前的通知[change[NSKeyValueChangeNotificationIsPriorKey] boolValue] ==
      YES;】

    說明: change是個字典,ios中dic獲取值通常用valueForKey或objectFroKey,,以上方式也可,dic[@"key"]..方便快捷。大家可以了解一下,不失為一種方式。。

NSKeyValueChange枚舉

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};
  • 當 change[NSKeyValueChangeKindKey] 是 NSKeyValueChangeSetting 的時候,說明被觀察屬性的setter方法被調用了
    • Insert, Remove, Replace:被觀察屬性是集合類型,且對它進行了 insert,remove,replace 操作的時候會返回這三種Key

:context解析

context作用一般都被忽略了。主要還是平常使用kvo都是簡單的訂閱-響應-移除。很少涉及到 多訂閱-響應-多移除 或 多訂閱-多響應-多移除。。。在以下的 三、kvo注意事項中有詳解

3:移除觀察者

你可以通過 removeObserver:forKeyPath: 或 removeObserver:forKeyPath:context: 方法來移除一個觀察。

注意:如果你的 context 是一個 對象,你必須在移除觀察之前持有它的強引用。當移除了觀察后,觀察者對象再也不會受到這個 keyPath 的通知。

三、kvo使用注意事項

:注冊監聽

  • 多次添加相同的監聽
    • 也就是添加過的監聽,都要挨個移除。。所以這一點,在cell中使用時候要特別注意。因為cell多次運行,監聽可能就是多次添加
    • 效果如下圖
observation.png

:響應

  • kvo觸發是嚴格依賴kvc機制的。簡單來說就是觸發kvo必須是kvc方式給屬性賦值。。

    • 反例:_name = @"qkn"..這種是不會觸發響應的。。。
    • 因為沒有調用屬性的setter方法,所以也就不會觸發notify,,kvo原理深入分析中有講kvo的實現原理
  • 由于監聽回調是一個函數,可能有多個監聽,所以,比較好的邏輯是,通過object和keypath過濾出來你要監聽的對象-屬性

  • KVO嚴重依賴string,換句話說,KVO中的keyPath必須是NSString這個事實使得編譯器沒辦法在編譯階段將錯誤的keyPath給找出來;譬如很容易將「contentSize」寫成「contentsize」;

    • 方案一:使用NSStringFromSelector(SEL aSelector)方法,即改@"contentSize"為NSStringFromSelector(@selector(contentSize))
    • 方案二:#define varName(var) [NSString stringWithFormat:@"%s",#var]。。使用:varName(屬性名)。。
  • 對于Objective-C,很多時候runtime系統都會自動幫助處理superclass的方法。但對于KVO不會這樣,所以為了保證父類(父類可能也會自己observe處理嘛)的observe事務也能被處理。所以要注意:在過濾到自己監聽的屬性后,要有個else分支,去處理:[superobserveValueForKeyPath:keyPath ofObject:object change:change context:context];

  • 針對上面那個問題,如果業務比較復雜,多類監聽了同一對象或爺父子孫類監聽了同一對象。怎么整?

    • 原則:誰的事兒誰負責
    • 所以,用到context,類注冊監聽時候,傳一個獨一無二的值。建議把自己的類名傳進去。在回調監聽時候,就可以通過context進行檢驗過濾了
    • 但是如果一個類監聽一個對象的多個屬性呢?傳的context也不夠用了。還有object和keyPath呢。。這三個參數已經可以確定獨一無二的監聽

:移除監聽

  • 有兩個方法:
    • 建議用上面的。注冊,響應,取消。方法保持一樣
-(void)removeObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath context:(void *)context;

-(void)removeObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath;
  • 我們一般會在dealloc中進行removeObserver操作(這也是Apple所推薦的)

  • 取消訂閱可能會crash:移除一個不存在的監聽

  • 多次remove相同的監聽會導致crash

    • 解決方案同上
    • 另外:同一個對象同一屬性的多次監聽(添加順序:123),默認移除時候,也是要多次移除(移除順序321)。
    • 當然也可以指定context。例如:注冊監聽時候context入參為@“1”,那么移除時候,就可以指定context為@“1”。并別這不是比對指針,而是值。
context.png
所以,如果使用中用到了context傳的是字符串,索性也就把context值提出來作為公共的獨一無二的,避免像keyPath入參一樣,誤寫了。但是如果是c指針,或對象引用就另說了
  • 如果某對象被釋放時候,還有對象在監聽它,也會報錯
reason: 'An instance 0x7fed3ef6e170 of class SecVC was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x600000038300> (
<NSKeyValueObservance 0x60000004ff60: Observer: 0x7fed3ef6e170, Key path: changeColor, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x60000005b720>

解決方案:

Facebook開源一個庫,KVOController。移除監聽不用再自己管理

四、kvo實現原理

原理

雖然ios不開源,官方api說的也很有限,但是kvo原理已經被大家通過“黑科技”摸透了

  • KVO在Apple中的API文檔如下:
Automatic key-value observing is implemented using a technique called isa-swizzling… 
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, 
pointing to an intermediate class rather than at the true class …

簡單翻譯:在我們對某個對象完成監聽的注冊后,編譯器會修改監聽對象(下文中原理驗證中的test對象)的isa指針,讓這個指針指向一個新生成的中間類。從某個意義上來說,這是一場騙局。

這里要說明的是isa這個指針,isa是一個Class類型的指針,對象的首地址一般是isa變量,同時isa又保存了對象的類對象的首地址。我們通過object_getClass方法來獲取這個對象的元類,即是對象的類對象的類型(正常來說,class方法內部的實現就是獲取這個isa保存的對象的類型,在kvo的實現中蘋果對被監聽對象的class方法進行了重寫隱藏了實現)。class方法是獲得對象的類型,雖然這兩個返回的結果是一樣的,但是兩個方法在本質上得到的結果不是同一個東西
在oc中,規定了只要擁有isa指針的變量,通通都屬于對象。上面的objc_object表示的是NSObject這個類的結構體表示,因此oc不允許出現非NSObject子類的對象(block是一個特殊的例外)*
當然了,蘋果并不想講述更多的實現細節,但是我們可以通過運行時機制來完成一些有趣的調試。

  • 原理簡析:引自滴滴構架師 sunnyxx 的一篇文章 objc kvo簡單探索
    • 當一個object有觀察者時,動態創建這個object的類的子類
    • 對于每個被觀察的property,重寫其set方法
    • 在重寫的set方法中調用- willChangeValueForKey:和- didChangeValueForKey:通知觀察者
    • 當一個property沒有觀察者時,刪除重寫的方法
    • 當沒有observer觀察任何一個property時,刪除動態創建的子類

原理驗證

(lldb)po xxx.class -> 對象的類型

(lldb)po object-getClass(xxx) -> 對象的類對象的類型

1:聲明對象

debug1.png

結果:

result1.png

2:注冊監聽

debug2.png

結果:

result2.png

3:移除監聽

debug3.png

結果:

result3.png

上面的結果說明,在test對象被觀察時,framework使用runtime動態創建了一個Sark類的子類NSKVONotifying_Test
而且為了隱藏這個行為,NSKVONotifying_Sark重寫了- class方法返回之前的類,就好像什么也沒發生過一樣
但是使用object_getClass()時就暴露了,因為這個方法返回的是這個對象的isa指針,這個指針指向的一定是個這個對象的類對象

拓展:類 動態創建后,觀察一下這個動態中間類實現的方法

ios 代碼庫。。大家可以積累下

NSObject+DLIntrospection :: 它封裝了打印一個類的方法、屬性、協議等常用調試方法,一目了然。

po [object_getClass(test) instanceMethods] -> 打印類內部實現方法

debug2.png
kvomethods.png

說明:

    • setxxx 最主要的重寫方法,set值時調用通知函數, 接下來會深入分析重寫setter方法
    • class 隱藏自己必備啊,返回原來類的class。。有了這個方法,你就不會知道,系統已經創建了中間類NSKVONotifying_xxx
    • dealloc 做清理犯罪現場工作。。既然實現了一個類,里面做的操作,產生的垃圾啥的,都要釋放回收
    • _isKVOA 這就是內部使用的標示了,判斷這個類有沒被KVO動態生成子類

原理深入分析

Apple 使用了 isa 混寫(isa-swizzling)來實現 KVO 。當觀察對象A時,KVO機制動態創建一個新的名為: NSKVONotifying_A的新類,該類繼承自對象A的本類,且KVO為NSKVONotifying_A重寫觀察屬性的setter 方法,setter 方法會負責在調用原 setter 方法之前和之后,通知所有觀察對象屬性值的更改情況。

(備注: isa 混寫(isa-swizzling)isa:is a kind of ; swizzling:混合,攪合;)

①NSKVONotifying_A類剖析:在這個過程,被觀察對象的 isa 指針從指向原來的A類,被KVO機制修改為指向系統新創建的子類 NSKVONotifying_A類,來實現當前類屬性值改變的監聽;

所以當我們從應用層面上看來,完全沒有意識到有新的類出現,這是系統“隱瞞”了對KVO的底層實現過程,讓我們誤以為還是原來的類。但是此時如果我們創建一個新的名為“NSKVONotifying_A”的類(),就會發現系統運行到注冊KVO的那段代碼時程序就崩潰,因為系統在注冊監聽的時候動態創建了名為NSKVONotifying_A的中間類,并指向這個中間類了。

(isa 指針的作用:每個對象都有isa 指針,指向該對象的類,它告訴 Runtime 系統這個對象的類是什么。所以對象注冊為觀察者時,isa指針指向新子類,那么這個被觀察的對象就神奇地變成新子類的對象(或實例)了。) 因而在該對象上對 setter 的調用就會調用已重寫的 setter,從而激活鍵值通知機制。

—>我猜,這也是KVO回調機制,為什么都俗稱KVO技術為黑魔法的原因之一吧:內部神秘、外觀簡潔。

②子類setter方法剖析:KVO的鍵值觀察通知依賴于 NSObject 的兩個方法:willChangeValueForKey:和 didChangevlueForKey:,在存取數值的前后分別調用2個方法:

被觀察屬性發生改變之前,willChangeValueForKey:被調用,通知系統該 keyPath 的屬性值即將變更;當改變發生后, didChangeValueForKey: 被調用,通知系統該 keyPath 的屬性值已經變更;之后, observeValueForKey:ofObject:change:context: 也會被調用。且重寫觀察屬性的setter 方法這種繼承方式的注入是在運行時而不是編譯時實現的。

KVO為子類的觀察者屬性重寫調用存取方法的工作原理在代碼中相當于:

-(void)setName:(NSString *)newName
{
    [self willChangeValueForKey:@"name"];    //KVO在調用存取方法之前總調用
    [super setValue:newName forKey:@"name"]; //調用父類的存取方法
    [self didChangeValueForKey:@"name"];     //KVO在調用存取方法之后總調用
}

系統重寫了setter方法:如何獲知呢?

在你監聽的屬性所在的類中,重寫willChangeValueForKey 和 didChangeValueForKey。。在你注冊完監聽,改變監聽屬性值的時候,看能不能走下面的方法就ok了

- (void)willChangeValueForKey:(NSString *)key {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [super willChangeValueForKey:key];
}

- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [super didChangeValueForKey:key];
}

五、kvo進階使用

監聽readOnly屬性

如果在開發中,不幸遇到要對readonly的屬性進行kvo監聽,那就只能求心理陰影面積了。。因為readonly的屬性沒有setter方法呀。那怎么去觸發kvo通知。
注意: 以下方案只針對自己創建的類的屬性有效,對于系統的屬性無效..親測,使用分類,通過_xxx拿到系統的屬性,還是不行。哈哈。。說到底,那還是系統的。豈能讓你亂動

方案一:
系統怎么實現的,咱也怎么實現。。在給屬性賦值前后,模仿系統的做法,如下:

[self willChangeValueForKey:@"xxx"];

_xxx = xxx;

[self didChangeValueForKey:@"xxx"];

方案二:

[被觀察者 setValue:xxx forKey:@"xxx"];

監聽數組變化

iOS默認不支持對數組的KVO,因為普通方式監聽的對象的地址的變化,而數組地址不變,而是里面的值發生了改變。
親測:

objectAddress.png
  • 該實踐并沒有什么實際作用,項目中不適宜這么用。除非有特殊需求,這么整也算是一種方案

使用方法:

1:創建繼承object類:一般就是創建一個模型,里面的屬性是你要監聽的數組

2:注冊監聽:[_object addObserver:forKeyPath:options:context]

3:改變數組,觸發監聽:[_object mutableArrayValueForKey:xxx] addObject:xxx]

4:響應監聽:observeValueForKeyPath~

注意: 第一步:初始化要監聽的數組,必須通過kvc方式。否則初始化不了。

1: 新建數組初始化
NSDictionary *dic = [NSDictionary dictionaryWithObject:[NSMutableArray arrayWithCapacity:0] forKey:@"數組屬性名"];  

self.model = [[model alloc] initWithDic:dic];
        
2: kvc方式,給模型中數組初始化
-(id)initWithDic:(NSDictionary *)dic  
{  
    self = [super init];  
    if (self) {  
        [self setValuesForKeysWithDictionary:dic];  
    }  

    return self;  
}  

-(void)setValue:(id)value forUndefinedKey:(NSString *)key  
{  
    NSLog(@"undefine key ---%@",key);  
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容

  • 本文結構如下: Why? (為什么要用KVO) What? (KVO是什么) How? ( KVO怎么用) Mo...
    等開會閱讀 1,659評論 1 21
  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,753評論 0 9
  • 本文由我們團隊的 糾結倫 童鞋撰寫。 文章結構如下: Why? (為什么要用KVO) What? (KVO是什么...
    知識小集閱讀 7,420評論 7 105
  • KVC 什么是 KVC KVC 是 Key-Value-Coding 的簡稱。 KVC 是一種可以直接通過字符串的...
    LeeJay閱讀 2,216評論 6 41
  • 一、概述 KVO,即:Key-Value Observing,它提供一種機制,當指定的對象的屬性被修改后,則其觀察...
    DeerRun閱讀 10,104評論 11 33