RunLoop(從源碼分析到Demo分析到mainLoop log分析)

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內(nèi)部結(jié)構(gòu)圖

每個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ā)監(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)):


穩(wěn)定后無事件的截圖

當(dāng)無其他點擊等事件時串稀,會進入睡眠狀態(tài),有事件時會立刻蘇醒過來處理事件狮杨。

拖拽textView時的截圖:

拖拽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)舔庶,其實就是指UITrackingRunLoopModekCFRunLoopDefaultMode這兩種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)部的邏輯官方文檔如下:

蘋果官方文檔RunLoop內(nèi)部邏輯

郭大神描述的內(nèi)部邏輯如下:

RunLoop內(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問題集

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喳挑,隨后出現(xiàn)的幾起案子彬伦,更是在濱河造成了極大的恐慌滔悉,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件单绑,死亡現(xiàn)場離奇詭異回官,居然都是意外死亡,警方通過查閱死者的電腦和手機询张,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門孙乖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浙炼,“玉大人份氧,你說我怎么就攤上這事⊥淝” “怎么了蜗帜?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長资厉。 經(jīng)常有香客問我厅缺,道長,這世上最難降的妖魔是什么宴偿? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任湘捎,我火速辦了婚禮,結(jié)果婚禮上窄刘,老公的妹妹穿的比我還像新娘窥妇。我一直安慰自己,他們只是感情好娩践,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布活翩。 她就那樣靜靜地躺著,像睡著了一般翻伺。 火紅的嫁衣襯著肌膚如雪材泄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天吨岭,我揣著相機與錄音拉宗,去河邊找鬼。 笑死辣辫,一個胖子當(dāng)著我的面吹牛簿废,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播络它,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼族檬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了化戳?” 一聲冷哼從身側(cè)響起单料,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤埋凯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扫尖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體白对,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年换怖,在試婚紗的時候發(fā)現(xiàn)自己被綠了甩恼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡沉颂,死狀恐怖条摸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铸屉,我是刑警寧澤钉蒲,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站彻坛,受9級特大地震影響顷啼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昌屉,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一钙蒙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧间驮,春花似錦躬厌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至抢呆,卻和暖如春煮嫌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抱虐。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工昌阿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恳邀。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓懦冰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谣沸。 傳聞我的和親對象是個殘疾皇子刷钢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容