iOS 底層探索篇 ——Runtime-objc_msgSend流程分析 - 慢速查找流程 (上)

這是我參與8月更文挑戰的第8天,活動詳情查看:8月更文挑戰c++

接着上篇的代碼分析

//這裏的p12 是第一次要找的index((_cmd ^ (_cmd >> 7)) & mask)
//左移4位至關於*16,p10是buckets的首地址,也就是將p10平移到第一次要查找的地方
//而後儲存到p12裏面。
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

						// do {
//至關於以前的bucket--循環當前查找
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket--
	cmp	p9, p1				// if (sel == _cmd)
// 找到就去 前面的cachehit
	b.eq	2b				// goto hit
// p9 不等於0
	cmp	p9, #0				// } while (sel != 0 &&
/// p13 大於 p12,確保不查找以前查過的bucket
	ccmp	p13, p12, #0, ne		// bucket > first_probed)
	b.hi	4b

LLookupEnd\Function:
LLookupRecover\Function:
	b	\MissLabelDynamic

複製代碼

真機中探尋objc_msgSend

從新建立一個app工程,在真機上跑代碼來探索objc_msgSend緩存

在這裏插入圖片描述

打開always show disassembly後運行。sass

在這裏插入圖片描述

按住control而後點擊step into,進入到objc_msgSend安全

在這裏插入圖片描述

接下來讀取寄存器x0,x1。markdown

在這裏插入圖片描述

