RunLoop 初探
Runloop與執行緒的關係是一一對應的,一個執行緒一次只能執行一個任務,任務執行完畢後執行緒就會被銷燬,而Runloop的作用就是來管理和排程執行緒是他在沒有任務的時候不會被銷燬。
對於主執行緒來說,runloop在程式一啟動就預設建立好了。
對於子執行緒來說,runloop是懶載入的,只有當我們使用的時候才會建立
RunLoop執行模式(一共有5種)
Default NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode(Core Foundation)
Event tracking NSEventTrackingRunLoopMode (Cocoa)
Common modes NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes(Core Foundation)
Connection NSConnectionReplyMode (Cocoa)
Modal NSModalPanelRunLoopMode (Cocoa)
5種RunLoop
-
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION(呼叫timer,performselector)
-
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE(GCD主佇列)
-
FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK(響應Block)
-
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION(observer)
-
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
-
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
RunLoop與執行緒的關係
主執行緒runloop的mode為Default,Event tracking 是觸控事件產生的時候,Common modes 是佔位符相當於 NSDefaultRunLoopMode + UITrackingRunLoopMode 。一些定時器的觸控失效就是因為可能被新增到了預設的Runloop裡面而不是common modes。子執行緒如果不保證任務執行完不被銷燬也一定要建立一個Runloop來保證執行緒的存活
RunLoop是一個訊息處理機制,系統交給它進行處理各種資訊 一個簡單程式執行的過程是 執行->處理計算->完成 -> 結束任務
UIApplication Main的函式註釋是 如果為PrincipalClassName指定了nil,則使用info.plist中nsPrincipalClass的值。如果沒有指定了nsPrincipalClass鍵,使用了uiApplication類。委託類將使用init進行例項化。所以就到了AppDelegate裡面。一個App啟動的流程
int main(int argc, char * argv[]) {
@autoreleasepool {
// If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no
// NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init.
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));;
}
}
複製程式碼
#if DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_IPHONESIMULATOR
CF_EXPORT pthread_t _CF_pthread_main_thread_np(void);
#define pthread_main_thread_np() _CF_pthread_main_thread_np()
#endif
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;
}
複製程式碼
函式中傳入了當前的主執行緒pthread_main_thread_np在巨集定義中定義為當前主執行緒主執行緒進入點進_CFRunLoopGet0b
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
/// '判斷當前是否是主執行緒'
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
/// '這裡定義了 CFMutableDictionaryRef 創造主Runloop並且將其相關聯 ///這裡達'
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);
__CFSpinLock(&loopsLock);
}
/// '這裡 直接把t指標取出來 拿到runloop 從這可以看出 執行緒和Runloop是一一對應的關係 key-- value'
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don“t release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFSpinUnlock(&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;
}
複製程式碼
從上述程式碼可以看出來 執行緒和Runloop是一一對應的關係 key-- value 開啟一條子執行緒裡面執行一個定時器如果Runloop不開啟的時候那麼裡面的計時器是不會執行的從這可以看出 子執行緒 Runloop預設不開啟,需要手動開啟
RunLoop 結構
Runloop是個結構體他是一個物件
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes; '/// models 集合'
CFMutableSetRef _commonModeItems; '/// item 是個集合'
CFRunLoopModeRef _currentMode; '/// 當前models'
CFMutableSetRef _modes; '/// models 集合'
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
複製程式碼
Model
RunLoop model 的結構
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;' /// 集合'
CFMutableSetRef _sources1; '/// 集合'
CFMutableArrayRef _observers; '/// 陣列'
CFMutableArrayRef _timers; '/// 陣列'
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
複製程式碼
RunLoop與model是一對多的關係,他在RunLoop裡是以一個集合的形式存在的。Runloop只能在一個model下執行。但是可以擁有多個model。
timer
timer在Runloop的model裡面是以陣列的形式存在 原始碼裡有個關鍵的方法是CFRunLoopRun呼叫這個方法最後會走到
CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) 這個方法裡會有一個**__CFRunLoopDoBlocks**通過這個方法
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
if (!rl->_blocks_head) return false;
if (!rlm || !rlm->_name) return false;
Boolean did = false;
struct _block_item *head = rl->_blocks_head;
struct _block_item *tail = rl->_blocks_tail;
rl->_blocks_head = NULL;
rl->_blocks_tail = NULL;
CFSetRef commonModes = rl->_commonModes;
CFStringRef curMode = rlm->_name;
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
struct _block_item *prev = NULL;
struct _block_item *item = head; "model裡的item以連結串列的形式存在,只要item存在就會一直存在直到為空為止。"
while (item) {
struct _block_item *curr = item;
item = item->_next; "訪問連結串列下層,併為其賦值,"
Boolean doit = false;
if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
"// timer 加入的mode 和 我們現在runloop的mode 相等
// 或者 curr->_mode = kCFRunLoopCommonModes 相等
// 事務就能執行 timer只要加入到當前model或者commanmodel裡他都可以執行"
doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
} else {
doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
}
if (!doit) prev = curr; "如果doit存在則執行響應的block"
if (doit) {
if (prev) prev->_next = item;
if (curr == head) head = item;
if (curr == tail) tail = prev;
void (^block)(void) = curr->_block;
CFRelease(curr->_mode);
free(curr);
if (doit) {
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);"block model的runloop"
did = true;
}
Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
if (head) {
tail->_next = rl->_blocks_head;
rl->_blocks_head = head;
if (!rl->_blocks_tail) rl->_blocks_tail = tail;
}
return did;
}
複製程式碼
之所以說是__CFRunLoopDoBlocks是因為最外層會有一個Runloop對一個timer進行新增那我們看下
RunLoop.current.addtimer()
複製程式碼
的底層實現
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
CFStringRef modeName = (CFStringRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {"item對應的timerid所以會走這個方法"
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
}
}
"可以看到Timer的模式加入到了CFRunLoopAddTimer方法"
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) { "如果model是commanmodes的話"
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
"將timer物件的model設定為commonmodels ,然後timer加到items裡面去,"
CFSetAddValue(rl->_commonModeItems, rlt);
if (NULL != set) {
CFTypeRef context[2] = {rl, rlt};
/* add new item to all common-modes */
// timer -- items()
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
} else {
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
if (NULL != rlm) {
if (NULL == rlm->_timers) {
CFArrayCallBacks cb = kCFTypeArrayCallBacks;
cb.equal = NULL;
rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
}
}
if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
__CFRunLoopTimerLock(rlt);
if (NULL == rlt->_runLoop) {
rlt->_runLoop = rl;
} else if (rl != rlt->_runLoop) {
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
return;
}
CFSetAddValue(rlt->_rlModes, rlm->_name);
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopTimerFireTSRLock();
__CFRepositionTimerInMode(rlm, rlt, false);
__CFRunLoopTimerFireTSRUnlock();
if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
// Normally we don't do this on behalf of clients, but for
// backwards compatibility due to the change in timer handling...
if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
}
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
}
複製程式碼
這個方法將將timer物件加到items進行呼叫,然後這裡加了後會在__CFRunLoopDoBlocks裡進行消費(呼叫),拿到對應的item,通過doit進行判斷,如果存在則建立一個堆block,引用的物件是timer,然後會進行block的呼叫。
Observer
observer監聽RunLoop的回撥狀態,只要Runloop的model改變就會通知RunLoop
Source
Source0
包含函式回撥指標 signal待處理 wajeup喚醒RunLoop處理事件,處理app內部事件,app自己負責管理的事物
CFRunLoopSourceContext context = {
0,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
schedule,
cancel,
perform,
};
/**
引數一:傳遞NULL或kCFAllocatorDefault以使用當前預設分配器。
引數二:優先順序索引,指示處理執行迴圈源的順序。這裡我傳0為了的就是自主回撥
引數三:為執行迴圈源儲存上下文資訊的結構
*/
CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
CFRunLoopRef rlp = CFRunLoopGetCurrent();
// source --> runloop 指定了mode 那麼此時我們source就進入待緒狀態
CFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode);
// 一個執行訊號
CFRunLoopSourceSignal(source0);
// 喚醒 run loop 防止沉睡狀態
CFRunLoopWakeUp(rlp);
// 取消 移除
CFRunLoopRemoveSource(rlp, source0, kCFRunLoopDefaultMode);
CFRelease(rlp);
複製程式碼
Source1
match_port 和 函式回撥指標 主要用線上程之間通訊
@property (nonatomic, strong) NSPort* subThreadPort;
@property (nonatomic, strong) NSPort* mainThreadPort;
- (void)setupPort{
self.mainThreadPort = [NSPort port];
self.mainThreadPort.delegate = self;
// port - source1 -- runloop
[[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];
[self task];
}
- (void) task {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
self.subThreadPort = [NSPort port];
self.subThreadPort.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.subThreadPort forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run]; "///這裡需要加Run因為子執行緒Runloop預設不開啟"
}];
[thread start];
// 主線 -- 子執行緒
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@", [NSThread currentThread]); // 3
NSString *str;
dispatch_async(dispatch_get_main_queue(), ^{
// 1
NSLog(@"%@", [NSThread currentThread]);
});
});
}
// 執行緒之間通訊
// 主執行緒 -- data
// 子執行緒 -- data1
// 更加低層 -- 核心
// mach
- (void)handlePortMessage:(id)message {
NSLog(@"%@", [NSThread currentThread]); // 3 1
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([message class], &count);
for (int i = 0; i<count; i++) {
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
// NSLog(@"%@",name);
}
sleep(1);
if (![[NSThread currentThread] isMainThread]) {
NSMutableArray* components = [NSMutableArray array];
NSData* data = [@"woard" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
"主執行緒往子執行緒傳送訊息"
[self.mainThreadPort sendBeforeDate:[NSDate date] components:components from:self.subThreadPort reserved:0];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSMutableArray* components = [NSMutableArray array];
NSData* data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
///"子執行緒往主執行緒傳送訊息"
[self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
}
複製程式碼
橫線下面為我概括總結正在一步一步學習中,下一篇文章將通過RunLoop的原始碼進行分析他們是如何實現的
RunLoop總結
Runloop的作用就是來管理和排程執行緒是他在沒有任務的時候不會被銷燬。
Runloop與執行緒的關係是一一對應的,一個執行緒一次只能執行一個任務,任務執行完畢後執行緒就會被銷燬。對於主執行緒來說,runloop在程式一啟動就預設建立好了子執行緒Runloop預設不開啟。
Runloop 與 執行緒關係是一對一 與mode關係是一對n 意思是同時可以持有多個model,model裡面分憂、Source、Tiemr、Observer 三中item同樣也是1對多的關係可以持有多個item,Timer
Observer是以陣列的形式存在,而Source 是以集合的形式存在Runloop中。
Source0 的作用是處理app的事物處理 觸控事件等
Source1 的作用是NSPort 來進行執行緒通訊這種操作偏底層偏核心
observers 的作用是Runloop的model改變的時候會通知Runloop
timer的作用是
Runloop工作的流程是:
- 通知observer即將進入Runloop
- 通知Observer將要處理Timer
- 通知Observer將要處理Source0
- 處理Source0
- 如果有Source1調到第9步
- 通知observer,執行緒即將進入休眠
- 休眠等待喚醒(source0,Timer,外部手動喚醒)
- 通知observer執行緒剛剛被喚醒
- 處理Source1,並回到第2步
- 通知observer將要推出Runloop