KVO

来源:http://www.cnblogs.com/EchoHG/archive/2017/06/15/7020441.html
-Advertisement-
Play Games

1.KVO概念 KVO即鍵值觀察,它提供一種機制,當被觀察的對象的屬性發生改變後,對象會接收到通知,從而做出相應的改變。 2.KVO實現原理 這裡要說一個isa指針,在Objective-C中,任何類的定義都是對象。類和類的實例(對象)沒有任何本質上的區別。任何對象都有isa指針。 那麼什麼是類呢? ...


1.KVO概念

KVO即鍵值觀察,它提供一種機制,當被觀察的對象的屬性發生改變後,對象會接收到通知,從而做出相應的改變。

2.KVO實現原理

  這裡要說一個isa指針,在Objective-C中,任何類的定義都是對象。類和類的實例(對象)沒有任何本質上的區別。任何對象都有isa指針。

  那麼什麼是類呢?在xcode中用快捷鍵Shift+Cmd+O 打開文件objc.h 能看到類的定義:

  

  可以看出:

  Class 是一個 objc_class 結構類型的指針, id是一個 objc_object 結構類型的指針.

  我們再來看看 objc_class 的定義:

 

 

  稍微解釋一下各個參數的意思:

  isa:是一個Class 類型的指針. 每個實例對象有個isa的指針,他指向對象的類,而Class里也有個isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當類方法被調用時,先會從本身查找類方法的實現,如果沒有,元類會向他父類查找該方法。同時註意的是:元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass).根元類的isa指針指向本身,這樣形成了一個封閉的內迴圈。

  super_class:父類,如果該類已經是最頂層的根類,那麼它為NULL。

  version:類的版本信息,預設為0

  info:供運行期使用的一些位標識。

  instance_size:該類的實例變數大小

  ivars:成員變數的數組

再來看看各個類實例變數的繼承關係:

 

  每一個對象本質上都是一個類的實例。其中類定義了成員變數和成員方法的列表。對象通過對象的isa指針指向類。

  每一個類本質上都是一個對象,類其實是元類(meteClass)的實例。元類定義了類方法的列表。類通過類的isa指針指向元類。

  所有的元類最終繼承一個根元類,根元類isa指針指向本身,形成一個封閉的內迴圈。

 

  原理:每一個對象都有一個isa指針,這個對象根據isa指針去尋找它所歸屬的類,當我們給一個對象註冊觀察者的時候,系統會在運行時給這個對象創建一個子類,這個子類繼承於當前對象歸屬的類,並把當前對象的isa指針指向這個子類,於是當前對象就變成了這個子類的一個實例。那麼這個子類內部做了什麼操作呢?其實這個子類重寫了set方法,當原對象在調用set方法賦值的時候,會根據isa指針到新建子類的方法列表去尋找set方法的IMP,此時這個重寫的set方法會對所有觀察這個屬性的對象發出通知,於是原有的對象會作出改變。

 

深入剖析:

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

 

  • NSKVONotifying_A類剖析:在這個過程,被觀察對象的 isa 指針從指向原來的A類,被KVO機制修改為指向系統新創建的子類 NSKVONotifying_A類,來實現當前類屬性值改變的監聽;
  • 所以當我們從應用層面上看來,完全沒有意識到有新的類出現,這是系統“隱瞞”了對KVO的底層實現過程,讓我們誤以為還是原來的類。但是此時如果我們創建一個新的名為“NSKVONotifying_A”的類(),就會發現系統運行到註冊KVO的那段代碼時程式就崩潰,因為系統在註冊監聽的時候動態創建了名為NSKVONotifying_A的中間類,並指向這個中間類了。
  • 因而在該對象上對 setter 的調用就會調用已重寫的 setter,從而激活鍵值通知機制。

 

  • KVO鍵值觀察依賴於NSObject的兩個方法:willChangeValueForKey和didChangevlueForKey,即在鍵值改變前後分別調用這兩個方法,然後在這兩個方法的中間調用父類set方法賦值。
  • 被觀察屬性發生改變之前,willChangeValueForKey:被調用,通知系統該 keyPath 的屬性值即將變更;當改變發生後, didChangeValueForKey: 被調用,通知系統該 keyPath 的屬性值已經變更;之後observeValueForKey:ofObject:change:context: 也會被調用。且重寫觀察屬性的setter 方法這種繼承方式的註入是在運行時而不是編譯時實現的。

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

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

 

 

