iOS開發,ios開發吧
RunLoop概念
運行迴圈,一個 run loop 就是一個事件處理的迴圈,用來不停的調度工作以及處理事件
作用
- 保持程式的持續運行
- 監聽處理App中的各種事件(觸摸事件,定時器事件,selector事件)
- 節省CPU資源,提高程式效能:該做事時做事,該休息時休息
- 一次RunLoop迴圈負責繪製螢幕上所有的點
入口函數
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }
}
UIApplicationMain()此函數內部就啟動了一個RunLoop,所以此函數一直沒有返回,保持了程式的持續運行,這個預設啟動的RunLoop是跟主線程相關的
RunLoop對象
- Foundation架構下 : NSRunLoop (基於 CFRunLoopRef 的封裝,提供了物件導向的 API,但是這些 API 不是安全執行緒的)
- Core Foundation架構下 : CFRunLoopRef (純 C 函數的 API,所有這些 API 都是安全執行緒的)
RunLoop與線程
- 每條線程有唯一的一個與之對應的RunLoop對象
- 主線程的RunLoop已經自動建立好了,子線程的RunLoop需要手動建立
- RunLoop在第一次擷取時建立,線上程結束時銷毀
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];//主線程對應的RunLoop [NSRunLoop mainRunLoop];//當前線程對應的RunLoop currentRunloop.getCFRunLoop;//轉化為CFRunLoop CFRunLoopGetMain(); CFRunLoopGetCurrent(); //開啟一個子線程 [[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil] start]; -(void)run { //建立子線程對應的RunLoop,currentRunLoop 懶載入的 [NSRunLoop currentRunLoop]; }
以下是蘋果官方源碼,通過分析原始碼可以看出利用pthread作為全域字典中的key,並建立與之對應的RunLoop作為Value,RunLoop在我們擷取的時候建立,不擷取不建立,主線程的RunLoop在一開始就自動建立。線程與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;}CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; return _CFRunLoopGet0(pthread_self());}//全域的Dictionary,key 是 pthread_t, value 是 CFRunLoopRefstatic CFMutableDictionaryRef __CFRunLoops = NULL;//訪問 Dictionary 時的鎖static CFLock_t loopsLock = CFLockInit;//擷取一個 pthread 對應的 RunLoopCF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { //如果傳進來的線程等於 0 if (pthread_equal(t, kNilPthreadT)) { //當前線程等於主線程 t = pthread_main_thread_np(); } //給操作加鎖 __CFLock(&loopsLock); //如果當前RunLoop為空白,建立。 if (!__CFRunLoops) { __CFUnlock(&loopsLock); // 建立字典 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); } // 從字典中擷取當前線程的RunLoop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); if (!loop) { // 如果當前線程的runloop不存在,那麼就為該線程建立一個對應的runloop CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); // 把當前子線程和對應的runloop儲存到字典中 if (!loop) { 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;}
RunLoop相關類
Core Foundation中關於RunLoop的5個類
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef (基於時間的觸發器)
- CFRunLoopObserverRef
說明:一個RunLoop包含若干個Mode,每個Mode又包含若干個Source、Timer、Observer,每次RunLoop啟動時,只能指定一個Mode,這個Mode被稱作CurrentMode,如果需要切換Mode,只能退出Loop,在重新指定一個Mode進入,這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。
1.CFRunLoopSourceRef :事件來源(輸入源)
- Source0:非基於Port的 (使用者主動觸發的事件)
- Source1:基於Port的 (系統內部的訊息事件)
(Port是線程間通訊的一種方式,如果兩個線程之間想通訊,可以通過Port來通訊。)
2.CFRunLoopTimerRef
基於時間觸發器,當其加入RunLoop時,RunLoop會註冊對應的時間點,當時間點到,RunLoop會被喚醒執行裡面的回調。
3.CFRunLoopObserverRef
觀察者,能夠監聽RunLoop的狀態改變。
/* Run Loop Observer Activities */typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0),//即將進入LoopkCFRunLoopBeforeTimers = (1UL << 1),//即將處理TimerkCFRunLoopBeforeSources = (1UL << 2),//即將處理SourcekCFRunLoopBeforeWaiting = (1UL << 5),//即將進入休眠kCFRunLoopAfterWaiting = (1UL << 6),//剛從休眠中喚醒kCFRunLoopExit = (1UL << 7),//即將退出LoopkCFRunLoopAllActivities = 0x0FFFFFFFU};
-(void)addObserver{ /* 參數一:怎麼分配儲存空間 參數二:要監聽的狀態 kCFRunLoopAllActivities 所有的狀態 參數三:是否要持續監聽 參數四:優先順序,總是傳0 參數五:當狀態改變時候的回調 */ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: NSLog(@"即將進入runloop"); break; case kCFRunLoopBeforeTimers: NSLog(@"即將處理timer事件"); break; case kCFRunLoopBeforeSources: NSLog(@"即將處理Sources事件"); break; case kCFRunLoopBeforeWaiting: NSLog(@"即將進入睡眠"); break; case kCFRunLoopAfterWaiting: NSLog(@"被喚醒"); break; case kCFRunLoopExit: NSLog(@"runloop退出"); break; default: break; } }); //給runloop添加監聽者 /* 參數一:要監聽哪個runloop 參數二:觀察者 參數三:運行模式 */ CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);}
4.CFRunLoopModeRef :RunLoop的運行模式
在RunLoop中有多個運行模式,但是RunLoop只能選擇一種模式運行,Mode裡面至少要有Timer或者Source
系統預設註冊了5個Mode:
- kCFRunLoopDefaultMode:App的預設Mode,通常主線程是在這個Mode下運行
- UITrackingRunLoopMode:介面跟蹤Mode,用於ScrollView追蹤觸摸滑動,保證介面滑動時不受其他Mode影響
- UIInitializationRunLoopMode:在剛進入App時進入的第一個Mode,啟動完成後就不在使用
- GCEventReceiveRunLoopMode:接受系統事件的內部Mode,通常用不到
- NSRunLoopCommonModes:這是一個佔位用的Mode,不是一種真正的Mode
RunLoop相關問題及解釋
1.Timer與滑動控制項的問題
問題:當在不停地拖動滑動控制項的時候,定時器不工作了
錯誤回答:runloop的優先順序
分析:runloop的幾種常用模式:DefaultMode預設模式,以及UITrackingMode模式,CommonModes佔位模式,runloop進入一種模式的時候,另一種模式的事件不會去處理,當Timer在啟動並執行時候處於DefaultMode模式,當拖動滑動控制項的時候,runloop會立即處理UI事件,切換到UITrackingRunLoopMode模式後,此時DefaultMode模式下的Timer就不工作了
解決:將Timer置於NSRunLoopCommonModes佔位模式下
- (void)viewDidLoad { [super viewDidLoad]; //1.建立定時器 NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //2.將Timer添加到RunLoop中 //[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; //[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode]; [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];}- (void)run { NSLog(@"-------- %@",[NSThread currentThread]);}
View Code
問題:在多線程開發中,耗時操作我們一般會放在子線程中執行,請問這種線程有什麼特點?
執行個體:假如在上面的定時器的run方法中,執行一個耗時操作,此時會卡住主線程,拖動滑動控制項會很不流暢,應該如何解決?
分析:子線程中預設不會開啟RunLoop迴圈,所以子線程在執行完任務之後就會被回收
- (void)viewDidLoad { [super viewDidLoad]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ //1.建立定時器 NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //2.將Timer添加到RunLoop中 [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes]; //3.讓RunLoop運行起來 [[NSRunLoop currentRunLoop] run];//死迴圈,後面的代碼不會執行 NSLog(@"+++++++++"); });}- (void)run { //耗時操作 [NSThread sleepForTimeInterval:1.0]; NSLog(@"-------- %@",[NSThread currentThread]);}
View Code
或者:
- (void)viewDidLoad { [super viewDidLoad]; [NSThread detachNewThreadSelector:@selector(time2) toTarget:self withObject:nil];}-(void)time2{ //建立當前線程的RunLoop NSRunLoop *currentLoop = [NSRunLoop currentRunLoop]; //該方法內部自動添加到RunLoop中,並且運行模式是預設模式 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //開啟RunLoop [currentLoop run];}- (void)run { //耗時操作 [NSThread sleepForTimeInterval:1.0]; NSLog(@"run ----- %@ ---- %@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);}
View Code
此博文會繼續不斷完善及更新關於涉及到runloop的知識,如果有理解不正確或者有涉及到runloop的應用執行個體,請留言吧,歡迎討論
參考文章:http://blog.ibireme.com/2015/05/18/runloop/