你好,歡迎來到IOS教程網

 Ios教程網 >> IOS使用技巧 >> IOS技巧綜合 >> KVO的底層實現

KVO的底層實現

編輯:IOS技巧綜合
[摘要]本文是對KVO的底層實現的講解,對學習IOS蘋果軟件開發有所幫助,與大家分享。

1、KVO是基於Runtime機制實現的;

2、當某個類的對象的某個屬性第一次被觀察時,系統會在運行期間動態地創建該類的一個派生類,在這個派生類中重寫基類的任何被觀察屬性的setter方法,派生類在被重寫的setter方法內實現真正的通知機制;

3、如果原類為Person,那麼生成的派生類名為NSKVONotifying_Person;

4、我們知道,每一個對象都有一個isa指針(即第一個成員變量)指向當前類,所以系統就是在當一個類的對象第一次被觀察的時候,偷偷的將isa指針指向動態生成的派生類,從而在 “被監聽屬性” 賦值時執行的是派生類的setter方法;

5、此外,蘋果還偷偷重寫了此派生類的class方法,這時候 po personObj,打印的依舊是Person,但使用po object_getClass(personObj),就可以打印出對象的類型NSKVONotifying_Person;這是蘋果為了隱藏生成的派生類,讓我們誤以為還是使用的當前類;

6、鍵值觀察通知依賴於NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey:;在一個被觀察屬性發生改變之前, willChangeValueForKey: 一定會被調用,這就會記錄舊的值;而當改變發生後,didChangeValueForKey: 會被調用,繼而 observeValueForKey:ofObject:change:context: 也會被調用。

7、注意:willChangeValueForKey:和didChangevlueForKey:缺一不可;

直接拿代碼來說明吧,說了一大堆,估計大家都看的不知所以然。

1、場景:我們聲明了一個Person類,裡面有兩個屬性,name和age,我們要使用kvo來監聽Person對象的age發生的變化;

2、創建Person類,聲明屬性:

 #import <Foundation/Foundation.h>
 
 /**
  Person模型類
  */
 @interface Person : NSObject
 
 /**
  姓名
  */
 @property(nonatomic,copy) NSString *name;
 
 /**
  年齡
  */
 @property(nonatomic,assign) NSInteger age;
 
 @end

3、創建Person對象,添加KVO監聽對象age屬性

 #import "ViewController.h"
 #import "Person.h"
 
 @interface ViewController ()
 
 @end
 
 @implementation ViewController
 
 - (void)viewDidLoad {
     [super viewDidLoad];
     
     Person *per = [[Person alloc] init];
     per.name = @"xiaoming";
     per.age = 18;
     
     // 觀察per對象的age屬性的變化情況
     [per addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew  context:NULL];
     
     // 修改age屬性的值,將會被觀察到
     per.age = 20;
     
     // 這裡po per的結果:<Person: 0x608000026960>
     // 但是:po object_getClass(per)的結果:NSKVONotifying_Person
     // 所以:蘋果偷偷重寫了派生類的class方法,但用運行時可以查看當前對象所屬類型
     
     // 移除KVO
     [per removeObserver:self forKeyPath:@"age"];
 }
 
 /**
  KVO監聽:當被監聽對象的被監聽屬性變化後調用
  
  @param keyPath 被監聽的屬性
  @param object 被監聽的對象
  @param change 變化情況
  @param context 攜帶的參數,此示例傳入NULL即可
  */
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
 {
     NSLog(@"%@對象的%@屬性發生變化了%@",object,keyPath,change);
     
     // 打印結果:
     // <Person: 0x608000026960>對象的age屬性發生變化了{
     //     kind = 1;
     //     new = 20;
     // }
 }
 
 
 @end

其實,實現KVO的是這2個方法:willChangeValueForKey:和didChangevlueForKey:

我們只要調用這兩個方法,屬性值不變化,也會被觀察到。

1、給Person類添加一個方法kvoTest:

 #import <Foundation/Foundation.h>
 
 /**
  Person模型類
  */
 @interface Person : NSObject
 
 /**
  姓名
  */
 @property(nonatomic,copy) NSString *name;
 
 /**
  年齡
  */
 @property(nonatomic,assign) NSInteger age;
 
 /**
  監聽age屬性發生變化
  */
 - (void)kvoTest;
 
 @end
 #import "Person.h"
 
 @implementation Person
 
 - (void)kvoTest{
     
     [self willChangeValueForKey:@"age"];
     
     [self didChangeValueForKey:@"age"];
 }
 
 @end

2、調用kvoTest方法監聽對象屬性age的變化:

 #import "ViewController.h"
 #import "Person.h"
 
 @interface ViewController ()
 
 @end
 
 @implementation ViewController
 
 - (void)viewDidLoad {
     [super viewDidLoad];
     
     Person *per = [[Person alloc] init];
     per.name = @"xiaoming";
     per.age = 18;
     
     // 觀察per對象的age屬性的變化情況
     [per addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew  context:NULL];
     
     // kvoTest裡面調用了willChangeValueForKey:和didChangeValueForKey
     // 所以:不改變age的值,依舊會被監聽到
     [per kvoTest];
     
     // 這裡po per的結果:<Person: 0x608000026960>
     // 但是:po object_getClass(per)的結果:NSKVONotifying_Person
     // 所以:蘋果偷偷重寫了派生類的class方法,但用運行時可以查看當前對象所屬類型
     
     // 移除KVO
     [per removeObserver:self forKeyPath:@"age"];
 }
 
 /**
  KVO監聽:當被監聽對象的被監聽屬性變化後調用
  
  @param keyPath 被監聽的屬性
  @param object 被監聽的對象
  @param change 變化情況
  @param context 攜帶的參數,此示例傳入NULL即可
  */
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
 {
     NSLog(@"%@對象的%@屬性發生變化了%@",object,keyPath,change);
     
     // 打印結果:
     // <Person: 0x608000032ca0>對象的age屬性發生變化了{
     //     kind = 1;
     //     new = 18; // 因為age的值根本沒有發生變化,所是這裡的new還是18
     // }
 }
 
 
 @end
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved