RunLoop的概念
首先我們通過Xcode創(chuàng)建一個Command Line Tool project。發(fā)現(xiàn)Xcode給我們自動生成了main.m,包含如下代碼:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
運行后咖杂,控制臺輸出Hello,World!然后程序就退出了乞巧。
然而洋访,我們在創(chuàng)建的iOS程序,main.m中包含的代碼如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
運行后斑司,程序并不會退出渗饮,而是一直處于運行狀態(tài),等待用戶響應(yīng)宿刮。當(dāng)我們把main函數(shù)稍作修改互站,如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
int i = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
return i;
}
}
給return i;
這一行加上斷點。發(fā)現(xiàn)并不會被斷點斷住僵缺。說明并沒有執(zhí)行到這一步胡桃,即上一步還沒有結(jié)束。為什么會這樣呢磕潮,那就引出了今天的主角:RunLoop翠胰!因為UIApplicationMain函數(shù)內(nèi)部幫我創(chuàng)建了一個RunLoop “運行循環(huán)”,來保證線程不會退出自脯,能隨時處理事件和消息之景。大致的代碼邏輯:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
會有一個do while
循環(huán)來等待message,并處理message膏潮,只有當(dāng)while條件不滿足時(比如傳入 quit 的消息)锻狗,才會退出循環(huán),讓函數(shù)返回戏罢。而RunLoop內(nèi)對其進行了進一步的優(yōu)化:它能很好的管理事件和消息屋谭,并且讓線程在沒有處理消息時休眠以避免資源占用脚囊、在有消息到來時立刻被喚醒龟糕。下文會對其進行講解。
在繼續(xù)之前悔耘,我們可以先下載CFRunLoopRef源碼讲岁,來進行更好的分析和學(xué)習(xí)。下載地址:https://opensource.apple.com/tarballs/CF/衬以。
通過CoreFoundation框架和Foundation框架缓艳,我們發(fā)現(xiàn)OSX/iOS 系統(tǒng)中,提供了兩個這樣的對象:CFRunLoopRef 和 NSRunLoop看峻。
CFRunLoopRef提供了純C語言的api阶淘,所以是線程安全。而NSRunLoop是基于CFRunLoopRef的封裝互妓,符合了面向?qū)ο蟮奶攸c溪窒,但是不是線程安全的坤塞。
RunLoop的創(chuàng)建過程
CFRunLoop.c文件的部分源代如下:
static CFMutableDictionaryRef __CFRunLoops = NULL;
static pthread_t kNilPthreadT = (pthread_t)0;
// t==0 is a synonym for "main thread" that always works //當(dāng)t==0時代表主線程
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
//進行加鎖操作
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
// 第一次進入時,初始化全局dict澈蚌,并先為主線程創(chuàng)建一個 RunLoop摹芙。并將mainLoop添加到dict中
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
//通過線程直接從dict中獲取loop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
//如果獲取失敗,通過線程創(chuàng)建一個loop宛瞄,
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//再次確認沒有l(wèi)oop浮禾,就添加到dict中。
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
// 注冊一個回調(diào)份汗,當(dāng)線程銷毀時盈电,順便也銷毀其對應(yīng)的 RunLoop。
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
//獲取當(dāng)前線程的RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
//獲取主線程的RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
整體的流程可以概括為以下幾步:
- 通過_CFRunLoopGet0函數(shù)傳入一條線程裸影。
- 判斷線程是否為主線程并且判斷是否已經(jīng)存在__CFRunLoops(全局CFMutableDictionaryRef)挣轨。
- 如果不存在,說明第一次進入轩猩,初始化全局dict卷扮,并先為主線程創(chuàng)建一個 RunLoop。并將mainLoop添加到dict中均践。
- 如果__CFRunLoops存在晤锹,會通過對應(yīng)線程在全局的__CFRunLoops中查找對應(yīng)的RunLoop。
- 如果對應(yīng)RunLoop不存在彤委,會創(chuàng)建一個新的RunLoop鞭铆,并添加到__CFRunLoops中。
- 注冊一個回調(diào)焦影,當(dāng)線程銷毀時车遂,順便也銷毀其對應(yīng)的 RunLoop。
- 返回RunLoop斯辰。
我們只能通過CFRunLoopGetMain函數(shù)或者CFRunLoopGetCurrent函數(shù)來獲取RunLoop舶担,通過上面的源代碼我們發(fā)現(xiàn),無論是CFRunLoopGetMain函數(shù)還是CFRunLoopGetCurrent函數(shù)彬呻,都是通過對應(yīng)的線程獲取對應(yīng)的RunLoop衣陶,線程和RunLoop是一一對應(yīng)的,不會重復(fù)創(chuàng)建闸氮。在主線程剪况,系統(tǒng)會幫我們創(chuàng)建RunLoop,來處理事件蒲跨。而子線程RunLoop并不會默認開啟译断。所有,子線程操作完成后或悲,線程就被銷毀了孙咪,如果我們想線程不被銷毀藏姐,得主動獲取一個RunLoop,并且在RunLoop中添加Timer/Source/Observer其中的一個该贾。
RunLoop的內(nèi)部結(jié)構(gòu)
在 CoreFoundation 里面關(guān)于 RunLoop 有5個類:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopObserverRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
CFRunLoopMode 和 CFRunLoop 的源碼結(jié)構(gòu)大致如下:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
可以看出CFRunLoop和CFRunLoopMode有如下關(guān)系:
每個RunLoop中有若干個Mode羔杨,每個Mode中又存在著若干個Observer、Source和Timer杨蛋,每次調(diào)用 RunLoop 的主函數(shù)時兜材,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode逞力。如果需要切換 Mode曙寡,只能退出 Loop,再重新指定一個 Mode 進入寇荧。這樣做主要是為了分隔開不同組的 Source/Timer/Observer举庶,讓其互不影響。
CFRunLoopObserverRef:是觀察者揩抡,我們可以通過CFRunLoopObserverCreateWithHandler函數(shù)來創(chuàng)建一個觀察者(函數(shù)會有一個block回調(diào))户侥,來對RunLoop進行觀察,當(dāng)RunLoop狀態(tài)變化時峦嗤,會觸發(fā)block回調(diào)蕊唐,回調(diào)會返回對應(yīng)的狀態(tài),我們可以在回調(diào)里做相應(yīng)做的操作烁设√胬妫可以觀察到的狀態(tài)有:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),// 1 即將進入Loop
kCFRunLoopBeforeTimers = (1UL << 1),// 2 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2),// 4 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5),// 32 即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 64 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7),// 128 即將退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU// 所有狀態(tài)類型
};
我們來看下具體的舉例(Demo地址)大家不妨下載看看:
- (void)observer {
// 創(chuàng)建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"監(jiān)聽到即將進入RunLoop------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
case kCFRunLoopBeforeTimers:
NSLog(@"監(jiān)聽到即將處理Timer------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
case kCFRunLoopBeforeSources:
NSLog(@"監(jiān)聽到即將處理Source------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"監(jiān)聽到即將進入睡眠------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
case kCFRunLoopAfterWaiting:
NSLog(@"監(jiān)聽到即將從睡眠中醒來------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
case kCFRunLoopExit:
NSLog(@"監(jiān)聽到即將從退出RunLoop------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
default:
break;
}
});
/*
CFRunLoopAddObserver函數(shù)有三個參數(shù):
* 第一個:傳入一個RunLoop,CFRunLoopGetCurrent()獲取當(dāng)前的RunLoop
* 第二個:傳入一個觀察者装黑,observer就是新創(chuàng)建的觀察者
* 第三個:傳入Mode副瀑,kCFRunLoopCommonModes指定要監(jiān)聽的Mode
*/
// 添加觀察者到RunLoop:來監(jiān)聽RunLoop的狀態(tài)
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
// 釋放Observer
CFRelease(observer);
}
除了在viewDidLoad里面調(diào)用了[self observer];
方法,還在Main.storyboard中添加了UITextView恋谭。下面我們拿打印結(jié)果闡述糠睡。
運行后的模擬器截圖:
剛剛觸發(fā)監(jiān)聽的截圖:
我們發(fā)現(xiàn)RunLoop在不停的處理Timer事件和Source事件。雖然我們沒有主動添加Timer和Source事件箕别,但是系統(tǒng)會添加Timer和Source铜幽,比如:模擬器的時間發(fā)生變化時就會觸發(fā)kCFRunLoopBeforeTimers
回調(diào)(每分鐘)滞谢,如下圖:
當(dāng)視圖顯示完成穩(wěn)定后無事件的截圖(看具體的時間點發(fā)現(xiàn)是在整分鐘的時候觸發(fā)的回調(diào)):
當(dāng)無其他點擊等事件時串稀,會進入睡眠狀態(tài),有事件時會立刻蘇醒過來處理事件狮杨。
拖拽textView時的截圖:
發(fā)現(xiàn)在拖拽textView的時候母截,當(dāng)前RunLoop的Mode[[NSRunLoop currentRunLoop] currentMode]
變成了從kCFRunLoopDefaultMode
變成了UITrachingRunloopMode
,RunLoop也觸發(fā)了kCFRunLoopExit
(即將退出RunLoop)的回調(diào)和kCFRunLoopEntry
(即將進入RunLoop)的回調(diào),也就是:退出了當(dāng)前RunLoop橄教,進入了一個新的RunLoop清寇,也就驗證了上面所說的:每次調(diào)用 RunLoop 的主函數(shù)時喘漏,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode华烟。如果需要切換 Mode翩迈,只能退出 當(dāng)前RunLoop,再重新指定一個 Mode 進入盔夜。
如果到這里看不懂负饲,沒有關(guān)系,后面會對RunLoop 的內(nèi)部邏輯進行分析喂链》凳看了后面的分析在回過頭來看就能明白了。
CFRunLoopSourceRef:是事件產(chǎn)生的地方椭微。Source分為Source0 和 Source1洞坑。
- Source0 只包含了一個回調(diào)(函數(shù)指針),它并不能主動觸發(fā)事件蝇率。使用時迟杂,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標(biāo)記為待處理本慕,然后手動調(diào)用 逢慌。
- Source1 包含了一個 mach_port 和一個回調(diào)(函數(shù)指針),被用于通過內(nèi)核和其他線程相互發(fā)送消息间狂。這種 Source 能主動喚醒 RunLoop 的線程攻泼。
CFRunLoopTimerRef:基于時間的觸發(fā)器,CFRunLoopTimerRef是Core Foundation提供的基礎(chǔ)定時器鉴象,NSTimer則是建立在CFRunLoopTimerRef之上的高層組件忙菠。當(dāng)Timer被加入到RunLoop時,RunLoop會注冊對應(yīng)的時間點纺弊,當(dāng)達到時間時牛欢,RunLoop會被喚醒,執(zhí)行創(chuàng)建Timer時的回調(diào)淆游。
RunLoop 的 Mode
我們在viewDidLoad中通過下面代碼打印當(dāng)前RunLoop
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
NSLog(@"%@",runloop);
我們通過打印結(jié)果和分析結(jié)合來看(可以先到下面看分析傍睹,再回到上面找對應(yīng)的打印結(jié)果或者下載demo,在控制欄對照來看犹菱。)
打印結(jié)果如下:
2018-03-13 23:14:28.671838+0800 runloop[30490:55545179] <CFRunLoop 0x6000001f2400 [0x110675bb0]>{wakeup port = 0x1b03, stopped = false, ignoreWakeUps = false,
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x6000002552d0 [0x110675bb0]>{type = mutable set, count = 2,
entries =>
0 : <CFString 0x1119f9820 [0x110675bb0]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x11064b7f0 [0x110675bb0]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x60400024e9a0 [0x110675bb0]>{type = mutable set, count = 16,
entries =>
0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
3 : <CFRunLoopObserver 0x60400013f0e0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1161d3648), context = <CFRunLoopObserver context 0x0>}
4 : <CFRunLoopSource 0x60c0001795c0 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23555, subsystem = 0x1119cb088, context = 0x600000224660}}
6 : <CFRunLoopSource 0x60c000178c00 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
7 : <CFRunLoopSource 0x600000179080 [0x110675bb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000bf260, callout = FBSSerialQueueRunLoopSourceHandler (0x114b2d821)}}
8 : <CFRunLoopSource 0x600000179380 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000003ffc0> {port = 4e1b, callback = 0x127cb11c0}}
9 : <CFRunLoopObserver 0x60400013ed20 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x110854057), context = <CFRunLoopObserver context 0x7f874c200000>}
10 : <CFRunLoopObserver 0x60400013ec80 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (
0 : <0x7f874a802048>
)}}
11 : <CFRunLoopObserver 0x60400013edc0 [0x110675bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (
0 : <0x7f874a802048>
)}}
12 : <CFRunLoopObserver 0x60000013f400 [0x110675bb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x110e121a9), context = <CFRunLoopObserver context 0x6000000dff00>}
13 : <CFRunLoopObserver 0x60400013fa40 [0x110675bb0]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x11034eeb0), context = <CFRunLoopObserver context 0x6040004454f0>}
16 : <CFRunLoopSource 0x604000179980 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22027, subsystem = 0x1119b0ce8, context = 0x0}}
18 : <CFRunLoopSource 0x600000179680 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000240240> {port = 5527, callback = 0x0}}
20 : <CFRunLoopSource 0x60400017a340 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000159860, callout = __handleEventQueue (0x11118cdcc)}}
21 : <CFRunLoopObserver 0x60400013e5a0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x110853fdc), context = <CFRunLoopObserver context 0x7f874c200000>}
22 : <CFRunLoopSource 0x6040001792c0 [0x110675bb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60c000256260, callout = __handleHIDEventFetcherDrain (0x11118cdd8)}}
}
,
modes = <CFBasicHash 0x600000255420 [0x110675bb0]>{type = mutable set, count = 4,
entries =>
2 : <CFRunLoopMode 0x60400019de90 [0x110675bb0]>{name = UITrackingRunLoopMode, port set = 0x2603, queue = 0x604000159910, source = 0x60400019e100 (not fired), timer port = 0x2a03,
sources0 = <CFBasicHash 0x60400024e8b0 [0x110675bb0]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
3 : <CFRunLoopSource 0x6040001792c0 [0x110675bb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60c000256260, callout = __handleHIDEventFetcherDrain (0x11118cdd8)}}
4 : <CFRunLoopSource 0x600000179080 [0x110675bb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000bf260, callout = FBSSerialQueueRunLoopSourceHandler (0x114b2d821)}}
5 : <CFRunLoopSource 0x60400017a340 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000159860, callout = __handleEventQueue (0x11118cdcc)}}
}
,
sources1 = <CFBasicHash 0x60400024e9d0 [0x110675bb0]>{type = mutable set, count = 5,
entries =>
0 : <CFRunLoopSource 0x60c0001795c0 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23555, subsystem = 0x1119cb088, context = 0x600000224660}}
1 : <CFRunLoopSource 0x600000179680 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000240240> {port = 5527, callback = 0x0}}
2 : <CFRunLoopSource 0x60c000178c00 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
3 : <CFRunLoopSource 0x600000179380 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000003ffc0> {port = 4e1b, callback = 0x127cb11c0}}
5 : <CFRunLoopSource 0x604000179980 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22027, subsystem = 0x1119b0ce8, context = 0x0}}
}
,
observers = (
"<CFRunLoopObserver 0x60400013edc0 [0x110675bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}",
"<CFRunLoopObserver 0x60000013f400 [0x110675bb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x110e121a9), context = <CFRunLoopObserver context 0x6000000dff00>}",
"<CFRunLoopObserver 0x60400013fa40 [0x110675bb0]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x11034eeb0), context = <CFRunLoopObserver context 0x6040004454f0>}",
"<CFRunLoopObserver 0x60400013e5a0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x110853fdc), context = <CFRunLoopObserver context 0x7f874c200000>}",
"<CFRunLoopObserver 0x60400013f0e0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1161d3648), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x60400013ed20 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x110854057), context = <CFRunLoopObserver context 0x7f874c200000>}",
"<CFRunLoopObserver 0x60400013ec80 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}"
),
timers = (null),
currently 542646869 (1754787729427038) / soft deadline in: 1.84449893e+10 sec (@ -1) / hard deadline in: 1.84449893e+10 sec (@ -1)
},
3 : <CFRunLoopMode 0x60400019e1d0 [0x110675bb0]>{name = GSEventReceiveRunLoopMode, port set = 0x2c03, queue = 0x6040001599c0, source = 0x60400019e2a0 (not fired), timer port = 0x2e03,
sources0 = <CFBasicHash 0x60400024ea60 [0x110675bb0]>{type = mutable set, count = 1,
entries =>
0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
}
,
sources1 = <CFBasicHash 0x60400024ea90 [0x110675bb0]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopSource 0x604000179440 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
}
,
observers = (null),
timers = (null),
currently 542646869 (1754787749292029) / soft deadline in: 1.84449893e+10 sec (@ -1) / hard deadline in: 1.84449893e+10 sec (@ -1)
},
4 : <CFRunLoopMode 0x60000019e370 [0x110675bb0]>{name = kCFRunLoopDefaultMode, port set = 0x1c03, queue = 0x6000001595a0, source = 0x60000019e440 (not fired), timer port = 0x1e03,
sources0 = <CFBasicHash 0x60400024ea00 [0x110675bb0]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
3 : <CFRunLoopSource 0x6040001792c0 [0x110675bb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60c000256260, callout = __handleHIDEventFetcherDrain (0x11118cdd8)}}
4 : <CFRunLoopSource 0x600000179080 [0x110675bb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000bf260, callout = FBSSerialQueueRunLoopSourceHandler (0x114b2d821)}}
5 : <CFRunLoopSource 0x60400017a340 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000159860, callout = __handleEventQueue (0x11118cdcc)}}
}
,
sources1 = <CFBasicHash 0x60400024ea30 [0x110675bb0]>{type = mutable set, count = 5,
entries =>
0 : <CFRunLoopSource 0x60c0001795c0 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23555, subsystem = 0x1119cb088, context = 0x600000224660}}
1 : <CFRunLoopSource 0x600000179680 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000240240> {port = 5527, callback = 0x0}}
2 : <CFRunLoopSource 0x60c000178c00 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
3 : <CFRunLoopSource 0x600000179380 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000003ffc0> {port = 4e1b, callback = 0x127cb11c0}}
5 : <CFRunLoopSource 0x604000179980 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22027, subsystem = 0x1119b0ce8, context = 0x0}}
}
,
observers = (
"<CFRunLoopObserver 0x60400013edc0 [0x110675bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}",
"<CFRunLoopObserver 0x60000013f400 [0x110675bb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x110e121a9), context = <CFRunLoopObserver context 0x6000000dff00>}",
"<CFRunLoopObserver 0x60400013fa40 [0x110675bb0]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x11034eeb0), context = <CFRunLoopObserver context 0x6040004454f0>}",
"<CFRunLoopObserver 0x60400013e5a0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x110853fdc), context = <CFRunLoopObserver context 0x7f874c200000>}",
"<CFRunLoopObserver 0x60400013f0e0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1161d3648), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x60400013ed20 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x110854057), context = <CFRunLoopObserver context 0x7f874c200000>}",
"<CFRunLoopObserver 0x60400013ec80 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}"
),
timers = <CFArray 0x6000002a00c0 [0x110675bb0]>{type = mutable-small, count = 2, values = (
0 : <CFRunLoopTimer 0x600000179980 [0x110675bb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 542646863 (-5.80513895 @ 1754781946507642), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x10f493d9c / 0x110d1c92d) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit), context = <CFRunLoopTimer context 0x60000026f400>}
1 : <CFRunLoopTimer 0x60400017adc0 [0x110675bb0]>{valid = Yes, firing = No, interval = 0.5, tolerance = 0, next fire date = 542646863 (-5.657637 @ 1754782094433008), callout = (NSTimer) [UITextSelectionView caretBlinkTimerFired:] (0x10f4a8acb / 0x10f1d6536) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit), context = <CFRunLoopTimer context 0x604000438880>}
)},
currently 542646869 (1754787749365050) / soft deadline in: 1.84467441e+10 sec (@ 1754781946507642) / hard deadline in: 1.84467441e+10 sec (@ 1754781946507642)
},
5 : <CFRunLoopMode 0x60400019ed30 [0x110675bb0]>{name = kCFRunLoopCommonModes, port set = 0x3f0f, queue = 0x604000159bd0, source = 0x60400019eac0 (not fired), timer port = 0x570f,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
currently 542646869 (1754787752303309) / soft deadline in: 1.84449893e+10 sec (@ -1) / hard deadline in: 1.84449893e+10 sec (@ -1)
},
}
}
從上往下看發(fā)現(xiàn)結(jié)果和上面說的CFRunLoopMode 和 CFRunLoop 的源碼結(jié)構(gòu)是對應(yīng)上的:
-
current mode = kCFRunLoopDefaultMode
:現(xiàn)在處于kCFRunLoopDefaultMode下拾稳。 -
common modes
:一個 Mode 可以將自己標(biāo)記為”Common”屬性(通過將其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時腊脱,RunLoop 都會自動將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 標(biāo)記的所有Mode里访得。 上面我們可以看到被標(biāo)記為“Common”屬性的有2種Mode(count = 2):UITrackingRunLoopMode
kCFRunLoopDefaultMode
-
common mode items
:count = 16 ,包含了所有被添加到Mode的Source/Observer/Timer陕凹。 -
modes
:所有被添加到RunLoop中的RunLoopMode悍抑,count = 4鳄炉,包含了四個Mode如下:-
name = UITrackingRunLoopMode
:scrollView滾動時候會切換到這種Mode,上文講Observer的具體demo中搜骡,在打印Observer監(jiān)聽時拂盯,當(dāng)拖拽textView的時候就會切換到這種Mode。從上面打印的RunLoop里面的UITrackingRunLoopMode
记靡,可以看出它有包含了sources0(count = 4包含了4個Source0)/sources1(count = 5包含了5個Source1)/observers(7個Observer)/timers(沒有Timer)磕仅。 -
name = GSEventReceiveRunLoopMode
: 蘋果接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到簸呈。從上面打印的RunLoop里面的GSEventReceiveRunLoopMode
榕订,可以看出它僅僅包含了一個Source0和一個Source1。 -
name = kCFRunLoopDefaultMode
:App的默認 Mode蜕便,通常主線程是在這個 Mode 下運行的劫恒,從上面打印的RunLoop里面的kCFRunLoopDefaultMode
,可以看出它包含了4個Source0轿腺、5個Source1两嘴、7個Observer和2個Timer。 -
name = kCFRunLoopCommonModes
:可以看到它里面sources0/sources1/observers/timers都是null族壳,可見這是一個占位的 Mode憔辫,沒有實際作用。 - 其實還有一種Mode仿荆,這里沒有被添加到當(dāng)前的RunLoop
UIInitializationRunLoopMode
:它在剛啟動 App 時進入的第一個 Mode贰您,啟動完成后就不再使用。所以在這里看不到這個Mode拢操。
-
在上文講Observer的具體demo中锦亦,有如下代碼:
// 添加觀察者到RunLoop:來監(jiān)聽RunLoop的狀態(tài)
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRunLoopAddObserver函數(shù)有三個參數(shù):
- 第一個:傳入一個RunLoop,CFRunLoopGetCurrent()獲取當(dāng)前的RunLoop
- 第二個:傳入一個觀察者令境,observer就是新創(chuàng)建的觀察者
- 第三個:傳入Mode杠园,kCFRunLoopCommonModes指定要監(jiān)聽的Mode
其中有個kCFRunLoopCommonModes
就是指被標(biāo)記成“Common”的Mode,上面打印和分析發(fā)現(xiàn)舔庶,其實就是指UITrackingRunLoopMode
和kCFRunLoopDefaultMode
這兩種Mode抛蚁。所以在拖拽textView的時候和不拖拽的時候都能收到回調(diào),如果將kCFRunLoopCommonModes
改為kCFRunLoopDefaultMode
惕橙,會發(fā)現(xiàn)瞧甩,當(dāng)拖拽的時候收不到回調(diào)。大家可以自己試試吕漂。
和將NSTimer添加到RunLoop時亲配,指定Mode回調(diào)是一樣的道理尘应,這里就不再闡述了惶凝。已經(jīng)夠啰嗦了吼虎。。苍鲜。
AutoreleasePool的創(chuàng)建和釋放時機
在上面對RunLoop的打印中思灰,我們發(fā)現(xiàn)系統(tǒng)添加了兩個Observer
-
<CFRunLoopObserver 0x60400013edc0>
:activities = 0x1, repeats = Yes, order = -2147483647 它的activities = 0x1,而0x1轉(zhuǎn)換為10進制1混滔,就是表示監(jiān)聽的kCFRunLoopEntry
-
<CFRunLoopObserver 0x60400013ec80>
:activities = 0xa0, repeats = Yes, order = 2147483647 它的activities = 0xa0洒疚,而0xa0轉(zhuǎn)換為10進制160,發(fā)現(xiàn)沒有160這個值的狀態(tài)坯屿,其實它是kCFRunLoopBeforeWaiting
|kCFRunLoopExit
油湖,也就是32+128
它們都觸發(fā)了_wrapRunLoopWithAutoreleasePoolHandler
回調(diào)。
第一個Observer監(jiān)聽kCFRunLoopEntry
(即將進入RunLoop)其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush()
創(chuàng)建自動釋放池,其 order 是-2147483647领跛,優(yōu)先級最高乏德,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個 Observer 監(jiān)視了kCFRunLoopBeforeWaiting
() 其回調(diào)內(nèi)會調(diào)用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
釋放舊的AutoreleasePool并創(chuàng)建新AutoreleasePool吠昭;kCFRunLoopExit
(即將退出RunLoop) 其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPop()
來釋放自動釋放池喊括。這個 Observer 的 order 是 2147483647,優(yōu)先級最低矢棚,保證其釋放池子發(fā)生在其他所有回調(diào)之后郑什。
前幾天有寫一篇iOS內(nèi)存管理的文章,里面有講AutoreleasePool的內(nèi)部實現(xiàn)蒲肋。
RunLoop 的內(nèi)部邏輯
根據(jù)CoreFoundation框架RunLoop源碼蘑拯,RunLoop 內(nèi)部的邏輯官方文檔如下:
郭大神描述的內(nèi)部邏輯如下:
大體上他們是一致的。
其內(nèi)部代碼整理如下:
/// 用DefaultMode啟動
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode啟動兜粘,允許設(shè)置RunLoop超時時間
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的實現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根據(jù)modeName找到對應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里沒有source/timer/observer, 直接返回强胰。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即將進入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 內(nèi)部函數(shù)妹沙,進入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)偶洋。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)距糖。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài)玄窝,直接處理這個 Source1 然后跳轉(zhuǎn)去處理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的線程即將進入休眠(sleep)悍引。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息恩脂。線程將進入休眠, 直到被下面某一個事件喚醒。
/// ? 一個基于 port 的Source 的事件趣斤。
/// ? 一個 Timer 到時間了
/// ? RunLoop 自身的超時時間到了
/// ? 被其他什么調(diào)用者手動喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了俩块。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,處理消息。
handle_msg:
/// 9.1 如果一個 Timer 到時間了玉凯,觸發(fā)這個Timer的回調(diào)势腮。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,執(zhí)行block漫仆。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 如果一個 Source1 (基于port) 發(fā)出事件了捎拯,處理這個事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 執(zhí)行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 進入loop時參數(shù)說處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出傳入?yún)?shù)標(biāo)記的超時時間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部調(diào)用者強制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一個都沒有了
retVal = kCFRunLoopRunFinished;
}
/// 如果沒超時盲厌,mode里沒空署照,loop也沒被停止,那繼續(xù)loop吗浩。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即將退出建芙。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
可以看到,實際上 RunLoop 就是這樣一個函數(shù)懂扼,其內(nèi)部是一個 do-while 循環(huán)岁钓。當(dāng)你調(diào)用 CFRunLoopRun() 時,線程就會一直停留在這個循環(huán)里微王;直到超時或被手動停止屡限,該函數(shù)才會返回。
PerformSelecter
當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 后,實際上其內(nèi)部會創(chuàng)建一個 Timer 并添加到當(dāng)前線程的 RunLoop 中。所以如果當(dāng)前線程沒有 RunLoop辱挥,則這個方法會失效。我們可以在沒有獲取RunLoop的子線程進行測試驗證啊央。viewDidLoad中調(diào)用performSelectorTest方法。
- (void)performSelectorTest {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
self.thread = thread;
[thread start];
}
- (void)run {
// [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
[self performSelector:@selector(test) withObject:nil afterDelay:2.0];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
}
- (void)test {
NSLog(@"-----------------");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
當(dāng)我們把下面代碼注釋就不會觸發(fā)test方法了涨醋。
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
這里有個疑問:
看郭大神的博客說:performSelector:onThread:也是需要RunLoop瓜饥,但是我在子線程執(zhí)行的run 方法中執(zhí)行[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
而且沒有主動獲取RunLoop的情況下,也是調(diào)用test方法浴骂。知道的大神指導(dǎo)下乓土。
RunLoop其他舉例
UIImageView在NSDefaultRunLoopMode下展示圖片
// 只在NSDefaultRunLoopMode模式下顯示圖片
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"image"] afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
創(chuàng)建常駐子線程
//在子線程中獲取并啟動添加了Source的RunLoop
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
除了獲取當(dāng)前線程的RunLoop外,還得添加Source或者Timer溯警。并且調(diào)用run方法趣苏。要不然RunLoop中沒有Source和Timer,會直接退出當(dāng)前RunLoop不會進入循環(huán)梯轻。
歡迎指導(dǎo)溝通食磕!
參考文獻:
官方文檔
深入理解RunLoop
RunLoop問題集