iOS開發進階:RunLoop

什麼是Runloop?
Runloop是通過內部維護的事件循環來對事件和消息進行管理的一種機制。當沒有消息需要處理的時候,線程進入休眠以避免佔用資源,有消息需要處理時,立即被喚醒。

runloop循環不是單獨的do-while循環,而是發生一個用戶態到內核態切換,以及內核態到用戶態切換。它維護的事件循環可以用來不斷的處理消息和事件,當沒有消息和事件需要處理時會從用戶態切換到內核態,由此可以用來休眠線程,避免資源佔用。當有消息需要處理時會從內核態切換到用戶態,當前線程會被喚醒,所以狀態切換纔是runloop的關鍵。

main函數爲何能保持不退出?
main函數中,會調用UIApplicationMain函數,在內部會啓動主線程的Runloop,可以不斷的接收消息,比如點擊屏幕事件,滑動列表以及處理網絡請求的返回等接收消息後對事件進行處理,處理完之後,就會繼續等待。

iOS中提供了兩套Runloop接口,一個是NSRunLoop基於Objective-C,在Foundation框架中,另一個是CFRunLoopRef基於C,在CoreFoundation中。而NSRunLoop是對CFRunLoopRef的封裝,兩者接口基本都是對應的。CFRunLoopRef runloop = [nsrunloop getCFRunLoop]可以獲取對應的CFRunLoopRef。通過一個表格來對比一下:

特徵 NSRunLoop CFRunLoopRef
所屬框架 Objective-C/Foundation C/CoreFoundation
獲取Runloop [NSRunLoop currentRunLoop]
[NSRunLoop mainRunLoop]
CFRunLoopGetCurrent()
CFRunLoopGetMain()
Source事件 addPort:forMode:
removePort:forMode:
CFRunLoopAddSource(...)
CFRunLoopRemoveSource(...)
Timer事件 addTimer:forMode: CFRunLoopAddTimer(...)
Observer事件 CFRunLoopAddObserver(...)
CFRunLoopRemoveObserver(...)

1. __CFRunLoop相關基本數據結構

struct __CFRunLoop {
    ...
    pthread_t _pthread;//對應的線程
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    ...
};

__CFRunLooprunloop本身:typedef struct __CFRunLoop *CFRunLoopRef__CFRunLoop對應多個__CFRunLoopMode

struct __CFRunLoopMode {
    ...
    CFStringRef _name;
    CFMutableSetRef _sources0;//
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    ...
};

__CFRunLoopMode是runloop的運行模式:typedef struct __CFRunLoopMode *CFRunLoopModeRef。每一個__CFRunLoopMode又包含多個_sources0、_sources1、_observers、_timers事件。_sources0:非基於Port的,也就是用戶主動發出的事件。_sources1:基於Port的,也就是系統內部的消息事件。_observers:觀察者。
_timers:定時器事件。
相關的類的成員變量與關係:

2.NSRunLoop的創建

[NSRunLoop mainRunLoop]對應底層的CFRunLoopGetMain(),[NSRunLoop currentRunLoop]對應底層的CFRunLoopGetCurrent(),內部都是通過CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t)獲取的runloop,分別傳入pthread_main_thread_np()pthread_self()也就是主線程和當前線程的id。

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        //如果外部傳入無效的0,則將主線程ID賦值給t。
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        //如果__CFRunLoops爲空,則創建主線程對應的runloop
        __CFUnlock(&loopsLock);
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        //__CFRunLoopCreate做線程的初始化
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); 
        // 將mainLoop保存到dict中,以線程id爲key,mainLoop爲value
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        //將dict中的內容複製到__CFRunLoops地址上
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    //通過線程id獲取runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
        //如果獲取到的爲空,則直接創建
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            //如果創建後還不能獲取到則使用剛纔創建的,並將newLoop保存到__CFRunLoops中
            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())) {
        //如果獲取的是當前線程的
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

__CFRunLoopCreate中通過_CFRuntimeCreateInstance實例華再進行其他變量的一些初始化。其中loop->_pthread = t;將線程id綁定到了runloop上。runloop通過線程id去查找,如果沒有則進行創建並將線程id綁定到runloop上,通過這個規則我們知道:線程與runloop一一對應;線程不一定都有runloop,首個runloop創建時會檢查__CFRunLoops是否爲空,爲空則先創建主線程的runloop,再創建指定線程的runloop

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章