【iOS】方法與消息底層分析

目錄

前言

方法的本質

向不同對象發送消息

發送實例方法

發送類方法

對象調用方法 實際執行是父類

向父類發送類方法

消息查找流程

開始查找

快速查找流程

慢速查找流程

動態方法決議

應用場景

優化方案

消息轉發機制

快速轉發流程

應用場景

慢速轉發流程

應用場景


前言

在OC底層中,方法的調用實質上是通過消息的發送實現的,這篇文章我們來看一看消息的發送是怎么樣的

方法的本質

方法的本質就是通過objc_msgSend發送消息,有兩個參數,第一個是id類型,表示消息接受者,第二個,表示方法編號。

向不同對象發送消息

發送實例方法

消息接收者是實例對象

發送類方法

本質上是向類對象發送消息

objc_getClass得到的是類對象

對象調用方法 實際執行是父類

Runtime中提供了一個接口處理這種情況:父類中實現了該方法,而子類沒有實現該方法,子類對象調用方法,會執行父類中實現(符合繼承的特性)

這個接口是objc_msgSendSuper,使用時還需要用到objc_super結構體,并給結構體賦值(receiver、super_class)

該結構體中receiver表示接收消息的實例對象,super_class表示父類類對象,根據這個賦值

可以看到這兩種方式都是執行父類的實現,因此可以推斷:方法調用首先在類中查找,如果找不到就到父類中查找

向父類發送類方法

上面向父類發送實例方法時,receiver表示實例對象,super_class表示父類類對象。而如果向父類發送類方法,reciever表示類對象,super_class表示父類元類對象

消息查找流程

消息查找的流程就是通過上層的sel發送消息objc_msgSend找到底層具體imp的實現的過程,objc_msgSend是用匯編寫的而不是用C語言

開始查找

在開始objc_msgSend之后

  1. 首先會判斷消息接受者是否為空,為空就直接返回

  2. 然后會判斷是否為小對象,也就是是否為tagged_pointers

  3. 之后取對象中的isa存到寄存器p13中,根據isa進行mask地址偏移來得到對應的上級對象(類、元類)

取得了上級對象之后,就可以開始快速查找流程了,也就是在緩存中找imp的過程

快速查找流程

  1. 首先通過類的首地址偏移16字節找到cache的地址(cache離首地址16字節,isa占8字節,superclass占8字節),cache高16位存mask,低48位存buckets

  2. 然后從cache中分別取出buckets和mask,根據mask通過哈希算法算出哈希下標,根據哈希下標和bukets首地址來得到對應的bucket,bucket中存放著imp和sel

  3. 那么怎么確定找到的imp和sel就是要找的那個呢?主要是通過兩層循環:

    1. 第一層循環:比較bucket中的sel和objc_msgSend中第二個參數_cmd是否相等:如果相等,就直接跳轉到CacheHit,即緩存命中,返回imp;如果不相等,有三種情況:

      1. 一種是一直找不到,就直接跳轉到CheckMiss,因為參數$0是normal,會跳轉到__objc_msgSend_uncached,看英文就能明白意思就是沒找到,這時就會進入慢速查找流程

      2. 第二種是如果獲取到的bucket是第一個元素,那么就手動把它設置為最后一個元素,然后進行第二層循環

      3. 如果當前bucket不是第一個元素,那就繼續當前的循環

    2. 第二層循環:和第一層循環基本相同,只是如果bucket還是等于buckets中第一個元素,就直接跳轉到JumpMiss,此時也會跳轉到沒找到__objc_msgSend_uncached,進入慢速查找

慢速查找流程

