【iOS】weak修飾符

前言

??前面我們已經學習了解了sideTable,今天來看看在OC中,sideTable是如何在我們使用weak時工作的。在OC中,weak修飾符是一種用于聲明“弱引用”的關鍵字,其核心特性是不參與對象的引用計數管理,而且當被引用的對象被釋放時,weak指針會自動置為nil(避免野指針)。為探究weak的工作原理和底層邏輯,筆者特寫此篇來記錄對weak的學習。

引用計數與sideTable

??OC的內存管理是基于引用計數的(ARC 下由編譯器自動生成引用計數增減代碼)。對象的isa指針指向其類信息,而對象的內存分布中通常包含:

  • isa指針:指向對象的所屬類或元類。
  • 引用計數相關數據:早期直接存儲在對象內存頭部,后來為了減少內存碎片,現將引用計數和弱引用信息分離到sideTable中。

sideTable是什么

??sideTable是一個輔助數據結構(本質是哈希表),用于存儲與對象關聯的元數據,包括引用計數表、弱引用表和其它元數據(如關聯對象、關聯引用等)

引用計數表(RefcountMap):記錄對象的引用計數值(分散存儲,避免每個對象都占用額外空間)。

弱引用表(WeakMap):記錄所有指向該對象的 weak指針地址(鍵為對象地址,值為 weak指針的集合)。

沒個對象可能共享一個sideTable(通過哈希計算映射),因此sideTable的內存開銷備份談到多個對象上。

weak修飾符的底層實現原理

首先,我們來看看調用weak時的底層調用。

請添加圖片描述
請添加圖片描述

weak工作流程第二階段

我們在調用weak的地方打上斷點,然后進行匯編代碼調試,然后我們能發現,weak調用了一個objc_storeWeak方法:
請添加圖片描述

然后我們在obj4-906-main源碼中找到了這部分方法的底層實現:

請添加圖片描述

這其實是weak的賦值階段,用于將weak指針的地址注冊到目標對象的弱引用表中。

這個函數中的參數含義如下:

  • location:指向 weak變量的指針(即存儲 weak指針的內存地址)。例如,有一個 __weak NSObject *obj;,則 &obj就是 location的值。
  • newObj:要賦值給 weak變量的新對象(可能為 nil)。
  • 返回值:返回舊值(即 location原來的對象,若未修改則為 nil)。

返回值中,有三個參數控制 storeWeak函數的行為邏輯:

DoHaveOld:是否處理舊值

若為 true,函數會先檢查 location原有的舊值(即之前指向的對象),并從該舊值的弱引用表中移除當前 location地址(避免舊對象釋放時錯誤地清理已失效的 weak指針);

若為 false,則跳過舊值處理(適用于首次賦值或無需清理舊值的場景)。

DoHaveNew:是否處理新值

若為 true,函數會將 newObj的地址注冊到其對應的弱引用表中(即把location地址添加到 newObj的弱引用表 referrers數組中);

若為 false,則跳過新值處理(適用于清空 weak指針的場景,如 obj = nil)。

DoCrashIfDeallocating:對象釋放時是否崩潰

若為 true,當 newObj正在被釋放(deallocating狀態)時,函數會觸發崩潰(避免向已釋放對象注冊弱引用);

若為 false,則允許向正在釋放的對象注冊弱引用(但后續對象釋放時會清理該 weak指針)。

storeWeak函數源碼:

storeWeak(id *location, objc_object *newObj)
{ASSERT(haveOld  ||  haveNew);if (!haveNew) ASSERT(newObj == nil);Class previouslyInitializedClass = nil;id oldObj;SideTable *oldTable;SideTable *newTable;// Acquire locks for old and new values.// Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us.retry:if (haveOld) {oldObj = *location;oldTable = &SideTables()[oldObj];} else {oldTable = nil;}if (haveNew) {newTable = &SideTables()[newObj];} else {newTable = nil;}SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);if (haveOld  &&  *location != oldObj) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);goto retry;}// Prevent a deadlock between the weak reference machinery// and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa.if (haveNew  &&  newObj) {Class cls = newObj->getIsa();if (cls != previouslyInitializedClass  &&  !((objc_class *)cls)->isInitialized()) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);class_initialize(cls, (id)newObj);// If this class is finished with +initialize then we're good.// If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself)// then we may proceed but it will appear initializing and // not yet initialized to the check above.// Instead set previouslyInitializedClass to recognize it on retry.previouslyInitializedClass = cls;goto retry;}}// Clean up old value, if any.if (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);}// Assign new value, if any.if (haveNew) {newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);// weak_register_no_lock returns nil if weak store should be rejected// Set is-weakly-referenced bit in refcount table.if (!_objc_isTaggedPointerOrNil(newObj)) {newObj->setWeaklyReferenced_nolock();}// Do not set *location anywhere else. That would introduce a race.*location = (id)newObj;}else {// No new value. The storage is not changed.}SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);// This must be called without the locks held, as it can invoke// arbitrary code. In particular, even if _setWeaklyReferenced// is not implemented, resolveInstanceMethod: may be, and may// call back into the weak reference machinery.callSetWeaklyReferenced((id)newObj);return (id)newObj;
}

weak工作流程第一階段

剛剛說objc_storeWeak方法是weak的賦值和持有階段,這是調用weak的第二階段,在這之前還有一個聲明階段即初始化階段。

初始化時,runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址。

源碼如下:

請添加圖片描述

weak工作流程第三階段

weak工作流程的第三階段就是對象釋放階段(清楚弱引用表并置nil):

請添加圖片描述

objc_object::clearDeallocating()函數,它是一個內聯函數,內部直接調用了sidetable_clearDeallocating()。這說明clearDeallocating()是對外暴露的接口,而sidetable_clearDeallocating()是具體的實現細節,負責實際的清理操作。

請添加圖片描述

這個函數的主要操作是處理SideTable中的弱引用表和相關元數據。步驟包括:

  1. 獲取SideTable:通過SideTables()[this]獲取當前對象對應的SideTable實例。
  2. 加鎖:使用table.lock()確保線程安全,避免多線程同時修改SideTable導致數據競爭。
  3. 查找引用計數項:在table.refcnts(引用計數表)中查找當前對象的迭代器it。
  4. 檢查弱引用標記:如果找到迭代器且其值包含SIDE_TABLE_WEAKLY_REFERENCED標志(表示該對象有弱引用需要處理),則調用weak_clear_no_lock函數清理弱引用表。
  5. 清理引用計數項:從refcnts中刪除當前對象的條目。
  6. 解鎖:釋放SideTable的鎖,確保其他線程可以繼續操作。

weak的本質

  1. 運行時維護的弱引用跟蹤機制

weak的本質是運行時通過SideTable動態跟蹤對象與weak指針的關聯關系。其核心特性(不參與引用計數、自動置nil)均由以下機制支撐:

  • 不參與引用計數:weak賦值時不調用retain,對象釋放時不依賴weak指針的計數。
  • 自動置nil:通過SideTable中的弱引用表,在對象釋放時主動遍歷并清理所有關聯的weak指針地址。
  1. 弱引用表的集中管理

Weak(即__weak修飾的指針)的本質是運行時在SideTable中維護的一張弱引用表(weak_table_t)。該表存儲了所有指向當前對象的weak指針地址(referrers數組),是對象釋放時定位并清理weak指針的核心依據。

weak置nil

weak指針置nil的關鍵操作發生在對象釋放階段,由sidetable_clearDeallocating函數觸發:

  1. 對象調用dealloc后,執行objc_object::clearDeallocating(內聯函數),調用sidetable_clearDeallocating
  2. sidetable_clearDeallocating獲取對象的SideTable并加鎖,檢查引用計數映射(refcnts)中是否存在SIDE_TABLE_WEAKLY_REFERENCED標記(表示被weak引用過)。
  3. 若存在,調用weak_clear_no_lock函數,遍歷該對象的弱引用表(weak_table_t.referrers),將每個weak指針的地址(entry->referrers)處的值置為nil(本質是修改內存為0)。
  4. 清理完成后,刪除引用計數映射項并解鎖SideTable,完成weak指針的置nil操作。

總結

??weak的實現以SideTable和弱引用表為核心,通過運行時動態跟蹤對象與weak指針的關聯關系,在對象釋放時主動清理所有weak指針并置nil,既避免了循環引用導致的內存泄漏,又保證了內存訪問的安全性,其本質是運行時維護的弱引用跟蹤機制。

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

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

