「iOS」——KVO

源碼學習

  • iOS底層學習:KVO 底層原理
    • KVO
      • 注冊 KVO 監聽
      • 實現 KVO 監聽
      • 移除 KVO 監聽
      • 處理變更通知
      • 手動KVO(禁用KVO)
        • 關閉自動通知
        • 手動實現 setter 方法
      • KVO 和線程
        • 如果 KVO 是多線程的
        • **單線程的保證**
        • 如果沒有 prior 選項
        • **prior 選項的作用**
    • KVO 實現原理
      • 派生類重寫的方法
      • 驗證 isa 指向示例
      • KVO 注意事項
      • 問題總結


iOS底層學習:KVO 底層原理

KVO

KVO 的全稱是 KeyValueObserving,俗稱 “鍵值監聽 ",可以用于監聽某個對象屬性值的改變;KVO 可以通過監聽 key,來獲得 value 的變化,用來在對象之間監聽狀態變化。

基本思想:對目標對象的某屬性添加觀察,當該屬性發生變化時,通過觸發觀察者對象實現的 KVO 接口方法,來自動的通知觀察者。

KVO 是蘋果提供的一套事件通知機制。KVO 和 NSNotificationCenter 都是 iOS 中觀察者模式的一種實現,區別是:NSNotificationCenter 可以是一對多的關系,而 KVO 是一對一的;

注冊 KVO 監聽

通過[addObserver:forKeyPath:options:context:]方法注冊 KVO,這樣可以接收到 keyPath 屬性的變化事件:

  • observer:觀察者,監聽屬性變化的對象。該對象必須實現observeValueForKeyPath:ofObject:change:context:方法。

  • keyPath:要觀察的屬性名稱,需與屬性聲明的名稱一致。

  • options:回調方法中收到被觀察者的屬性的舊值或新值等,對 KVO 機制進行配置,修改 KVO 通知的時機以及通知的內容。

  • context:上下文,會傳遞到觀察者的函數中,用于區分消息,應當為不同值。

options所包括的內容:

  • NSKeyValueObservingOptionNew:change 字典包括改變后的值。

  • NSKeyValueObservingOptionOld:change 字典包括改變前的值。

  • NSKeyValueObservingOptionInitial:注冊后立刻觸發 KVO 通知。

  • NSKeyValueObservingOptionPrior:值改變前是否也要通知(決定是否在改變前、改變后通知兩次)。

實現 KVO 監聽

通過方法[observeValueForKeyPath:ofObject:change:context:]實現 KVO 的監聽:

- (void)observeValueForKeyPath:(NSString *)keyPath  ofObject:(id)object  change:(NSDictionary *)change  context:(void *)context  
  • keyPath:被觀察對象的屬性。

  • object:被觀察的對象。

  • change:字典,存放相關的值,根據options傳入的枚舉返回新值、舊值。

  • context:注冊觀察者時傳遞的context值。

移除 KVO 監聽

通過方法[removeObserver:forKeyPath:]移除監聽;

處理變更通知

每當監聽的 keyPath 發生變化時,會在observeValueForKeyPath函數中回調

- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary *)changecontext:(void *)context

change字典保存了變更信息,具體內容取決于注冊時的NSKeyValueObservingOptions

手動KVO(禁用KVO)

KVO 的實現是在注冊的 keyPath 的 setter 方法中,自動插入并調用了兩個函數:

- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key

手動實現 KVO 需先關閉自動生成 KVO 通知,再手動調用通知方法,可靈活添加判斷條件。

關閉自動通知
 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {  if ([key isEqualToString:@"age"]) {  return NO;  } else {  return [super automaticallyNotifiesObserversForKey:key];  }  
}  
手動實現 setter 方法

接著手動實現屬性的 setter 方法,在setter方法中先調用willChangeValueForKey:接著進行賦值操作,然后調用didChangeValueForKey:

- (void)setAge:(int)theAge {  [self willChangeValueForKey:@"age"];  age = theAge;  [self didChangeValueForKey:@"age"];  
}  