這裏能夠看到x1SELx0就是receiver,再按住control而後點擊step into,進入到libobjc.A.dylib`objc_msgSend。看到第2行就是拿x0和0x0對比來判斷x0是否存在,而第5行則是經過將receiver的地址於上掩碼獲得isa。app

在這裏插入圖片描述

輸出證實一下確實獲得isa。oop

在這裏插入圖片描述

_objc_msgSend_uncached

以前說到,若是找到了方法就會cacheHit,沒有找到就會進入_objc_msgSend_uncached,那麼在源碼中搜索_objc_msgSend_uncached,找到entrypost

在這裏插入圖片描述

這裏看到會進入MethodTableLookup,來搜索一下MethodTableLookup。優化

在這裏插入圖片描述

再來看一下TailCallFunctionPointer,好像只是返回。說明了重要信息在MethodTableLookup裏面。spa

接着看MethodTableLookup,看到下面有x0,是寄存器的第一個地址,存放返回值的地方,因此有x0就表明有返回值imp,咱們目標須要找imp,因此須要去看_lookUpImpOrForward

在這裏插入圖片描述

搜索一下_lookUpImpOrForward,發如今objc-msg-arm64.s只有剛纔那裏有。

在這裏插入圖片描述

接下來去看c++代碼,搜索lookUpImpOrForward,回到objc-runtime-new裏面,找到 lookUpImpOrForward的實現。

不是所有代碼。

爲何緩存要特地用匯編寫而不是c++寫呢

  1. 彙編流程更加接近機器語言,執行快,優化查找時間
  2. 安全
  3. 參數未知,c語言沒法知足

若是在彙編找不到方法,就會進入到慢速查找流程,就是遍歷methodList的過程。

lookUpImpOrForward中,咱們的目標是imp,咱們須要關注的是返回值imp

在這裏插入圖片描述

如今開始探尋這個方法。

看到這裏有個checkIsKnownClass,故名思義就是檢查是否是已知的類

在這裏插入圖片描述

點進去看看方法實現。

在這裏插入圖片描述

在看看isKnownClass的實現,大概看出來allocatedClasses是個集合,用來存放全部已經加載的類。

在這裏插入圖片描述

回到lookUpImpOrForward往下走來到這裏,這裏主要是進行類的實現。

在這裏插入圖片描述

在看這裏

在這裏插入圖片描述

繼續點進去

在這裏插入圖片描述

在點這裏

在這裏插入圖片描述

看到了熟悉的class_rw_t,supercls,metaclas,這個方法對ro,rw進行了一些處理

在這裏插入圖片描述

接下來看到這裏,發現對父類以及元類也進行了實現

在這裏插入圖片描述

往下看,看到了這裏對cls的父類以及元類進行了賦值。到這裏能夠看到,這裏對類以及他的父類以及元類 -> 根類根元類進行了初始化。這是爲了若是在當前類中找不到方法,能夠在父類或者元類中進行查找。

在這裏插入圖片描述

回到lookUpImpOrForward方法繼續往下走

在這裏插入圖片描述

來到一個死循環裏面,會先到共享緩存找一遍,以避免到這裏的時候,恰好以前方法緩存進去了

在這裏插入圖片描述

若是沒有緩存走到getMethodNoSuper_nolock

在這裏插入圖片描述

點進去看看getMethodNoSuper_nolock的實現。這裏的cls->data()->methods()就比較熟悉了,是獲取方法列表。而後獲取方法列表中的第一個和最後一個方法,遍歷查找。

在這裏插入圖片描述

接下來就會進入search_method_list_inline,點進去看一下。

在這裏插入圖片描述

再來看findMethodInSortedMethodList。這裏通常都是走else,其餘如m1會走isSmallList

在這裏插入圖片描述

點開findMethodInSortedMethodList查看。這裏運用了二分查找法。假設count是8也就是二進制的1000,要查找的爲7,1000 >> 1 = 0100 = 4,那麼第一次進循環時,probe就是4,而probeValue就是經過getName方法獲得的SEL,若是和要查找的SEL匹配就返回probeprobe--是在返回是有分類的狀況,先調用分類的方法,這裏不展開。若是不等於,則繼續往下走,若是7大於4,那麼base = probe + 1 = 5,count -- 就爲7. 再進一次循環,count >>= 1, 7 >>= 1,那麼就是0011等於3。probe = base + (count >> 1) = 5+1 = 6,繼續查找,沒有找到,那麼就是base = probe + 1 = 7,count -- 等於 2. 在進一次循環。count >>=1 也就是0010 >>= 1等於1,probe = base + (count >>1 ) = 7 + 0 = 7,此次keyValue 就等於probeValue,找到了咱們要找的probe。

在這裏插入圖片描述

在回到lookUpImpOrForward往下走,看到這裏若是找到了imp,那麼就會goto done

在這裏插入圖片描述

看一下done是什麼,發現這裏有log_and_fill_cache,進行緩存填充了。

在這裏插入圖片描述

log_and_fill_cache點進去看,發現這裏插入緩存cache.insert.

在這裏插入圖片描述

到這裏,msgSend已經造成了一個閉環。 第一次loopUpCache尋找若是沒有找到的話,會進入慢速查找,若是rw,ro裏面沒有,就會報錯。若是有,就會insert,下一次進來的時候就進行快速查找loopUpCache。

在回到lookUpImpOrForward往下走。這裏把curClass換成了父類。若是全部的父類都找完了,那麼imp就會設爲forward_imp,退出循環,具體流程下回分析。

在這裏插入圖片描述

繼續往下走發現若是找不到的話就會進入cache_getImp。

在這裏插入圖片描述

點進去cache_getImp發現找不到實現。

在這裏插入圖片描述

就去彙編中尋找,發現實現,又是熟悉的GetClassFromIsa_p16 以及CacheLookup,說明這裏是快查找流程。這裏找不到的話,就會返回一個空。

在這裏插入圖片描述

因此上面的 imp = cache_getImp(curClass, sel); imp就會爲空。而後在回到lookUpImpOrForward往下走 就會開始從新循環,而這時候的curClass 就是父類。這個循環會一直下去,直到找到方法,或者父類爲空也沒有找到。

lookUpImpOrForward流程總結:

  1. 檢測類是否註冊過, 若是沒有註冊過就報錯
  2. 檢測是否實現/初始化superclass鏈上的類和元類, 沒有的話就去實現,這樣的話在本類沒有找到就會去父類中尋找。
  3. 進入for循環查找
  • 判斷是否有共享緩存,以避免到這裏的時候,恰好以前方法緩存進去了
  • findMethodInSortedMethodList中二分查找當前類的sel對應的Method, 找到就退出循環,插入緩存。
  • 若是沒有找到就檢測父類, 父類若是爲nil, 則退出循環, imp = forward_imp
  • 若是沒有找到且父類不爲nil的話就進行父類的慢速查找流程。
  • 父類中找到imp, 則退出循環, 跳轉到goto done
相關文章
相關標籤/搜索