相關文章

【JVM篇10】:三種垃圾回收算法對比詳解

文章目錄1. 標記-清除算法2. 復制算法3. 標記-整理算法總結與面試要點在通過 可達性分析等算法識別出所有存活對象和垃圾對象后&#xff0c;垃圾收集器&#xff08;GC&#xff1a;Garbage Collector&#xff09;就需要執行回收操作來釋放垃圾對象所占用的內存。以下是三種最基礎…

JXD進步25.7.30

1.為啥是update&#xff0c;因為你if判斷有問題。或者是你上來就給id賦值了。2. 這個是清空network歷史3.斷點位置打在這里&#xff1a;打在上面它進不來4.

Flutter開發實戰之網絡請求與數據處理

第6章:網絡請求與數據處理 “數據是應用的血液,網絡是連接世界的橋梁。” 在移動應用開發中,與服務器進行數據交互是必不可少的功能。無論是獲取用戶信息、提交表單數據,還是上傳圖片、下載文件,都離不開網絡請求。本章將帶你深入掌握Flutter中的網絡編程技巧。 6.1 網絡…

快速分頁實現熱點功能-索引和order by

需求:分頁求出進三天的發布視頻的權重熱度 權重 / 衰減時間 衰減時間 當前時間 - 視頻發布時間 小根堆來實現這個公式可以很好的利用半衰期來進行解決難點:如果一次性加載太多到springBoot服務器里面會造成堆內存占用過多&#xff0c;分頁又有可能造成深分頁問題&#xff0c;…

HAProxy(高可用性代理)

1 HAProxy 簡介 HAProxy&#xff08; High Availability Proxy&#xff09;是一個高性能的負載均衡器和代理服務器&#xff0c;為基于 TCP 和 HTTP 的應用程序提供高可用性、負載平衡和代理&#xff0c;廣泛應用于提高 web 應用程序的性能和可靠性。它支持多種協議&#xff0c…

Vulnhub靶場:ica1

一、信息收集nmap掃描一下IP。&#xff08;掃不出來的可以看一下前面幾篇找ip的步驟&#xff09;下面給了框架的版本是9.2的&#xff0c;我們去kali里搜一下有沒有已經公開的漏洞。searchsploit qdPM 9.2 locate 50176.txt more /usr/share/exploitdb/exploits/php/webapps/50…

【Dv3admin】ORM數據庫無法查詢的問題

Django 運行過程中&#xff0c;數據庫連接的健康狀態直接影響應用的穩定性和數據訪問準確性。長時間空閑的數據庫連接經常因外部機制被回收&#xff0c;進而引發數據查詢異常和返回無效結果。 本文圍繞 Django 中數據庫連接長時間空閑導致的連接失效問題&#xff0c;介紹相關的…

使用 Flownex 對機械呼吸機進行建模

當患者無法獨立呼吸時&#xff0c;機械呼吸機通過氣管插管將富氧空氣輸送到患者的肺部。肺是敏感而復雜的器官&#xff0c;因此在無法忍受的壓力和體積范圍內提供空氣&#xff0c;根據每分鐘所需的呼吸次數計時&#xff0c;并適當加濕和加熱。機械呼吸機的精確建模對于其安全有…

力扣刷題日常(7-8)

力扣刷題日常(7-8) 第7題: 整數反轉(難度: 中等) 原題: 給你一個 32 位的有符號整數 x ,返回將 x 中的數字部分反轉后的結果. 如果反轉后整數超過 32 位的有符號整數的范圍 [?231, 231 ? 1] ,就返回 0. 假設環境不允許存儲 64 位整數&#xff08;有符號或無符號&#xff09;.…

串口接收數據包(協議帶幀頭幀尾)的編程實現方法:1、數據包格式定義結構體2、使用隊列進行數據接收、校驗解包

這種帶幀頭幀尾的數據包處理流程可以簡單概括為 “識別邊界→提取有效數據→驗證完整性” 三個核心步驟&#xff0c;具體操作如下&#xff1a;1. 數據包格式定義&#xff08;先約定規則&#xff09;首先明確一個 “合格數據包” 的結構&#xff0c;比如&#xff1a; 幀頭&#…