KVO 和線程

  • KVO 行為是同步的,在所觀察的值發生變化的同一線程上觸發,無隊列或 Runloop 處理。

  • 手動或自動調用didChangeValueForKey:會觸發 KVO 通知。

  • 單線程保證(如主隊列):

  1. 確保所有監聽某一屬性的觀察者在 setter 方法返回前被通知到。

  2. 若鍵觀察時附上NSKeyValueObservingOptionPrior選項,直到observeValueForKeyPath被調用前,監聽的屬性返回值不變。

    1. 該鍵對應的值是一個 NSNumberBOOL 類型),用于判斷當前 KVO 通知是在屬性值 變更前(前置通知,值為 YES)還是 變更后(后置通知,值為 NO)發送。

上述兩個特點可以有效解決復雜場景下的數據一致性時序問題

我們看以下代碼:

// User.h
@interface User : NSObject
@property (nonatomic, assign) NSInteger age;
@end// ViewController.m
- (void)viewDidLoad {[super viewDidLoad];// 注冊 KVO 監聽[self.user addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];// 主線程修改 agedispatch_async(dispatch_get_main_queue(), ^{self.user.age = 20;NSLog(@"主線程修改 age 為 20");});// 子線程同時修改 agedispatch_async(dispatch_get_global_queue(0, 0), ^{self.user.age = 30;NSLog(@"子線程修改 age 為 30");});
}// KVO 回調
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {if ([keyPath isEqualToString:@"age"]) {NSNumber *newAge = change[NSKeyValueChangeNewKey];NSLog(@"KVO 接收到 age 變化: %@", newAge);}
}
如果 KVO 是多線程的
  • 可能出現通知丟失:主線程和子線程同時修改 age,觀察者可能只收到最后一次通知(如只收到 30,丟失 20)。
  • 可能出現通知順序錯亂:觀察者先收到 30 的通知,再收到 20 的通知,導致邏輯混亂。
單線程的保證
  • 原子性:KVO 會在 setter 方法返回前同步且順序地通知所有觀察者,確保:
    1. 所有觀察者都能收到每一次變化。
    2. 通知順序與 setter 調用順序一致(先收到 20,再收到 30)。

再來學習NSKeyValueObservingOptionPrior。該屬性主要應用在復雜數據更新與 UI 動畫同步

