iOS之武功祕籍 文章彙總html
上文說到cache_t
緩存的是方法,咱們分析了cache
的寫入流程,在寫入流程以前,還有一個cache
讀取流程,即objc_msgSend
和 cache_getImp
.那麼方法又是什麼呢?這一切都要從Runtime
開始提及...c++
Runtime
是一套API
,由c、c++、彙編
一塊兒寫成的,爲OC
提供了運行時.github
OC、Swift
)翻譯成機器語言(彙編等),最後變成二進制Runtime
有兩個版本——Legacy
和Modern
,蘋果開發者文檔都寫得清清楚楚算法
源碼中-old
、__OBJC__
表明Legacy
版本,-new
、__OBJC2__
表明Modern
版本,以此作兼容緩存
Runtime
底層通過編譯會提供一套API
和供FrameWork
、Service
使用sass
Runtime
調用方式:markdown
Runtime API
,如 sel_registerName()
,class_getInstanceSize
NSObject API
,如 isKindOf()
OC
上層方式,如 @selector()
原來日常在用的這麼多方法都是Runtime
啊,那麼方法到底是什麼呢?app
經過clang
編譯成cpp文件
能夠看到底層代碼,獲得方法的本質iphone
clang -rewrite-objc main.m -o main.cpp
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
或xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
((TCJPerson *(*)(id, SEL))(void *)
是類型強轉(id)objc_getClass("TCJPerson")
獲取TCJPerson
類對象sel_registerName("alloc")
等同於@selector()
便可以理解爲((類型強轉)objc_msgSend)(對象, 方法調用)
方法的本質是經過objc_msgSend
發送消息,id
是消息接收者,SEL
是方法編號.
注意:若是外部定義了C函數
並調用如void sayHello() {}
,在clang
編譯以後仍是sayHello()
而不是經過objc_msgSend
去調用.由於發送消息就是找函數實現的過程,而C函數
能夠經過函數名
——指針
就能夠找到.
爲了驗證,經過objc_msgSend
方法來完成[person sayHello]
的調用,查看其打印是不是一致. 其打印結果以下,發現是一致的,因此
[person sayHello]
等價於objc_msgSend(person,sel_registerName("sayHello"))
這其中須要注意兩點:
objc_msgSend
,須要導入頭文件#import <objc/message.h>
target --> Build Setting -->搜索msg -- 將enable strict checking of obc_msgSend calls由YES 改成NO
,將嚴厲的檢查機制關掉,不然objc_msgSend
的參數會報錯子類TCJTeacher
有實例方法sayHello
、sayNB
, 類方法sayNC
父類TCJPerson
有實例方法sayHello
、sayCode
, 類方法sayNA
消息接收者——實例對象
注意前面的細節:父類TCJPerson
中實現了sayHello
方法,而子類TCJTeacher
沒有實現sayHello
方法.如今咱們能夠嘗試讓teacher
調用sayHello
執行父類中實現,經過objc_msgSendSuper
實現.
由於objc_msgSend
不能向父類發送消息,須要使用objc_msgSendSuper
,並給objc_super
結構體賦值(在objc2
中只須要賦值receiver
、super_class
)
receiver——實例對象
;super_class——父類類對象
發現不管是
[teacher sayHello]
仍是objc_msgSendSuper
都執行的是父類中sayHello
的實現,因此這裏,咱們能夠做一個猜想:方法調用,首先是在類中查找,若是類中沒有找到,會到類的父類中查找
.
receiver——實例對象
;super_class——父類類對象
receiver——類對象
;super_class——父類元類對象
消息查找流程實際上是經過上層的方法編號sel
發送消息objc_msgSend
找到具體實現imp
的過程
objc_msgSend
是用匯編寫成的,至於爲何不用C而
是用匯編寫
,是由於:
C語言
不能經過寫一個函數,保留未知的參數,跳轉到任意的指針,而彙編有寄存器打開objc4
源碼,因爲主要研究arm64結構
的彙編實現,來到objc-msg-arm64.s
,先附上其彙編總體執行的流程圖
p0表示0寄存器的指針,x0表示它的值,w0表示低32位的值(不用過多在乎)
objc_msgSend
消息接收者
是否爲空,爲空直接返回tagged_pointers
(以後會講到)isa
存一份到p13
中isa
進行mask
地址偏移獲得對應的上級對象
(類、元類)查看GetClassFromIsa_p16
定義,主要就是進行isa & mask
獲得class
操做
imp
——開始了快速流程從CacheLookup
開始了快速查找流程(此時x1
是sel
,x16
是class
)
①經過cache
首地址平移16字節
(由於在objc_class
中,首地址距離cache
正好16
字節,即isa
首地址 佔8
字節,superClass
佔8
字節),獲取cahce
,cache
中高16
位存mask
,低48
位存buckets
,即p11 = cache
②從cache
中分別取出buckets
和mask
,並由mask
根據哈希算法計算出哈希下標
cache
和掩碼(即0x0000ffffffffffff)
的 &
運算,將高16位mask抹零
,獲得buckets
指針地址,即p10 = buckets
cache
右移48
位,獲得mask
,即p11 = mask
objc_msgSend
的參數p1
(即第二個參數_cmd)& msak
,經過哈希算法
,獲得須要查找存儲sel-imp
的bucket
下標index
,即p12 = index = _cmd & mask
,爲何經過這種方式呢?由於在存儲sel-imp
時,也是經過一樣哈希算法計算哈希下標進行存儲
,因此讀取
也須要經過一樣的方式讀取
,以下所示③根據所得的哈希下標index
和 buckets
首地址,取出哈希下標對應的bucket
PTRSHIFT
等於3
,左移4位
(即2^4 = 16
字節)的目的是計算出一個bucket
實際佔用的大小,結構體bucket_t
中sel
佔8
字節,imp
佔8
字節index
乘以 單個bucket佔用的內存大小
,獲得buckets
首地址在實際內存
中的偏移量
首地址 + 實際偏移量
,獲取哈希下標index
對應的bucket
④根據獲取的bucket,取出其中的imp存入p17,即p17 = imp,取出sel存入p9,即p9 = sel
⑤第一次遞歸循環
bucket
中sel
與 objc_msgSend
的第二個參數的_cmd
(即p1)是否相等CacheHit
,即緩存命中,返回imp
CheckMiss
,由於$0
是normal
,會跳轉至__objc_msgSend_uncached
,即進入慢速查找流程
index
獲取的bucket
等於 buckets
的第一個元素,則人爲的將當前bucket
設置爲buckets
的最後一個元素(經過buckets首地址+mask右移44位
(等同於左移4位)直接定位到bucker的最後一個元素
),而後繼續進行遞歸循環(第一個
遞歸循環嵌套第二個
遞歸循環),即⑥bucket
不等於buckets
的第一個元素,則繼續向前查找
,進入第一次遞歸循環
⑥第二次遞歸循環:重複⑤的操做,與⑤中惟一區別是,若是當前的bucket
仍是等於 buckets
的第一個元素,則直接跳轉至JumpMiss
,此時的$0
是normal
,也是直接跳轉至__objc_msgSend_uncached
,即進入慢速查找流程
如下是整個快速查找
過程值的變化
過程流程圖
在快速查找流程中,若是沒有找到方法實現,不管是走到CheckMiss
仍是JumpMiss
,最終都會走到__objc_msgSend_uncached
彙編函數
在objc-msg-arm64.s
文件中查找__objc_msgSend_uncached
的彙編實現,其中的核心是MethodTableLookup
(即查詢方法列表),其源碼以下
搜索MethodTableLookup
的彙編實現,其中的核心是_lookUpImpOrForward
,彙編源碼實現以下
驗證 上述彙編的過程,能夠經過彙編調試
來驗證
main
中,例如[person sayHello]
對象方法調用處加一個斷點,而且開啓彙編調試【Debug -- Debug worlflow -- 勾選Always show Disassembly】
,運行程序objc_msgSend
加一個斷點,執行斷住,按住control + stepinto
,進入objc_msgSend
的彙編_objc_msgSend_uncached
加一個斷點,執行斷住,按住control + stepinto
,進入彙編從上能夠看出最後走到的就是lookUpImpOrForward
,此時並非彙編實現.
注意
C/C++
中調用 彙編
,去查找彙編時
,C/C++調用
的方法須要多加一個下劃線
彙編
中調用 C/C++
方法時,去查找C/C++
方法,須要將彙編調用的方法去掉一個下劃線
根據彙編部分的提示,全局續搜索lookUpImpOrForward
,最後在objc-runtime-new.mm
文件中找到了源碼實現,這是一個c
實現的函數 其總體的慢速查找流程如圖所示
慢速流程主要分爲幾個步驟:
cache
緩存中進行查找,即快速查找
,找到則直接返回imp
,反之,則進入②已知類
,若是不是,則報錯
實現
,若是沒有,則須要先實現,肯定其父類鏈,此時實例化的目的是爲了肯定父類鏈、ro、以及rw等,方便後續數據的讀取以及查找的循環初始化
,若是沒有,則初始化for
循環,按照類繼承鏈
或者 元類繼承鏈
的順序查找
cls
的方法列表中使用二分查找算法
查找方法,若是找到,則進入cache寫入流程
(在iOS之武功祕籍⑤:cache_t分析文章中已經詳述過),並返回imp
,若是沒有找到,則返回nil
cls
被賦值爲父類
,若是父類等於nil
,則imp = 消息轉發
,並終止遞歸,進入④父類鏈
中存在循環
,則報錯,終止循環
父類緩存
中查找方法
未找到
,則直接返回nil
,繼續循環查找
找到
,則直接返回imp
,執行cache寫入
流程判斷
是否執行過動態方法解析
沒有
,執行動態方法解析
執行過一次
動態方法解析,則走到消息轉發流程
以上就是方法的慢速查找流程
,下面在分別詳細解釋二分查找原理
以及 父類緩存查找
詳細步驟
查找方法列表的流程以下所示
其二分查找核心的源碼實現以下
算法原理
簡述爲:從第一次查找開始,每次都取中間位置
,與想查找的key的value值
做比較,若是相等
,則須要排除分類方法
,而後將查詢到的位置的方法實現返回,若是不相等
,則須要繼續二分查找
,若是循環至count = 0
仍是沒有找到,則直接返回nil
,以下所示:
以查找TCJPerson
類的sayHello
實例方法爲例,其二分查找過程以下
cache_getImp
方法是經過彙編_cache_getImp
實現,傳入的$0
是 GETIMP
,以下所示
父類緩存
中找到了方法實現,則跳轉至CacheHit
即命中,則直接返回imp
父類緩存
中,沒有找到
方法實現,則跳轉至CheckMiss
或者 JumpMiss
,經過判斷$0
跳轉至LGetImpMiss
,直接返回nil
.總結
對象方法(即實例方法)
,即在類中查找
,其慢速查找的父類鏈
是:類--父類--根類--nil
類方法
,即在元類中查找
,其慢速查找的父類鏈
是:元類--根元類--根類--nil
快速查找、慢速查找
也沒有找到方法實現,則嘗試動態方法決議
動態方法決議
仍然沒有找到,則進行消息轉發
若是在快速查找、慢速查找、方法解析流程中,均沒有找到實現,則使用消息轉發,其流程以下
消息轉發會實現
其中_objc_msgForward_impcache
是彙編實現,會跳轉至__objc_msgForward
,其核心是__objc_forward_handler
彙編實現中查找__objc_forward_handler
,並無找到,在源碼中去掉一個下劃線
進行全局搜索_objc_forward_handler
,有以下實現,本質是調用的objc_defaultForwardHandler
方法
看着objc_defaultForwardHandler
有沒有很眼熟,這就是咱們在平常開發中最多見的錯誤:沒有實現函數,運行程序,崩潰時報的錯誤提示
.
🌰:定義TCJPerson
父類,其中有sayNB
實例方法 和 sayHappay
類方法
定義子類:TCJStudent
類,有實例方法sayHello
和sayMaster
,類方法sayObjc
,其中實例方法sayMaster
未實現.
在main
中 調用TCJStudend
的實例方法sayMaster
,運行程序報錯,提示方法未實現,以下所示
下面,咱們來說講如何在崩潰前,如何操做,能夠防止方法未實現的崩潰.
在慢速查找
流程未找到
方法實現時,首先會嘗試一次動態方法決議
,其源碼實現以下: 主要分爲如下幾步
類是不是元類
類
,執行實例方法
的動態方法決議resolveInstanceMethod
元類
,執行類方法
的動態方法決議resolveClassMethod
,若是在元類中沒有找到
或者爲空
,則在元類
的實例方法
的動態方法決議resolveInstanceMethod
中查找,主要是由於類方法在元類中是實例方法
,因此還須要查找元類中實例方法的動態方法決議動態方法決議
中,將其實現指向了其餘方法
,則繼續查找指定的imp
,即繼續慢速查找lookUpImpOrForward
流程其流程以下
針對實例方法
調用,在快速-慢速查找均沒有找到實例方法的實現時,咱們有一次挽救的機會,即嘗試一次動態方法決議
,因爲是實例方法
,因此會走到resolveInstanceMethod
方法,其源碼以下 主要分爲如下幾個步驟:
resolveInstanceMethod
消息前,須要查找cls
類中是否有該方法的實現,即經過lookUpImpOrNil
方法又會進入lookUpImpOrForward
慢速查找流程查找resolveInstanceMethod
方法
resolveInstanceMethod
消息lookUpImpOrNil
方法又會進入lookUpImpOrForward
慢速查找流程查找實例方法針對實例方法say666
未實現的報錯崩潰,能夠經過在類
中重寫resolveInstanceMethod
類方法,並將其指向其餘方法的實現,即在TCJPerson
中重寫resolveInstanceMethod類方法
,將實例方法say666
的實現指向sayMaster
方法實現,以下所示
假如咱們在resolveInstanceMethod
類方法中,不指向其餘方法的實現,它會來兩次,爲何會這樣呢?咱們在後面在解釋...
針對類方法
,與實例方法相似,一樣能夠經過重寫resolveClassMethod
類方法來解決前文的崩潰問題,即在TCJPerson
類中重寫該方法,並將sayNB
類方法的實現指向類方法sayHappy
resolveClassMethod
類方法的重寫須要注意一點,傳入的cls再也不是類
,而是元類
,能夠經過objc_getMetaClass
方法獲取類的元類,緣由是由於類方法在元類中是實例方法
.
上面的這種方式是單獨在每一個類中重寫,有沒有更好的,一勞永逸的方法呢?其實經過方法慢速查找流程能夠發現其查找路徑有兩條
它們的共同點是若是前面沒找到,都會來到根類即NSObject中查找
,因此咱們是否能夠將上述的兩個方法統一整合在一塊兒呢?答案是能夠的,能夠經過NSObject添加分類
的方式來實現統一處理
,並且因爲類方法的查找,在其繼承鏈,查找的也是實例方法,因此能夠將實例方法 和 類方法的統一處理放在resolveInstanceMethod
方法中,以下所示 這種方式的實現,正好與源碼中針對類方法的處理邏輯是一致的,即完美闡述爲何調用了類方法動態方法決議,還要調用對象方法動態方法決議,其根本緣由仍是類方法在元類中是實例方法.
固然,上面這種寫法仍是會有其餘的問題,好比系統方法也會被更改
,針對這一點,是能夠優化的,即咱們能夠針對自定義類中方法統一方法名的前綴,根據前綴來判斷是不是自定義方法,而後統一處理自定義方法
,例如能夠在崩潰前pop到首頁,主要是用於app線上防崩潰的處理,提高用戶的體驗.
實例方法
能夠重寫resolveInstanceMethod
添加imp
類方法
能夠在本類重寫resolveClassMethod
往元類
添加imp
,或者在NSObject分類
重寫resolveInstanceMethod
添加imp
動態方法解析
只要在任意一步lookUpImpOrNil
查找到imp
就不會查找下去——即本類
作了動態方法決議,不會走到NSObjct分類
的動態方法決議NSObject分類
重寫resolveInstanceMethod
添加imp
解決崩潰那麼把全部崩潰都在NSObjct分類
中處理,加之前綴區分業務邏輯,豈不是美滋滋?錯!
NSObjct分類
動態方法決議以前已經作了處理SDK
封裝的時候須要給一個容錯空間所以前面的 ④ 優化方案
也不是一個最完美的解決方案.那麼,這也不行,那也不行,那該怎麼辦?放心,蘋果爸爸已經給咱們準備好後路了!
在慢速查找的流程(lookUpImpOrForward
)中,咱們瞭解到,若是快速+慢速沒有找到方法實現,動態方法決議也不行,就使用消息轉發
,可是,咱們找遍了源碼也沒有發現消息轉發的相關源碼,能夠經過如下方式來了解,方法調用崩潰前都走了哪些方法
instrumentObjcMessageSends
方式打印發送消息的日誌經過lookUpImpOrForward --> log_and_fill_cache --> logMessageSend
,在logMessageSend
源碼下方找到instrumentObjcMessageSends
的源碼實現,因此,在main
中調用instrumentObjcMessageSends
打印方法調用的日誌信息,有如下兩點準備工做
一、打開 objcMsgLogEnabled
開關,即調用instrumentObjcMessageSends
方法時,傳入YES
二、在main
中經過extern
聲明instrumentObjcMessageSends
方法
經過logMessageSend
源碼,瞭解到消息發送打印信息存儲在/tmp/msgSends
目錄,以下所示
運行代碼,並前往/tmp/msgSends
目錄,發現有msgSends
開頭的日誌文件,打開發如今崩潰前,執行了如下方法
resolveInstanceMethod
方法forwardingTargetForSelector
方法methodSignatureForSelector + resolveInvocation
forwardingTargetForSelector
在源碼中只有一個聲明,並無其它描述,好在幫助文檔中提到了關於它的解釋:
sel
的新對象,也就是本身處理不了會將消息轉發給別的對象進行相關方法的處理,可是不能返回self
,不然會一直找不到forwardInvocation:
方法進行處理objc_msgSend(forwardingTarget, sel, ...);
來實現消息的發送以下代碼就是經過快速轉發解決崩潰——即TCJPerson
實現不了的方法,轉發給TCJStudent
去實現(轉發給已經實現該方法的對象)
也能夠直接不指定消息接收者,直接調用父類的該方法
,若是仍是沒有找到,則直接報錯
在快速轉發流程找不到轉發的對象後,會來到慢速轉發流程methodSignatureForSelector
依葫蘆畫瓢,在幫助文檔中找到methodSignatureForSelector
點擊查看
forwardInvocation
forwardInvocation
和methodSignatureForSelector
必須是同時存在的,底層會經過方法簽名,生成一個NSInvocation
,將其做爲參數傳遞調用NSInvocation
中編碼的消息的對象(對於全部消息,此對象沒必要相同)anInvocation
將消息發送到該對象.anInvocation
將保存結果,運行時系統將提取結果並將其傳遞給原始發送者慢速轉發流程
就是先methodSignatureForSelector
提供一個方法簽名,而後forwardInvocation
經過對NSInvocation
來實現消息的轉發
其實也能夠對forwardInvocation
方法中的invocation
不進行處理,也不會崩潰報錯
因此,由上述可知,不管在forwardInvocation
方法中是否處理invocation
事務,程序都不會崩潰.
Hopper和IDA是一個能夠幫助咱們靜態分析可視性文件的工具,能夠將可執行文件反彙編成僞代碼、控制流程圖等,下面以Hopper爲例.
運行程序崩潰,查看堆棧信息
發現___forwarding___
來自CoreFoundation
經過image list
,讀取整個鏡像文件,而後搜索CoreFoundation
,查看其可執行文件的路徑
經過文件路徑,找到CoreFoundation
的可執行文件
打開hopper
,選擇Try the Demo
,而後將上一步的可執行文件拖入hopper
進行反彙編,選擇x86(64 bits)
如下是反彙編後的界面,主要使用上面的三個功能,分別是 彙編、流程圖、僞代碼
經過左側的搜索框搜索__forwarding_prep_0___
,而後選擇僞代碼
如下是__forwarding_prep_0___
的彙編僞代碼,跳轉至___forwarding___
如下是___forwarding___
的僞代碼實現,首先是查看是否實現forwardingTargetForSelector
方法,若是沒有響應,跳轉至loc_6459b
即快速轉發沒有響應,進入慢速轉發流程
跳轉至loc_6459b
,在其下方判斷是否響應methodSignatureForSelector
方法
若是沒有響應,跳轉至loc_6490b
,則直接報錯
若是獲取methodSignatureForSelector
的方法簽名爲nil
,也是直接報錯
若是methodSignatureForSelector
返回值不爲空,則在forwardInvocation
方法中對invocation
進行處理
經過上面兩種查找方式能夠驗證,消息轉發的方法有3個
消息轉發總體的流程以下
消息轉發的處理主要分爲兩部分:
forwardingTargetForSelector
方法
消息接收者
,在消息接收者中仍是沒有找到方法實現,則進入另外一個方法的查找流程methodSignatureForSelector
方法
方法簽名
爲nil
,則直接崩潰報錯
方法簽名
不爲nil
,走到forwardInvocation
方法中,對invocation
事務進行處理,若是不處理也不會報錯
在前文中說起了動態方法決議方法執行了兩次,有如下兩種分析方式
在慢速查找流程中,咱們瞭解到resolveInstanceMethod
方法的執行是經過lookUpImpOrForward --> resolveMethod_locked --> resolveInstanceMethod
來到resolveInstanceMethod
源碼,在源碼中經過發送resolve_sel
消息觸發,以下所示 因此能夠在
resolveInstanceMethod
方法中IMP imp = lookUpImpOrNil(inst, sel, cls);
處加一個斷點,經過bt
打印堆棧信息
來看到底發生了什麼
在resolveInstanceMethod
方法中IMP imp = lookUpImpOrNil(inst, sel, cls);
處加一個斷點,運行程序,直到第一次「來了」,經過bt
查看第一次動態方法決議
的堆棧信息,此時的sel
是say666
繼續往下執行,直到第二次「來了」打印,查看堆棧信息,在第二次中,咱們能夠看到是經過CoreFoundation
的-[NSObject(NSObject) methodSignatureForSelector:]
方法,而後經過class_getInstanceMethod
再次進入動態方法決議
經過上一步的堆棧信息,咱們須要去看看CoreFoundation
中到底作了什麼?經過Hopper
反彙編CoreFoundation
的可執行文件,查看methodSignatureForSelector
方法的僞代碼
經過methodSignatureForSelector
僞代碼進入___methodDescriptionForSelector
的實現
進入 ___methodDescriptionForSelector
的僞代碼實現,結合彙編的堆棧打印,能夠看到,在___methodDescriptionForSelector
這個方法中調用了objc4源碼
的class_getInstanceMethod
在objc4-818.2
源碼中搜索class_getInstanceMethod
,其源碼實現以下所示
這一點能夠經過代碼調試來驗證,以下所示,在class_getInstanceMethod
方法處加一個斷點,在執行了methodSignatureForSelector
方法後,返回了簽名,說明方法簽名是生效的,蘋果在走到invocation
以前,給了開發者一次機會再去查詢,因此走到class_getInstanceMethod
這裏,又去走了一遍方法查詢say666
,而後會再次走到動態方法決議
因此,上述的分析也印證了前文中resolveInstanceMethod
方法執行了兩次的緣由
若是在沒有上帝視角的狀況下,咱們也能夠經過代碼
來推導在哪裏再次調用了動態方法決議
TCJPerson
類中重寫resolveInstanceMethod
方法,並加上class_addMethod
操做即賦值IMP
,此時resolveInstanceMethod
會走兩次嗎?經過運行發現,若是賦值了IMP,動態方法決議只會走一次
,說明不是在這裏走第二次動態方法決議
繼續往下探索
resolveInstanceMethod
方法中的賦值IMP
,在TCJPerson
類中重寫forwardingTargetForSelector
方法,並指定返回值爲[TCJStudent alloc]
,從新運行,若是resolveInstanceMethod
打印了兩次,說明是在forwardingTargetForSelector
方法以前執行了動態方法決議,反之,在forwardingTargetForSelector
方法以後結果發現resolveInstanceMethod
中的打印仍是隻打印了一次,那說明第二次動態方法決議 在forwardingTargetForSelector
方法後
TCJPerson
類中重寫 methodSignatureForSelector
和 forwardInvocation
,運行結果發現第二次動態方法決議在 methodSignatureForSelector
和 forwardInvocation
方法之間.
第二種分析一樣能夠論證前文中resolveInstanceMethod
執行了兩次的緣由. 通過上面的論證,咱們瞭解到其實在慢速消息轉發流程中,在methodSignatureForSelector
和 forwardInvocation
方法之間還有一次動態方法決議,即蘋果再次給的一個機會,以下圖所示
到目前爲止,objc_msgSend
發送消息的流程就分析完成了,在這裏簡單總結下
【快速查找流程】
首先,在類的緩存cache
中查找指定方法的實現【慢速查找流程
】若是緩存中沒有找到,則在類的方法列表
中查找,若是仍是沒找到,則去父類鏈的緩存和方法列表
中查找【動態方法決議】
若是慢速查找仍是沒有找到時,第一次補救機會
就是嘗試一次動態方法決議
,即重寫resolveInstanceMethod/resolveClassMethod
方法【消息轉發】
若是動態方法決議仍是沒有找到,則進行消息轉發
,消息轉發中有兩次補救機會:快速轉發+慢速轉發
unrecognized selector sent to instance
最後,和諧學習,不急不躁.我仍是我,顏色不同的煙火.