JSON 對象封裝教程

JSON 對象封裝方法在 Java 中封裝 JSON 對象通常使用第三方庫&#xff0c;如 org.json、Gson 或 Jackson。以下是幾種常見的方法&#xff1a;使用 org.json 庫添加 Maven 依賴&#xff1a;<dependency><groupId>org.json</groupId><artifactId>json<…

【WRF-Chem】EDGAR 排放數據處理:分部門合并轉化為二進制(Python全代碼)

目錄 process.py process_biofl.py process_fossil.py process_micro.py process_sector.py 參考 process.py 讀取 EDGAR 排放數據庫中 2000 至 2023 年間不同行業的甲烷(CH?)排放數據,進行合并處理,并將總排放以二進制格式保存到文件中。 導入必要的庫 import numpy as n…

【學習過程記錄】【czsc】1、安裝

文章目錄 背景 安裝 安裝python 安裝czsc 功能測試 附錄 奇葩的報錯 背景 詳見: https://github.com/waditu/czsc 安裝 安裝python !重要!作者強調,python必須是大于等于3.8 為此呢,我也是花了一點時間裝了一個python3.13。 安裝czsc 關于czsc的安裝呢,官方也是給出…

Python批量生成N天前的多word個文件,并根據excel統計數據,修改word模板,合并多個word文件

1&#xff0c;需求 根據word模板文件&#xff0c;生成多個帶日期后綴的word文件根據excel-每日告警統計數量&#xff0c;逐個修改當日的文檔2&#xff0c;實現 shell腳本&#xff1a;根據word模板文件&#xff0c;生成多個帶日期后綴的word文件 #!/bin/bash # 生成近一年日期 …

基于uni-app的血糖血壓刻度滑動控件

想要做一個基于uni-app的血糖血壓刻度滑動控件&#xff0c;hbuilder市場沒有好的&#xff0c;參照別人的寫了一個。如圖&#xff1a;源碼&#xff0c;自己放入components里面。<!-- 刻度滑動選擇 --> <template><view><view class"slide-title"…

C語言(02)——標準庫函數大全(持續更新)

想要了解更多的C語言知識&#xff0c;可以訂閱下面的專欄&#xff0c;里面也有很多品質好文&#xff1a; 打怪升級之路——C語言之路_ankleless的博客-CSDN博客 還在持續更新中&#xff0c;以下是學習過程中遇到的一些庫函數&#xff08;排序不分先后&#xff09;&#xff1a…

永磁同步電機無速度算法--靜態補償電壓模型Harnefors觀測器

一、原理介紹本文基于Harnefors教授提出的靜態補償電壓模型&#xff0c;可以實現帶載零速啟動、正反轉切換等功能&#xff0c;原理清晰&#xff0c;實現簡便。二、仿真模型在MATLAB/simulink里面驗證所提算法&#xff0c;搭建仿真。采用和實驗中一致的控制周期1e-4&#xff0c;…

[SKE]Python gmssl庫的C綁定

Python gmssl庫的C綁定 摘要:本文展示gmssl庫的C綁定,并給出完整代碼。將參考模型從Python腳本遷移到純C代碼中使用gmssl庫(TongSuo項目,支持國密算法如SM4,同時兼容AES、DES、3DES、RSA等)。這樣,UVM(SystemVerilog)可以通過DPI-C直接調用C函數,而無需嵌入Py…

4.方法的使用

方法是指一段具有獨立功能的代碼塊&#xff0c;只有被調用時才會執行方法的主要作用體現在&#xff1a;代碼組織&#xff1a;將原本擠在一起的臃腫代碼按照功能進行分類管理例如&#xff1a;將用戶注冊的驗證邏輯、數據庫操作、結果返回等分離成不同方法提高復用性&#xff1a;…

day21-Excel文件解析

目錄 1. 概述 2. Apache POI 3. XSSF解析Excel文件 3.1. 添加Jar包依賴 3.2. Workbook&#xff08;Excel文件&#xff09; 3.2.2. 加載&#xff08;解析&#xff09;Excel文件 3.3. Sheet &#xff08;工作簿&#xff09; 3.3.1. 創建工作簿 3.3.2. 獲取工作簿 3.3.3.…