// 注冊 KVO,帶上 prior 選項
[self.dataSource addObserver:self forKeyPath:@"items" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionPrior) context:nil];// KVO 回調
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {if ([keyPath isEqualToString:@"items"]) {// 1. 先收到 prior 通知(change[NSKeyValueChangeNotificationIsPriorKey] = @YES)if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {// 準備動畫(此時 items 還是舊值)[self.tableView beginUpdates];} // 2. 再收到實際變化通知else {// 執行動畫(此時 items 已是新值)[self.tableView endUpdates]; // 自動觸發 insert/delete 動畫}}
}
如果沒有 prior 選項
  • 直接在 endUpdates 時才知道數據變化,無法提前準備動畫。
  • 可能導致 UI 閃爍或動畫不連貫。
prior 選項的作用
  • 分階段通知
    1. 第一次通知:在屬性值實際變更前觸發(NSKeyValueChangeNotificationIsPriorKey = @YES),此時屬性值仍為舊值。
    2. 第二次通知:在屬性值變更后觸發(默認行為),此時屬性值已更新。
  • 實際應用
    • 在第一次通知時,計算新舊數據的差異(如哪些行需要插入 / 刪除)。
    • 在第二次通知時,執行 beginUpdates/endUpdates,讓表格視圖平滑過渡。

KVO 實現原理

KVO 通過isa-swizzling實現,基本流程如下:

isa-swizzling 的本質:

修改對象的類型:通過修改對象的 isa 指針,使其指向另一個類,從而改變對象的行為。

  1. 創建派生類:編譯器自動為被觀察對象創建派生類(如NSKVONotifying_XXX),將被觀察實例的isa指向該派生類,派生類的superclass指向原類。

  2. 重寫方法:若注冊了某屬性的觀察,派生類會重寫該屬性的 setter 方法,并添加通知代碼。

  3. 消息傳遞:Objective-C 通過isa指針找到對象所屬類,調用派生類重寫后的方法,觸發通知。

派生類重寫的方法

  • setter 方法:插入willChangeValueForKey:didChangeValueForKey:調用,觸發通知。
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

然后在 didChangeValueForKey: 中,去調用:

- (void)observeValueForKeyPath:(nullable NSString *)keyPathofObject:(nullable id)objectchange:(nullable NSDictionary<NSKeyValueChangeKey, id> *)changecontext:(nullable void *)context;
  • class 方法:返回原類,隱藏子類存在,避免isKindOfClass判斷異常。
- (Class)class {  return class_getSuperclass(object_getClass(self));  
}  
  • dealloc 方法:釋放 KVO 相關資源。

  • _isKVOA 方法:返回YES,標識該類為 KVO 生成的子類。

請添加圖片描述

驗證 isa 指向示例

#import <Foundation/Foundation.h>  
#import <objc/runtime.h>  @interface ObjectA: NSObject  
@property (nonatomic) NSInteger age;  
@end  @implementation ObjectA  
@end  @interface ObjectB: NSObject  
@end  @implementation ObjectB  
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {  NSLog(@"%@", change);  
}  
@end  int main(int argc, const char * argv[]) {  @autoreleasepool {  ObjectA *objA = [[ObjectA alloc] init];  ObjectB *objB = [[ObjectB alloc] init];  [objA addObserver:objB forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];  NSLog(@"%@", [objA class]); // 輸出:ObjectA(表面類型)  NSLog(@"%@", object_getClass(objA)); // 輸出:NSKVONotifying_ObjectA(實際類型)  }  return 0;  
}  
  • class方法返回對象所屬的類(原類)。

  • object_getClass返回對象的isa指向的實際類(派生類)。

請添加圖片描述

KVO 注意事項

  1. 內存管理
  • addObserverremoveObserver需成對調用,避免觀察者釋放后仍接收通知導致 Crash。

  • KVO 不對觀察者強引用,需注意觀察者生命周期。否則會導致觀察者被釋放帶來的Crash。

  1. 方法實現:觀察者必須實現observeValueForKeyPath:ofObject:change:context:方法,否則崩潰。

  2. KeyPath 安全:在調用KVO時需要傳入一個keyPath,由于keyPath是字符串的形式,所以其對應的屬性發生改變后,字符串沒有改變容易導致Crash。我們可以利用系統的反射機制將keyPath反射出來,這樣編譯器可以在@selector()中進行合法性檢查。

  3. 數組監聽:默認僅監聽數組對象本身變化,需通過mutableArrayValueForKey操作數組或手動觸發通知來監聽元素變化。

問題總結

  1. **直接修改成員變量是否觸發 KVO?**不會。KVO 本質是替換 setter 方法,僅通過 setter 或 KVC 修改屬性值時觸發。

  2. **KVC 修改屬性會觸發 KVO 嗎?**會。setValue:forKey:會調用willChangeValueForKeydidChangeValueForKey,觸發監聽器回調。

  3. 如何監聽數組元素變化?

  • 使用NSMutableArray并通過mutableArrayValueForKey獲取數組,其操作會自動觸發通知。

  • 手動調用willChangeValueForKeydidChangeValueForKey觸發通知。

當數組中的元素發生變化時,手動觸發KVO通知即可實現監聽。具體實現方式如下:

使用NSKeyValueObservingOptionNewNSKeyValueObservingOptionOld選項來監聽可變數組中的元素變化。這兩個選項會在KVO通知中包含新舊值的信息,因此可以在觀察者中獲取到數組中元素的變化。
代碼如下:

[observedObject addObserver:self forKeyPath:@"myArray" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
//觀察者中實現.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {if ([keyPath isEqualToString:@"myArray"]) {NSArray *oldArray = change[NSKeyValueChangeOldKey];NSArray *newArray = change[NSKeyValueChangeNewKey];// 處理數組元素的變化}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/90169.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/90169.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/90169.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Unreal5從入門到精通之使用 Python 編寫虛幻編輯器腳本

文章目錄 前言 如何運行Python 1.控制臺 2.藍圖調用python python 入門 變量 數據類型 運算符 條件判斷 循環 函數 模塊引用 類型轉換 類 類方法 繼承 構造函數 unreal API 創建材質 創建材質實例 獲取Content下選中資源 獲取關卡中選中Actors 放置Cube 編輯器進度條 展示對話框…

Django3 - Web前端開發基礎 HTML、CSS和JavaScript

網站開發可以分為前端開發和后端開發&#xff0c;前端開發是指網頁設計&#xff0c;我們在瀏覽器看到網站的圖片、文字、音樂視頻等內容排版都是由前端開發人員實現的&#xff1b;后端開發是為前端開發提供實際的數據內容和業務邏輯&#xff0c;比如提供文字內容、圖片和音樂視…

Nginx和Apache的區別

一。Nginx和Apache的優缺點和對比Nginx 優點Apache 優點性能與并發采用事件驅動模型&#xff0c;支持 10 萬 高并發連接&#xff0c;資源&#xff08;CPU / 內存&#xff09;占用極低生態成熟&#xff0c;內置模塊可直接處理動態內容&#xff0c;無需依賴第三方程序配置與部署…

前端實現可編輯腦圖的方案

前端實現可編輯腦圖的方案 實現可編輯腦圖(Mind Map)在前端有多種方案&#xff0c;以下是一些主流的技術方案&#xff1a; 1. 基于現有開源庫的方案 JavaScript 庫 MindElixir: 輕量級開源腦圖庫&#xff0c;支持節點增刪改、拖拽、導入導出等 GitHub: https://github.com/sssh…

7-大語言模型—指令理解:指令微調訓練+模型微調

目錄 1、指令微調的訓練過程 2、指令微調數據 2.1、“指令輸入” 2.2、“答案輸出” 3、指令微調數據的構建方法 3.1、手動構建&#xff1a;純人工 “出題 寫答案” 3.1.1、構建流程 3.1.1.1、定義任務類型 3.1.1.2、設計指令模板 3.1.1.3、人工標注響應 3.1.2、工…

服務器版本信息泄露-iis返回包暴露服務器版本信息

漏洞信息描述&#xff1a;服務器版本信息泄露 測試過程&#xff1a;訪問http://192.168.23.63&#xff0c;看返回包可以得知服務器版本信息 顯示暴露返回server版本信息 修復建議&#xff1a;限制返回包帶有服務器版本信息 如何隱藏IIS Web服務響應頭中的IIS Server版本信息…

rust嵌入式開發零基礎入門教程(二)

本教程的第二部分&#xff0c;我們將深入理解 Rust 語言的核心概念——所有權&#xff08;Ownership&#xff09;、借用&#xff08;Borrowing&#xff09;和生命周期&#xff08;Lifetimes&#xff09;。這些是 Rust 內存安全的基礎&#xff0c;也是初學者理解 Rust 最關鍵的部…

【黑產大數據】2025年上半年互聯網黑灰產趨勢年度總結

2025年上半年&#xff0c;互聯網黑灰產攻擊持續演化&#xff0c;呈現出更隱蔽、更智能、更產業化的趨勢。黑灰產從業人員數量繼續增長&#xff0c;攻擊資源、技術與作案場景全面升級。整體來看&#xff0c;2025年上半年黑灰產行業發生的幾大事件&#xff0c;也時刻印證了黑灰產…

低代碼/無代碼平臺如何重塑開發生態

低代碼/無代碼平臺通過降低技術門檻、提升開發效率、推動業務和IT深度融合重塑開發生態。 具體而言&#xff0c;低代碼/無代碼平臺極大降低了應用開發的技術門檻&#xff0c;使得非專業人員也能輕松構建業務應用。此外&#xff0c;它們通過可視化的開發模式&#xff0c;大幅提升…

ICA學習(2)

1.公式推導1.1兩個問題ICA算法會帶來2個不確定性&#xff1a;幅值不確定性和順序不確定性。1.2 推導觀測數據 x 是盲源 s 的線性混合&#xff1a;x As (1)此時&#xff0c;W矩陣是未知的&#xff0c;ICA算法的目的便是找到一個最優的矩陣W&#xff0c;實現對矩陣…

【愚公系列】《MIoT.VC》002-構建基本仿真工作站(布局一個基本工作站)

??【行業認證權威頭銜】 ? 華為云天團核心成員:特約編輯/云享專家/開發者專家/產品云測專家 ? 開發者社區全滿貫:CSDN博客&商業化雙料專家/阿里云簽約作者/騰訊云內容共創官/掘金&亞馬遜&51CTO頂級博主 ? 技術生態共建先鋒:橫跨鴻蒙、云計算、AI等前沿領域…

網絡協議相關

OSI七層模型包含物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層;TCP/IP四層模型將其簡化為網絡接口層、網絡層、傳輸層和應用層;映射關系:例如OSI的物理層和數據鏈路層對應TCP/IP的網絡接口層&#xff0c;主要處理MAC地址尋址和物理介質傳輸。協議模型對比兩者的…

【CNN】LeNet網絡架構

1.MLP多層感知機MLP&#xff08;Multilayer Perceptron&#xff09;&#xff0c;也是人工神經網絡&#xff08;ANN&#xff0c;Artificial Neural Network&#xff09;&#xff0c;是一種全連接多層感知機&#xff08;Multilayer Perceptron, MLP&#xff09;是一種前饋神經網絡…

VSCODE 禁用git 功能

第一步&#xff0c;打開設置第二步&#xff0c;搜 git:Enabled

Spring Boot05-熱部署

一、Spring Boot 啟動熱部署Spring Boot 啟動“熱部署&#xff08;Hot Deployment&#xff09;”&#xff0c;可以讓你在不重啟項目的情況下快速看到代碼變更的效果&#xff08;特別是前后端調試階段&#xff09;。1-1、什么是熱部署&#xff1f;熱部署是指&#xff1a;修改 Ja…

網站域名備案和服務器有關系嗎

域名備案的那些事兒域名備案&#xff0c;簡單來說&#xff0c;就是把你的網站信息登記到相關管理部門那里。這就好比你開個小店&#xff0c;得去工商局登記一下&#xff0c;讓人家知道你在干啥。根據我國相關規定&#xff0c;凡是使用大陸境內服務器提供服務的網站&#xff0c;…

2025華為ODB卷-推薦多樣性200分-三語言題解

?? 華為OD機試真題精選 2025B卷合集 推薦多樣性200分 問題描述 A先生正在設計一個推薦系統,需要考慮多樣性,要求從多個列表中選擇元素。系統一次性需要返回 N N N 屏數據(窗口數量),每屏展示 K K

ZeroMQ源碼深度剖析:網絡機制與性能優化實戰

目錄1 發布訂閱過濾的高效實現2 ZeroMQ的核心優勢3 常見Socket類型及應用4 異步連接實現機制5 斷線重連機制6 高水位線&#xff08;HWM&#xff09;深度解析7 消息丟失與錯誤處理8 消息幀&#xff08;Frame&#xff09;高級特性9 高效性實現原理10 無鎖消息隊列設計11 零拷貝實…

[數據庫]Neo4j圖數據庫搭建快速入門

[數據庫]圖數據庫基礎入門 概念 圖數據庫是一種使用圖結構&#xff08;節點、邊和屬性&#xff09;進行數據存儲和查詢的數據庫管理系統。與傳統的關系型數據庫不同&#xff0c;圖數據庫專注于實體之間的關系&#xff0c;特別適合處理高度互聯的數據。常見的圖數據庫包括&#…

本地數據庫有數據,web頁面無信息顯示,可能是pymysql的版本問題【pymysql連接本地數據庫新舊版本的區別】

pymysql連接本地數據庫新舊版本的區別新版本老版本python web下的settings文件 新版本 的pymysql 連接本地數據庫&#xff1a; mysql_conn pymysql.connect(hostself.conn_infos["HOST"],userself.conn_infos["USER"],passwordself.conn_infos["PAS…