// 供外部調(diào)用的公開(kāi)的CFRunLoopRun方法,其內(nèi)部會(huì)調(diào)用CFRunLoopRunSpecific
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
// 調(diào)用CFRunLoopRunSpecific
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 其內(nèi)部會(huì)調(diào)用 __CFRunLoopRun 函數(shù)
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 首先根據(jù)modeName找到對(duì)應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// 如果沒(méi)找到 || 如果mode里沒(méi)有source/timer/observer, 直接返回
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
// 取上一次運(yùn)行的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
// 如果本次mode和上次的mode一致
rl->_currentMode = currentMode;
// 初始化一個(gè)result為kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
// 通知observer:即將進(jìn)入runloop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 核心的Loop邏輯
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers:退出Loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//記錄最后runloop狀態(tài)淮摔,用于return
int32_t retVal = 0;
do {
//初始化一個(gè)存放內(nèi)核消息的緩沖池
uint8_t msg_buffer[3 * 1024];
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
//取所有需要監(jiān)聽(tīng)的port
__CFPortSet waitSet = rlm->_portSet;
//設(shè)置RunLoop為可以被喚醒狀態(tài)
__CFRunLoopUnsetIgnoreWakeUps(rl);
//2.通知observer,即將觸發(fā)timer回調(diào),處理timer事件
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//3.通知observer段审,即將觸發(fā)Source0回調(diào)
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//執(zhí)行加入當(dāng)前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
//4.處理source0事件
//有事件處理返回true犹菇,沒(méi)有事件返回false
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//執(zhí)行加入當(dāng)前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
}
//如果沒(méi)有Sources0事件處理 并且 沒(méi)有超時(shí)德迹,poll為false
//如果有Sources0事件處理 或者 超時(shí),poll都為true
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//第一次do-while循環(huán)不會(huì)走該分支揭芍,因?yàn)閐idDispatchPortLastTime初始化是true
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
//從緩沖區(qū)讀取消息
msg = (mach_msg_header_t *)msg_buffer;
//5.接收dispatchPort端口的消息胳搞,(接收source1事件)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
//如果接收到了消息的話,前往第9步開(kāi)始處理msg
goto handle_msg;
}
}
didDispatchPortLastTime = false;
//6.通知觀察者RunLoop即將進(jìn)入休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//設(shè)置RunLoop為休眠狀態(tài)
__CFRunLoopSetSleeping(rl);
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
//這里有個(gè)內(nèi)循環(huán)称杨,用于接收等待端口的消息
//進(jìn)入此循環(huán)后肌毅,線程進(jìn)入休眠,直到收到新消息才跳出該循環(huán)姑原,繼續(xù)執(zhí)行run loop
do {
//7.接收waitSet端口的消息
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
//收到消息之后悬而,livePort的值為msg->msgh_local_port,
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// 醒來(lái)
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
//取消runloop的休眠狀態(tài)
__CFRunLoopUnsetSleeping(rl);
//8.通知觀察者runloop被喚醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//9.處理收到的消息
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
//通過(guò)CFRunloopWake喚醒
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
//什么都不干锭汛,跳回2重新循環(huán)
// do nothing on Mac OS
}
//如果是定時(shí)器事件
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
//9.1 處理timer事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}
//如果是定時(shí)器事件
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
//9.1處理timer事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
//如果是dispatch到main queue的block
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
//9.2執(zhí)行block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
// 有source1事件待處理
if (rls) {
mach_msg_header_t *reply = NULL;
//9.2 處理source1事件
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
}
}
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
__CFRunLoopDoBlocks(rl, rlm);
// 根據(jù)之前的執(zhí)行結(jié)果來(lái)決定怎么做笨奠,為retVal賦相應(yīng)的值
if (sourceHandledThisLoop && stopAfterHandle) {
//進(jìn)入RunLoop時(shí)傳入的參數(shù),處理完事件就返回
retVal = kCFRunLoopRunHandledSource;
}else if (timeout_context->termTSR < mach_absolute_time()) {
//RunLoop超時(shí)
retVal = kCFRunLoopRunTimedOut;
}else if (__CFRunLoopIsStopped(rl)) {
//RunLoop被手動(dòng)終止
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
}else if (rlm->_stopped) {
//mode被終止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
}else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
//mode中沒(méi)有要處理的事件
retVal = kCFRunLoopRunFinished;
}
//除了上面這幾種情況唤殴,都繼續(xù)循環(huán)
} while (0 == retVal);
return retVal;
}
- 實(shí)際上 RunLoop 內(nèi)部就是一個(gè) do-while 循環(huán)
- 當(dāng)你調(diào)用 CFRunLoopRun() 時(shí)般婆,線程就會(huì)一直停留在這個(gè)循環(huán)里,直到超時(shí)或被手動(dòng)停止朵逝,該函數(shù)才會(huì)返回
- 同時(shí)RunLoop有很多個(gè)mode腺兴,但是RunLoop在run的時(shí)候必須只能指定其中一個(gè)mode,運(yùn)行起來(lái)之后廉侧,被指定的mode即為currentMode
LLDB常用的調(diào)試命令页响?
po:print object的縮寫,表示顯示對(duì)象的文本描述段誊,如果對(duì)象不存在則打印nil闰蚕。
p:可以用來(lái)打印基本數(shù)據(jù)類型。
call:執(zhí)行一段代碼 如:call NSLog(@"%@", @"yang")
expr:動(dòng)態(tài)執(zhí)行指定表達(dá)式
bt:打印當(dāng)前線程堆棧信息 (bt all 打印所有線程堆棧信息)
image:常用來(lái)尋找棧地址對(duì)應(yīng)代碼位置 如:image lookup --address 0xxxx
斷點(diǎn)調(diào)試连舍?
條件斷點(diǎn)
打上斷點(diǎn)之后没陡,對(duì)斷點(diǎn)進(jìn)行編輯,設(shè)置相應(yīng)過(guò)濾條件。下面簡(jiǎn)單的介紹一下條件設(shè)置:
- Condition:返回一個(gè)布爾值盼玄,當(dāng)布爾值為真觸發(fā)斷點(diǎn)贴彼,一般里面我們可以寫一個(gè)表達(dá)式。
- Ignore:忽略前N次斷點(diǎn)埃儿,到N+1次再觸發(fā)斷點(diǎn)器仗。
- Action:斷點(diǎn)觸發(fā)事件,分為六種:
- AppleScript:執(zhí)行腳本童番。
- Capture GPU Frame:用于OpenGL ES調(diào)試精钮,捕獲斷點(diǎn)處GPU當(dāng)前繪制幀。
- Debugger Command:和控制臺(tái)中輸入LLDB調(diào)試命令一致剃斧。
- Log Message:輸出自定義格式信息至控制臺(tái)轨香。
- Shell Command:接收命令文件及相應(yīng)參數(shù)列表,Shell Command是異步執(zhí)行的幼东,只有勾選“Wait until done”才會(huì)等待Shell命令執(zhí)行完在執(zhí)行調(diào)試臂容。
- Sound:斷點(diǎn)觸發(fā)時(shí)播放聲音。
- Options(Automatically continue after evaluating actions選項(xiàng)):選中后根蟹,表示斷點(diǎn)不會(huì)終止程序的運(yùn)行策橘。
異常斷點(diǎn)
異常斷點(diǎn)可以快速定位不滿足特定條件的異常,比如常見(jiàn)的數(shù)組越界娜亿,這時(shí)候很難通過(guò)異常信息定位到錯(cuò)誤所在位置。這個(gè)時(shí)候異常斷點(diǎn)就可以發(fā)揮作用了蚌堵。
Exception:可以選擇拋出異常對(duì)象類型:OC或C++买决。
Break:選擇斷點(diǎn)接收的拋出異常來(lái)源是Throw還是Catch語(yǔ)句。
符號(hào)斷點(diǎn)
符號(hào)斷點(diǎn)的創(chuàng)建方式和異常斷點(diǎn)一樣一樣的吼畏,在符號(hào)斷點(diǎn)中可以指定要中斷執(zhí)行的方法:
Symbol:[類名 方法名]可以執(zhí)行到指定類的指定方法中開(kāi)始斷點(diǎn)督赤。
iOS 常見(jiàn)的崩潰類型有哪些?
- unrecognized selector crash
- KVO crash
- NSNotification crash
- NSTimer crash
- Container crash
- NSString crash
- Bad Access crash (野指針)
- UI not on Main Thread Crash
造成tableView卡頓的原因有哪些泻蚊?
- 最常用的就是cell的重用躲舌, 注冊(cè)重用標(biāo)識(shí)符
- 如果不重用cell時(shí),每當(dāng)一個(gè)cell顯示到屏幕上時(shí)性雄,就會(huì)重新創(chuàng)建一個(gè)新的cell
- 如果有很多數(shù)據(jù)的時(shí)候没卸,就會(huì)堆積很多cell。
- 如果重用cell秒旋,為cell創(chuàng)建一個(gè)ID约计,每當(dāng)需要顯示cell 的時(shí)候,都會(huì)先去緩沖池中尋找可循環(huán)利用的cell迁筛,如果沒(méi)有再重新創(chuàng)建cell
- 避免cell的重新布局
- cell的布局填充等操作比較耗時(shí)煤蚌,一般創(chuàng)建時(shí)就布局好
- 如可以將cell單獨(dú)放到一個(gè)自定義類,初始化時(shí)就布局好
- 提前計(jì)算并緩存cell的屬性及內(nèi)容
- 當(dāng)我們創(chuàng)建cell的數(shù)據(jù)源方法時(shí),編譯器并不是先創(chuàng)建cell 再定cell的高度
- 而是先根據(jù)內(nèi)容一次確定每一個(gè)cell的高度尉桩,高度確定后筒占,再創(chuàng)建要顯示的cell,滾動(dòng)時(shí)蜘犁,每當(dāng)cell進(jìn)入憑虛都會(huì)計(jì)算高度翰苫,提前估算高度告訴編譯器,編譯器知道高度后沽瘦,緊接著就會(huì)創(chuàng)建cell革骨,這時(shí)再調(diào)用高度的具體計(jì)算方法,這樣可以方式浪費(fèi)時(shí)間去計(jì)算顯示以外的cell
- 減少cell中控件的數(shù)量
- 盡量使cell得布局大致相同析恋,不同風(fēng)格的cell可以使用不用的重用標(biāo)識(shí)符良哲,初始化時(shí)添加控件,
- 不適用的可以先隱藏
- 不要使用ClearColor助隧,無(wú)背景色筑凫,透明度也不要設(shè)置為0
- 渲染耗時(shí)比較長(zhǎng)
- 使用局部更新
- 如果只是更新某組的話,使用reloadSection進(jìn)行局部更
- 加載網(wǎng)絡(luò)數(shù)據(jù)并村,下載圖片巍实,使用異步加載,并緩存
- 少使用addView 給cell動(dòng)態(tài)添加view
- 按需加載cell哩牍,cell滾動(dòng)很快時(shí)棚潦,只加載范圍內(nèi)的cell
- 不要實(shí)現(xiàn)無(wú)用的代理方法,tableView只遵守兩個(gè)協(xié)議
- 緩存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同時(shí)存在膝昆,這兩者同時(shí)存在才會(huì)出現(xiàn)“竄動(dòng)”的bug丸边。所以我的建議是:只要是固定行高就寫預(yù)估行高來(lái)減少行高調(diào)用次數(shù)提升性能。如果是動(dòng)態(tài)行高就不要寫預(yù)估方法了荚孵,用一個(gè)行高的緩存字典來(lái)減少代碼的調(diào)用次數(shù)即可
- 不要做多余的繪制工作妹窖。在實(shí)現(xiàn)drawRect:的時(shí)候,它的rect參數(shù)就是需要繪制的區(qū)域收叶,這個(gè)區(qū)域之外的不需要進(jìn)行繪制骄呼。例如上例中,就可以用CGRectIntersectsRect判没、CGRectIntersection或CGRectContainsRect判斷是否需要繪制image和text蜓萄,然后再調(diào)用繪制方法。
- 預(yù)渲染圖像澄峰。當(dāng)新的圖像出現(xiàn)時(shí)绕德,仍然會(huì)有短暫的停頓現(xiàn)象。解決的辦法就是在bitmap context里先將其畫一遍摊阀,導(dǎo)出成UIImage對(duì)象耻蛇,然后再繪制到屏幕踪蹬;
- 使用正確的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù)。
如何提升 tableview 的流暢度臣咖?
本質(zhì)上是降低 CPU跃捣、GPU 的工作,從這兩個(gè)大的方面去提升性能夺蛇。
CPU:對(duì)象的創(chuàng)建和銷毀疚漆、對(duì)象屬性的調(diào)整、布局計(jì)算刁赦、文本的計(jì)算和排版娶聘、圖片的格式轉(zhuǎn)換和解碼、圖像的繪制
GPU:紋理的渲染
- 卡頓優(yōu)化在 CPU 層面
- 盡量用輕量級(jí)的對(duì)象甚脉,比如用不到事件處理的地方丸升,可以考慮使用 CALayer 取代 UIView
- 不要頻繁地調(diào)用 UIView 的相關(guān)屬性,比如 frame牺氨、bounds狡耻、transform 等屬性,盡量減少不必要的修改
- 盡量提前計(jì)算好布局猴凹,在有需要時(shí)一次性調(diào)整對(duì)應(yīng)的屬性夷狰,不要多次修改屬性
- Autolayout 會(huì)比直接設(shè)置 frame 消耗更多的 CPU 資源
- 圖片的 size 最好剛好跟 UIImageView 的 size 保持一致
- 控制一下線程的最大并發(fā)數(shù)量
- 盡量把耗時(shí)的操作放到子線程
- 文本處理(尺寸計(jì)算、繪制)
- 圖片處理(解碼郊霎、繪制)
- 卡頓優(yōu)化在 GPU層面
- 盡量避免短時(shí)間內(nèi)大量圖片的顯示沼头,盡可能將多張圖片合成一張進(jìn)行顯示
- GPU能處理的最大紋理尺寸是 4096x4096,一旦超過(guò)這個(gè)尺寸书劝,就會(huì)占用 CPU 資源進(jìn)行處理进倍,所以紋理盡量不要超過(guò)這個(gè)尺寸
- 盡量減少視圖數(shù)量和層次
- 減少透明的視圖(alpha<1),不透明的就設(shè)置 opaque 為 YES
- 盡量避免出現(xiàn)離屏渲染
iOS 保持界面流暢的技巧庄撮?
- 預(yù)排版,提前計(jì)算
- 在接收到服務(wù)端返回的數(shù)據(jù)后毙籽,盡量將 CoreText 排版的結(jié)果洞斯、單個(gè)控件的高度、cell 整體的高度提前計(jì)算好坑赡,將其存儲(chǔ)在模型的屬性中烙如。需要使用時(shí),直接從模型中往外取毅否,避免了計(jì)算的過(guò)程亚铁。
- 盡量少用 UILabel,可以使用 CALayer 螟加。避免使用 AutoLayout 的自動(dòng)布局技術(shù)徘溢,采取純代碼的方式
- 預(yù)渲染吞琐,提前繪制
- 例如圓形的圖標(biāo)可以提前在,在接收到網(wǎng)絡(luò)返回?cái)?shù)據(jù)時(shí)然爆,在后臺(tái)線程進(jìn)行處理站粟,直接存儲(chǔ)在模型數(shù)據(jù)里,回到主線程后直接調(diào)用就可以了
- 避免使用 CALayer 的 Border曾雕、corner奴烙、shadow、mask 等技術(shù)剖张,這些都會(huì)觸發(fā)離屏渲染切诀。
- 異步繪制
- 全局并發(fā)線程
- 高效的圖片異步加載
APP啟動(dòng)時(shí)間應(yīng)從哪些方面優(yōu)化?
App啟動(dòng)時(shí)間可以通過(guò)xcode提供的工具來(lái)度量搔弄,在Xcode的Product->Scheme-->Edit Scheme->Run->Auguments中幅虑,將環(huán)境變量DYLD_PRINT_STATISTICS設(shè)為YES,優(yōu)化需以下方面入手
- dylib loading time
- 核心思想是減少dylibs的引用
- 合并現(xiàn)有的dylibs(最好是6個(gè)以內(nèi))
- 使用靜態(tài)庫(kù)
- rebase/binding time
- 核心思想是減少DATA塊內(nèi)的指針
- 減少Object C元數(shù)據(jù)量肯污,減少Objc類數(shù)量翘单,減少實(shí)例變量和函數(shù)(與面向?qū)ο笤O(shè)計(jì)思想沖突)
- 減少c++虛函數(shù)
- 多使用Swift結(jié)構(gòu)體(推薦使用swift)
- ObjC setup time
- 核心思想同上,這部分內(nèi)容基本上在上一階段優(yōu)化過(guò)后就不會(huì)太過(guò)耗時(shí)
- initializer time
- 使用initialize替代load方法
- 減少使用c/c++的attribute((constructor))蹦渣;推薦使用dispatch_once() pthread_once() std:once()等方法
- 推薦使用swift
- 不要在初始化中調(diào)用dlopen()方法哄芜,因?yàn)榧虞d過(guò)程是單線程,無(wú)鎖柬唯,如果調(diào)用dlopen則會(huì)變成多線程认臊,會(huì)開(kāi)啟鎖的消耗,同時(shí)有可能死鎖
- 不要在初始化中創(chuàng)建線程
Runtime方法調(diào)用流程锄奢?
1失晴、當(dāng)調(diào)用對(duì)象方法的時(shí)候,會(huì)通過(guò)obj_object的isa指針找對(duì)對(duì)應(yīng)的歸屬類拘央。
2涂屁、從歸屬類(obj_class)類中的obj_cache中尋找對(duì)應(yīng)的相等的sel方法編號(hào)。
3灰伟、如果沒(méi)有找到拆又,繼續(xù)obj_class中的obj_method_list中查找,如果找到寫入obj_cache中栏账。
4帖族、如果沒(méi)有到找到,會(huì)一直找到它的元類上挡爵。
5竖般、如果元類也沒(méi)有的話,會(huì)調(diào)用消息動(dòng)態(tài)解析方法+resovleInstanceMethod:
和+resloveClassMethod:
的方法茶鹃,查看是否存在綁定的方法涣雕。
6艰亮、如果沒(méi)有綁定方法,會(huì)調(diào)用消息轉(zhuǎn)發(fā)方法-forwardingTargetForSelector:
的方法胞谭。查看是否存在轉(zhuǎn)發(fā)對(duì)象垃杖。
7、如果沒(méi)有存在消息轉(zhuǎn)發(fā)對(duì)象丈屹,會(huì)調(diào)用-methodSignatureForSelector:
的方法调俘,查看是否有方法簽名返回類型和參數(shù)類型。
8旺垒、不存在簽名方法和類型彩库,就會(huì)來(lái)到-doseNotRecognizeSelector:
方法內(nèi)部程序crash提示無(wú)法識(shí)別選擇器unrecognized selector sent to instance。
9先蒋、存在簽名的方法骇钦,就是繼續(xù)執(zhí)行-forwardInvocation:
尋找IMP,沒(méi)有找到IMP竞漾,就會(huì)來(lái)到-doseNotRecognizeSelector:
方法內(nèi)部程序crash提示無(wú)法識(shí)別選擇器unrecognized selector sent to instance眯搭。
@implementation Person
- (BOOL)respondsToSelector:(SEL)aSelector {
bool a= [super respondsToSelector:aSelector];
return a;
}
//如果方法沒(méi)有實(shí)現(xiàn),默認(rèn)返回false
//如果返回false业岁,就會(huì)走消息轉(zhuǎn)發(fā)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
bool a = [super resolveInstanceMethod:sel];
return a;
}
//默認(rèn)返回空
//又被稱為快速消息轉(zhuǎn)發(fā)鳞仙。
// 如果為空,走慢速消息轉(zhuǎn)發(fā)笔时,繼續(xù)轉(zhuǎn)發(fā)消息
- (id)forwardingTargetForSelector:(SEL)aSelector {
id a = [super forwardingTargetForSelector:aSelector];
return a;
}
// 默認(rèn)一般普通方法是返回空的棍好。
// 如果是協(xié)議方法,沒(méi)有實(shí)現(xiàn)允耿,不會(huì)反回空借笙。
//反回空,到這里就會(huì)崩潰了
//如果這里返回了簽名较锡,會(huì)再次調(diào)用resolveInstanceMethod:(SEL)sel判斷是否實(shí)現(xiàn)
//如果仍然沒(méi)有實(shí)現(xiàn)业稼,就會(huì)走到fowardInvocation:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *a = [super methodSignatureForSelector:aSelector];
return a;
}
//默認(rèn)實(shí)現(xiàn)是崩潰
//并且不能用try-catch捕獲
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[super forwardInvocation:anInvocation];
NSLog(@"");
}
@end