慢速查找的過程分為匯編和C兩個部分,這里我們不糾結匯編部分,匯編最后調用的是lookUpImpOrForward,這是一個C實現的函數

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{// 定義的消息轉發const IMP forward_imp = (IMP)_objc_msgForward_impcache;IMP imp = nil;Class curClass;
?runtimeLock.assertUnlocked();
?if (slowpath(!cls->isInitialized())) {// The first message sent to a class is often +new or +alloc, or +self// which goes through objc_opt_* or various optimized entry points.//// However, the class isn't realized/initialized yet at this point,// and the optimized entry points fall down through objc_msgSend,// which ends up here.//// We really want to avoid caching these, as it can cause IMP caches// to be made with a single entry forever.//// Note that this check is racy as several threads might try to// message a given class for the first time at the same time,// in which case we might cache anyway.behavior |= LOOKUP_NOCACHE;}
?// runtimeLock is held during isRealized and isInitialized checking// to prevent races against concurrent realization.
?// runtimeLock is held during method search to make// method-lookup + cache-fill atomic with respect to method addition.// Otherwise, a category could be added but ignored indefinitely because// the cache was re-filled with the old value after the cache flush on// behalf of the category.
?//加鎖,目的是保證讀取的線程安全runtimeLock.lock();
?// We don't want people to be able to craft a binary blob that looks like// a class but really isn't one and do a CFI attack.//// To make these harder we want to make sure this is a class that was// either built into the binary or legitimately registered through// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.//判斷是否是一個已知的類:判斷當前類是否是已經被認可的類,即已經加載的類checkIsKnownClass(cls);
?//判斷類是否實現,如果沒有,需要先實現,此時的目的是為了確定父類鏈,方法后續的循環cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);// runtimeLock may have been dropped but is now locked againruntimeLock.assertLocked();curClass = cls;
?// The code used to lookup the class's cache again right after// we take the lock but for the vast majority of the cases// evidence shows this is a miss most of the time, hence a time loss.//// The only codepath calling into this without having performed some// kind of cache lookup is class_getInstanceMethod().//----查找類的緩存// unreasonableClassCount -- 表示類的迭代的上限//(猜測這里遞歸的原因是attempts在第一次循環時作了減一操作,然后再次循環時,仍在上限的范圍內,所以可以繼續遞歸)for (unsigned attempts = unreasonableClassCount();;) {if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHESimp = cache_getImp(curClass, sel);if (imp) goto done_unlock;curClass = curClass->cache.preoptFallbackClass();
#endif} else {// curClass method list.//---當前類方法列表(采用二分查找算法),如果找到,則返回,將方法緩存到cache中Method meth = getMethodNoSuper_nolock(curClass, sel);if (meth) {imp = meth->imp(false);goto done;}//當前類 = 當前類的父類,并判斷父類是否為nilif (slowpath((curClass = curClass->getSuperclass()) == nil)) {// No implementation found, and method resolver didn't help.// Use forwarding.//--未找到方法實現,方法解析器也不行,使用轉發imp = forward_imp;break;}}
?// Halt if there is a cycle in the superclass chain.// 如果父類鏈中存在循環,則停止if (slowpath(--attempts == 0)) {_objc_fatal("Memory corruption in class list.");}
?// Superclass cache.// --父類緩存imp = cache_getImp(curClass, sel);if (slowpath(imp == forward_imp)) {// Found a forward:: entry in a superclass.// Stop searching, but don't cache yet; call method// resolver for this class first.// 如果在父類中找到了forward,則停止查找,且不緩存,首先調用此類的方法解析器break;}if (fastpath(imp)) {// Found the method in a superclass. Cache it in this class.//如果在父類中,找到了此方法,將其存儲到cache中goto done;}}
?// No implementation found. Try method resolver once.//沒有找到方法實現,嘗試一次方法解析
?if (slowpath(behavior & LOOKUP_RESOLVER)) {//動態方法決議的控制條件,表示流程只走一次behavior ^= LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}
?done:if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHESwhile (cls->cache.isConstantOptimizedCache(/* strict */true)) {cls = cls->cache.preoptFallbackClass();}
#endif//存儲到緩存log_and_fill_cache(cls, imp, sel, inst, curClass);}done_unlock://解鎖runtimeLock.unlock();if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {return nil;}return imp;
}

上面是慢速查找的源碼,用自然語言來表述就是:

  1. 首先進行一次快速查找,也就是在cache緩存中查找,找到就直接返回imp,沒找到就繼續

  2. 先判斷cls是否是已知類,如果不是就報錯;再判斷類是否實現,如果沒實現需要先實現,這個時候實現的目的是為了確定它的父類鏈,ro以及rw等,方便之后數據讀取和查找;還要判斷是否初始化,沒有就初始化

  3. 接下來進入for循環,沿著類或元類的繼承鏈進行查找:

    1. 對于當前cls,在方法列表中使用二分查找進行查找,如果找到就進入cache寫入流程并返回imp,如果沒找到就返回nil

    2. 當前cls賦值為父類,如果父類為nil,imp = 消息轉發,并終止遞歸,開始判斷是否執行過動態方法解析

    3. 如果父類鏈中存在循環就報錯

    4. 在父類中查找時,會先在父類緩存中查找,再在方法列表中查找

  4. 判斷是否執行過動態方法解析,如果沒有就執行動態方法解析,執行過一次的話就走消息轉發流程

在二分查找過程中,如果找到的與key的value值相等,需要先排除分類方法

在進行完快速查找和慢速查找的流程之后,會進入動態方法決議和消息轉發流程

動態方法決議

在查找流程沒找到方法時,有一次機會補救就是動態方法決議,以實例方法為例,程序會走到resolveInstanceMethod方法:

用自然語言描述如下:

  1. 在發送resolveInstanceMethod消息前,先查找cls中有沒有這個方法的實現,也就是通過lookUpImpOrNil方法進入lookUpImpOrForward慢速查找流程找這個方法:

    1. 如果沒找到就直接返回

    2. 如果找到了就發送resolveInstanceMethod消息

  2. 再慢速查找實例方法的實現,又進行一次慢速查找

應用場景

使用動態方法決議可以解決一些方法未實現的報錯,重寫resolveInstanceMethod類方法并在其中將其指向其他方法的實現,比如有一個say666沒實現,但是實現了sayMaster方法

類方法同理,將方法名改為resolveClassMethod即可

優化方案

在上面的場景中,我們需要對每一個類的方法進行重寫,并且我們又知道慢速方法查找路徑最后都會走到根類,因此我們可以為NSObjct添加分類來統一處理

消息轉發機制

如果前面的過程都沒找到該方法,那我也是沒招了(bushi),那就會進行消息轉發流程,消息轉發流程分為快速轉發和慢速轉發,如果方法沒有實現而崩潰報錯,在崩潰之前會調用兩遍動態方法決議,兩遍快速轉發,兩遍慢速轉發

快速轉發流程

forwardingTargetForSelector在源碼中只有聲明,但是我們可以從幫助文檔中看到有關于它的解釋:

  • 該方法的返回對象是執行sel的新對象,也就是自己處理不了會將消息轉發給別的對象進行相關方法的處理,但是不能返回self,否則會一直找不到

  • 該方法的效率較高,如果不實現,會走到forwardInvocation:方法進行處理

  • 底層會調用objc_msgSend(forwardingTarget, sel, ...);來實現消息的發送

  • 被轉發消息的接受者參數、返回值等應和原方法相同

應用場景

比如TCJPerson沒實現的方法,轉發給實現了的TCJStudent

也可以直接調用父類的該方法,如果沒找到的話會直接報錯

慢速轉發流程

methodSignatureForSelector慢速查找流程同樣在幫助文檔中尋找,可以發現forwardInvocationmethodSignatureForSelector必須同時存在

底層會通過方法簽名生成一個NSInvocation,作為參數傳遞使用,接著查找可以響應NSInvocation中編碼的消息的對象,找到后使用anInvocation將消息發送給該對象,并且anInvocation保存結果,運行時系統將提取結果并傳遞給原始發送者

應用場景

慢速轉發的流程就是methodSignatureForSelector提供一個方法簽名,然后forwardInvocation通過NSInvocation來實現消息的轉發

無論在forwardInvocation方法中是否處理invocation事務,程序都不會崩潰

方法和消息的流程就到這里了,在上面的過程中你有沒有注意到動態方法決議進行了兩遍這個問題?它為什么會執行兩遍呢?

其實第二次動態方法決議是在methodSignatureForSelectorforwardInvocation方法之間,是開始進行慢速消息轉發之前再給的一次機會

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

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

相關文章

如何通過 WebSocket 接口訂閱實時外匯行情數據(PHP 示例)

步驟 1&#xff1a;準備工作確保已安裝 PHP 和 Composer安裝 WebSocket 客戶端庫&#xff1a;composer require textalk/websocket步驟 2&#xff1a;編寫代碼訂閱行情以下是最簡可運行的 PHP 示例&#xff0c;訂閱 EUR/USD 的 1分鐘K線數據&#xff1a;<?phprequire vendo…

第十八篇 數據清洗:Python智能篩選與統計:從海量Excel數據中秒級挖掘,輔助決策!你的數據分析利器!

Excel 數據挖掘Excel篩選復雜&#xff0c;統計耗時&#xff0c;無法快速挖掘數據價值1.數據篩選核心&#xff1a;df.loc與df.iloc&#xff0c;精準定位你想要的數據1.1基于條件篩選&#xff1a;過濾數據中的不恰當因素1.2 多條件組合篩選&#xff1a;精確鎖定目標數據1.3字符串…

小木的機器學習日記——KNN

核心知識點總結與星級排序我為你梳理了這節課的精髓&#xff0c;并按照重要性進行了星級評定&#xff08;★★★★★為最高&#xff09;。★★★★★ 核心思想&#xff1a;回歸 (Regression) 到底是什么&#xff1f;是否關鍵&#xff1a;是必須了解&#xff1a;是必須記住&…

Product Hunt 每日熱榜 | 2025-07-15

1. OpenArt One-Click Video Story 標語&#xff1a;一鍵即可將任何內容轉換為可隨時發布的視頻。 介紹&#xff1a;有一個創意、劇本、節奏&#xff0c;或者喜歡的角色嗎&#xff1f;OpenArt可以將它們變成一個視覺故事—完整的畫面、音樂和敘事結構&#xff0c;輕松實現&am…

Dubbo高階難題:異步轉同步調用鏈上全局透傳參數的丟失問題

?問題場景?&#xff1a; 在分布式電商系統中&#xff0c;下單服務通過Dubbo調用庫存服務&#xff08;異步接口返回CompletableFuture&#xff09;&#xff0c;同時在Gateway層通過RpcContext設置traceId。你發現&#xff1a;當庫存服務內部同步調用其他服務時&#xff0c;tra…

實測兩款效率工具:駕考刷題和證件照處理的免費方案

今天阿燦給大家推薦兩款實用的軟件&#xff0c;一款是駕考助手&#xff0c;另一款是證件照制作軟件。第一款&#xff1a;駕考助手以前考駕照&#xff0c;很多人擔心過不了關&#xff0c;還會花冤枉錢買VIP練習&#xff0c;精選500題。其實&#xff0c;只要用對工具&#xff0c;…

Python 函數的維護性與復用性

目錄 一、從“能跑就行”到“能改不怕”——維護性的第一要義 二、單一職責與最小驚訝——維護性的縱深防御 三、可組合的樂高——復用性的第一階梯 四、面向協議設計——復用性的第二階梯 五、異常策略與日志——維護性的隱形護盾 七、測試金字塔——維護性的最后護城河…

C++中的模板參數 vs 函數參數:編譯期與運行期的分界線

引言 在日常開發中&#xff0c;我們經常接觸 函數參數&#xff0c;這是控制函數行為的最直接方式。但在 C 中還有一種強大的機制 —— 模板參數&#xff08;Template Parameters&#xff09;&#xff0c;它賦予了我們在編譯期就生成代碼結構的能力。 本文將通過直觀的類比&…

Elasticsearch 9.x 搜索執行過程(源碼解析)

1. Elasticsearch 9.x 搜索執行過程 - 源碼解析 #mermaid-svg-Vp6WKKBLo3omajeq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Vp6WKKBLo3omajeq .error-icon{fill:#552222;}#mermaid-svg-Vp6WKKBLo3omajeq .error…

簡單易懂,操作系統的內存管理機制是如何實現的

系統地梳理一下操作系統在“內存管理”這個重要領域中&#xff0c;到底扮演了什么角色&#xff0c;需要完成哪些核心任務。想象一下&#xff0c;操作系統是一位經驗豐富的高級公寓管理員。內存&#xff1a;就是這棟高級公寓大樓。進程&#xff1a;一個個想要入住的租戶。內存管…

《大數據技術原理與應用》實驗報告一 熟悉常用的Linux操作和Hadoop操作

目 錄 一、實驗目的 二、實驗平臺 三、 實驗內容和要求 1. 安裝虛擬機 2. 熟悉常用的 Linux 命令 3. 進行 Hadoop 偽分布式安裝 4. 熟悉常用的 Hadoop 操作 四、實驗環境 五、實驗內容與完成情況 1. 安裝虛擬機 2. 熟悉常用的 Linux 命令 3. 進行 Hadoop 偽分布式…

I/O 多路復用詳解筆記

I/O 多路復用筆記 什么是I/O多路復用 I/O多路復用&#xff08;I/O Multiplexing&#xff09;是一種**允許單個線程&#xff08;或進程&#xff09;監聽多個I/O描述符&#xff08;fd&#xff09;**上是否就緒&#xff08;可讀/可寫/異常&#xff09;的方法。這種方式可以有效地管…

李白周游記50篇

https://mp.weixin.qq.com/s/7MThy1kCOATS-8ZWc09_1g 李白周游記50篇 卡西莫多 2025年07月15日 安徽 李白周游記50篇記錄&#xff0c;現在寫了50個小朋友&#xff0c;覺得有趣愿意加進這個連載的歡迎告知大名和出生年月&#xff0c;限20歲以下6歲以上的小朋友&#xff0c;慢…

文心一言開源版部署及多維度測評實例

文章目錄第一章 文心一言開源模型簡介第二章 模型性能深度實測2.1 通用能力基準測試2.1.1 文本生成質量2.1.2 數學推理能力2.2 極端場景壓力測試2.2.1 高并發性能2.2.2 長上下文記憶第三章 中文特色能力解析3.1.2 文化特定理解3.2 行業術語處理3.2.1 法律文書解析3.2.2 醫療報告…

ARM單片機OTA解析(二)

文章目錄二、Bootloader加載啟動App代碼講解二、Bootloader加載啟動App代碼講解 代碼詳細解析&#xff1a; typedef void (*pFunction)(void);static void DrvInit(void) {RS485DrvInit();DelayInit();SystickInit(); }#define RAM_START_ADDRESS 0x20000000 #define RAM_S…

深度解讀virtio:Linux IO虛擬化核心機制

當你在虛擬機中流暢傳輸文件時&#xff0c;是否想過背后是誰在高效調度 IO 資源&#xff1f;當云計算平臺承載千萬級并發請求時&#xff0c;又是誰在底層保障數據通路的穩定&#xff1f;答案藏在一個低調卻關鍵的技術里 ——virtio。作為 Linux IO 虛擬化的 “隱形引擎”&#…

大宗現貨電子盤交易系統核心功能代碼解析

系統架構設計交易系統采用分布式微服務架構&#xff0c;核心模塊包括訂單匹配引擎、風控系統、清算結算模塊、行情推送服務和用戶管理接口。系統設計遵循高并發、低延遲原則&#xff0c;使用事件驅動模型處理交易流程。訂單匹配引擎實現訂單簿數據結構采用紅黑樹或跳表實現&…

AAAI-2025 | 同濟大學面向嘈雜環境的音頻視覺導航!BeDAViN:大規模音頻-視覺數據集與多聲源架構研究

作者&#xff1a;Zhanbo Shi, Lin Zhang, Linfei Li, Ying Shen單位&#xff1a;同濟大學計算機學院論文標題&#xff1a;Towards Audio-visual Navigation in Noisy Environments: A Large-scale Benchmark Dataset and An Architecture Considering Multiple Sound-Sources論…

【推薦】前端低端機和弱網環境下性能優化

下面從設計、技術選型到具體實現&#xff0c;為你詳細闡述前端低端機和弱網環境下的性能優化方案。一、設計階段 1. 降級策略分級 根據設備性能和網絡質量將設備分為3個等級&#xff1a; 高性能設備&#xff1a;內存≥4GB、CPU核心數≥4、網絡RTT≤200ms中等性能設備&#xff1…

HP LoadRunner 12.02 語言包安裝教程(含下載/漢化步驟)

想給HP LoadRunner 12.02安裝語言包&#xff08;比如中文漢化&#xff09;&#xff1f;按照這個教程一步步操作就行&#xff0c;包含下載、安裝和切換語言的詳細步驟&#xff0c;輕松搞定多語言支持&#xff01;適合需要本地化使用的測試人員。 先找到安裝文件 安裝包下載&am…