示例驗證

 1 //Person類
 2 @interface Person : NSObject
 3 @property (nonatomic,copy) NSString *name;
 4 @end
 5 
 6 //controller
 7 Person *per = [[Person alloc]init];
 8 //斷點1
 9 [per addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
10 //斷點2
11 per.name = @"小明";
12 [per removeObserver:self forKeyPath:@"name"];
13 //斷點3
View Code

運行項目,

  • 斷點1位置:

 

 

  • 可以看到isa指向Person類,我們也可以使用lldb命令查看:
    (lldb) po [per class]
    Person
    (lldb) po object_getClass(per)
    Person
    (lldb)
    

 

  • 斷點2位置:

 

(lldb) po [per class]
Person
(lldb) po object_getClass(per)
NSKVONotifying_Person
(lldb)

 

  • 斷點3位置:
(lldb) po [per class]
Person
(lldb) po object_getClass(per)
Person
(lldb)

 

 

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

 

3.KVO的特點

由於KVO內部實現的原理是重寫了set方法,因此只有當被觀察對象的屬性調用set方法賦值的時候才會執行KVO的的回調方法。所以如果直接對屬性的成員變數直接賦值那麼不會觸發KVO。

4.KVO的調用步驟

1.註冊觀察者
2.在回調方法中處理事件
3.移除觀察者

5.代碼實踐

 1     self.changeStr = @"您好";
 2     [self addObserver:self forKeyPath:@"changeStr" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
 3     self.changeStr = @"大家都好";
 4 
 5 
 6 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
 7 {
 8     NSLog(@"被改變的屬性是%@",keyPath);
 9     NSString *str = [change   objectForKey:NSKeyValueChangeNewKey];
10     NSString *odlStr = [change   objectForKey:NSKeyValueChangeOldKey];
11     NSLog(@"舊屬性是%@",odlStr);
12     NSLog(@"新屬性是%@",str);
13 }
View Code

 

輸出結果:
  一個Demo:   在LYXItem.h文件
1 #import <Foundation/Foundation.h>
2 
3 @interface LYXItem : NSObject
4 
5 @property(nonatomic, copy) NSString *name;
6 @property(nonatomic, copy) NSString *price;
7 
8 @end

 

在LYXItemView.h文件

 1 #import <Foundation/Foundation.h>
 2 #import "LYXItem.h"
 3 
 4 @interface LYXItemView : NSObject
 5 
 6 @property(nonatomic, weak) LYXItem *item;
 7 
 8 - (void) showItemInfo;
 9 
10 @end

 

在LYXItemView.m中

 1 #import "LYXItemView.h"
 2 
 3 @implementation LYXItemView
 4 
 5 @synthesize item = _item;
 6 
 7 - (void)showItemInfo
 8 {
 9     NSLog(@"item名為:%@, 價格為: %@",self.item.name, self.item.price);
10 }
11 
12 
13 - (void)setItem:(LYXItem *)item
14 {
15     self -> _item = item;
16     //為item添加監聽器,監聽item的name的屬性的變化
17     [self.item addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
18     
19     [self.item addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionNew context:nil];
20 }
21 
22 
23 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
24 {
25     NSLog(@"---------------------------observeValueForKeyPath------------------------");
26     NSLog(@"被修改的keyPath為:%@",keyPath);
27     NSLog(@"被修改的對象為:%@",object);
28     NSLog(@"新被修改的屬性值是:%@",[change objectForKey:@"new"]);
29     NSLog(@"被修改的上下文是:%@",context);
30 }
31 
32 
33 @end
View Code

 

在運行文件中

 

 1     LYXItem *item = [[LYXItem alloc] init];
 2     item.name = @"IOS";
 3     item.price = @"6888";
 4     
 5     LYXItemView *lyxView = [[LYXItemView alloc] init];
 6     lyxView.item = item;
 7     [lyxView showItemInfo];
 8     
 9 //    更改item的值,觸發監聽器的方法
10     item.name = @"Android";
11     item.price =@"1999";

 

列印結果:

 

                   
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.層級選擇器 後代選擇器 "父元素 後代元素" 比如:$("div p") 選取div元素下所有的p元素 子元素選擇器 "父元素 > 子元素" 比如:$("div>p") 選取div的子元素中的p元素 第一個向後兄弟元素 "元素名+兄弟元素" 比如:$("div+p") 選取div元素後的第一個p ...
  • 一.Javascript的含義 是一種解釋性的語言,主要給網頁添加各色各樣的動態功能,同時為用戶提供瀏覽效果。 二.JavaScript的主要特點 三.JavaScript的組成 四.JavaScript的三種引入方式 1.標簽內引入2.內部引入3.外部引入 五.javaScript語法的基本要求 ...
  • nodejs中文網,官網同步翻譯 http://nodejs.cn/api/ Webpack 中文手冊(社區同步翻譯) http://6.course.uprogrammer.cn/webpack2 doc cn/index.html ECMAScript6 中文教程 https://www.w3c ...
  • XMPP詳解 XMPP(eXtensible Messaging and Presence Protocol,可擴展消息處理和現場協議)是一種在兩個地點間傳遞小型結構化數據的協議。在此基礎上,XMPP協議已經被用來構建大規模即時通信系統、游戲平臺、協作空間及語音和視頻會議系統。 XMPP由幾個小的構 ...
  • 一、用戶登錄流程 用戶登錄流程.png 註意:XMPP核心文件,基於TCP的XML流的傳輸,XMPPFrame框架是通過代理的方式實現消息傳遞的 實現用戶登錄的步驟如下: 1、實例化XMPPStream並設置代理,同時添加代理到工作隊列 2、使用JID連接至伺服器,預設埠為5222,JID字元串中 ...
  • 項目.xcodeproj 文件夾底下一般有4個文件: project.pbxproj 文件 xcuserdata 文件夾 xcshareddata 文件夾 project.xcworkspace 文件夾 (這裡暫不講解) 下麵主要講解涉及到 .xcschemes 相關文件,其他設置(例如斷點一類的) ...
  • 一,效果圖。 二,代碼。 RootViewController.h RootViewController.m ...
  • 先看效果: 使用ListView的ItemTemplateSelector 1、定義一個 HomeTemplateSelector繼承DataTemplateSelector, 並定義兩個數據模板AdTemplate,NewsTemplate,代碼如下 1 public class HomeData ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...