這是我參與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
複製代碼
從新建立一個app工程,在真機上跑代碼來探索objc_msgSend
。緩存
打開always show disassembly
後運行。sass
按住control而後點擊step into,進入到objc_msgSend
。安全
接下來讀取寄存器x0,x1。markdown
這裏能夠看到x1
是SEL
,x0
就是receiver
,再按住control而後點擊step into,進入到libobjc.A.dylib`objc_msgSend
。看到第2行就是拿x0和0x0對比來判斷x0是否存在,而第5行則是經過將receiver的地址於上掩碼獲得isa。app
輸出證實一下確實獲得isa。oop
以前說到,若是找到了方法就會cacheHit,沒有找到就會進入_objc_msgSend_uncached
,那麼在源碼中搜索_objc_msgSend_uncached,找到entry
。post
這裏看到會進入MethodTableLookup
,來搜索一下MethodTableLookup。優化
再來看一下TailCallFunctionPointer
,好像只是返回。說明了重要信息在MethodTableLookup
裏面。spa
接着看MethodTableLookup,看到下面有x0,是寄存器的第一個地址,存放返回值的地方,因此有x0就表明有返回值imp
,咱們目標須要找imp,因此須要去看_lookUpImpOrForward
。
搜索一下_lookUpImpOrForward,發如今objc-msg-arm64.s
只有剛纔那裏有。
接下來去看c++代碼
,搜索lookUpImpOrForward,回到objc-runtime-new
裏面,找到 lookUpImpOrForward
的實現。
爲何緩存要特地用匯編
寫而不是c++寫呢
執行快,優化查找時間
。安全
參數未知
,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匹配就返回probe
,probe--
是在返回是有分類
的狀況,先調用分類
的方法,這裏不展開。若是不等於
,則繼續往下走,若是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流程總結: