KVO的用法、底層實現原理


KVO的用法

KVO也就是key-value-observing(即鍵值觀察),利用一個key來找到某個屬性並監聽其值得改變。用法如下:

  • 添加觀察者
  • 在觀察者中實現監聽方法,observeValueForKeyPath: ofObject: change: context:(通過查閱文檔可以知道,絕大多數對象都有這個方法,因為這個方法屬於NSObject)
  • 移除觀察者
//讓對象b監聽對象a的name屬性
//options屬性可以選擇是哪個
 /* NSKeyValueObservingOptionNew =0x01, 新值 
  * NSKeyValueObservingOptionOld =0x02, 舊值 
  */ 
[a addObserver:b forKeyPath:@"name"options:kNilOptionscontext:nil]; 
a.name = @"zzz";
#pragma mark - 實現KVO回調方法
/* * 當對象的屬性發生改變會調用該方法
    * @param keyPath 監聽的屬性 
    * @param object 監聽的對象 
    * @param change 新值和舊值 
    * @param context 額外的數據 
*/
- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary<NSString *,id>*)change context:(void *)context{ 
    NSLog(@"%@的值改變了,",keyPath); 
    NSLog(@"change:%@", change);
}
//最后不要忘記了,去移除observer
- (void)dealloc{ 
       [a removeObserver:b forKeyPath:@"name"];
  }
KVO鍵值觀察者底層解析
涉及到了runtime,關於isa指針
手動實現鍵值觀察(代碼示例)

被觀察的對象Target(重寫setter/getter方法)
Target.h

@interface Target : NSObject
{
   int age;
}
// for manual KVO 
- age- (int) age;
- (void) setAge:(int)theAge;
@end

 

 

Target.m

@implementation Target
- (id) init{ 
    self = [super init]; 
    if (nil != self) { 
          age = 10; 
     } 
    return self;
}
// for manual KVO - age
- (int) age{
    return age;
}
- (void) setAge:(int)theAge{ 
    [self willChangeValueForKey:@"age"];
    age = theAge; 
    [self didChangeValueForKey:@"age"];
}
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key { 
    if ([key isEqualToString:@"age"]) {
     return NO;
 } 
return [super automaticallyNotifiesObserversForKey:key];
}
@end

 

首先,需要手動實現屬性的 setter 方法,並在設置操作的前后分別調用 willChangeValueForKey: didChangeValueForKey方法,這兩個方法用於通知系統該 key 的屬性值即將和已經變更了;
其次,要實現類方法 automaticallyNotifiesObserversForKey,並在其中設置對該 key 不自動發送通知(返回 NO 即可)。這里要注意,對其它非手動實現的 key,要轉交給 super 來處理。

實現原理

KVO的實現是基於runtime運行時的,下面就來詳細介紹一下原理:還是這張圖:


 
  • 當某個類的對象第一次被觀察時,系統就會在運行期動態地創建該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的 setter 方法。
  • 派生類在被重寫的 setter 方法中實現真正的通知機制,就如前面手動實現鍵值觀察那樣。這么做是基於設置屬性會調用 setter 方法,而通過重寫就獲得了 KVO 需要的通知機制。當然前提是要通過遵循 KVO 的屬性設置方式來變更屬性值,如果僅是直接修改屬性對應的成員變量,是無法實現 KVO 的。
  • 同時派生類還重寫了 class 方法以“欺騙”外部調用者它就是起初的那個類。然后系統將這個對象的 isa 指針指向這個新誕生的派生類,因此這個對象就成為該派生類的對象了,因而在該對象上對 setter 的調用就會調用重寫的 setter,從而激活鍵值通知機制。此外,派生類還重寫了 dealloc 方法來釋放資源。

 

KVO與Notification之間的區別:

notification是需要一個發送notification的對象,一般是notificationCenter,來通知觀察者。

KVO是直接通知到觀察對象,並且邏輯非常清晰,實現步驟簡單。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM