從字面意思看,Runloop的意思就是
運行循環,跑圈
如果沒有Runloop
int main(int argc, char * argv[]) { NSLog(@"execute main function");//程序開始 return 0;//程序結束 }
每條線程都有唯一的一個與之對應的RunLoop對象
主線程的RunLoop已經自動創建好了,子線程的RunLoop需要主動創建
RunLoop在第一次獲取時創建,在線程結束時銷毀
iOS中有2套API來訪問和使用RunLoop
Foundation
NSRunLoopCore Foundation
CFRunLoopRefNSRunLoop和CFRunLoopRef都代表著RunLoop對象
NSRunLoop是基於CFRunLoopRef的一層OC包裝,所以要了解RunLoop內部結構,需要多研究CFRunLoopRef層面的API(Core Foundation層面)
Foundation
[NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象 [NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象Core Foundation
CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象 CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
Core Foundation中關於RunLoop的5個類
CFRunLoopRef CFRunLoopModeRef 【Runloop的運行模式】 CFRunLoopSourceRef 【Runloop要處理的事件源】 CFRunLoopTimerRef 【Timer事件】 CFRunLoopObserverRef 【Runloop的觀察者(監聽者)】CFRunLoopModeRef代表RunLoop的運行模式
一個 RunLoop 包含若干個 Mode,每個Mode又包含若干個Source/Timer/Observer
每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode
如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入
這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
系統默認注冊了5個Mode:
kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
UITrackingRunLoopMode:界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
典型事例就是拖動scrollview時,NSTimer不工作,其原因就是,拖動時runloop切換到界面追蹤模式,此時其他模式的事件(例如定時器事件)就無法繼續。UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成後就不再使用
GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode
CFRunLoopSourceRef是事件源(輸入源)
以前的分法
Port-Based Sources Custom Input Sources Cocoa Perform Selector Sources現在的分法
Source0:非基於Port的 Source1:基於Port的CFRunLoopTimerRef是基於時間的觸發器
基本上說的就是NSTimer
CFRunLoopObserverRef是觀察者,能夠監聽RunLoop的狀態改變
可以監聽的時間點有以下幾個
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), //即將進入Runloop kCFRunLoopBeforeTimers = (1UL << 1), //即將處理NSTimer kCFRunLoopBeforeSources = (1UL << 2), //即將處理Sources kCFRunLoopBeforeWaiting = (1UL << 5), //即將進入休眠 kCFRunLoopAfterWaiting = (1UL << 6), //剛從休眠中喚醒 kCFRunLoopExit = (1UL << 7), //即將退出runloop kCFRunLoopAllActivities = 0x0FFFFFFFU //所有狀態改變 };
1)NSTimer
2)ImageView顯示:控制方法在特定的模式下可用
3)PerformSelector
4)常駐線程:在子線程中開啟一個runloop
5)自動釋放池的釋放時機:
第一次創建:進入runloop的時候
最後一次釋放:runloop退出的時候
其它創建和釋放:當runloop即將休眠的時候會把之前的自動釋放池釋放,然後重新創建一個新的釋放池
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event { [self observer]; } -(void)observer { //1.創建監聽者 /* 第一個參數:怎麼分配存儲空間 第二個參數:要監聽的狀態 kCFRunLoopAllActivities 所有的狀態 第三個參數:時候持續監聽 第四個參數:優先級 總是傳0 第五個參數:當狀態改變時候的回調 */ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { /* kCFRunLoopEntry = (1UL << 0), 即將進入runloop kCFRunLoopBeforeTimers = (1UL << 1), 即將處理timer事件 kCFRunLoopBeforeSources = (1UL << 2),即將處理source事件 kCFRunLoopBeforeWaiting = (1UL << 5),即將進入睡眠 kCFRunLoopAfterWaiting = (1UL << 6), 被喚醒 kCFRunLoopExit = (1UL << 7), runloop退出 kCFRunLoopAllActivities = 0x0FFFFFFFU */ switch (activity) { case kCFRunLoopEntry: NSLog(@"即將進入runloop"); break; case kCFRunLoopBeforeTimers: NSLog(@"即將處理timer事件"); break; case kCFRunLoopBeforeSources: NSLog(@"即將處理source事件"); break; case kCFRunLoopBeforeWaiting: NSLog(@"即將進入睡眠"); break; case kCFRunLoopAfterWaiting: NSLog(@"被喚醒"); break; case kCFRunLoopExit: NSLog(@"runloop退出"); break; default: break; } }); /* 第一個參數:要監聽哪個runloop 第二個參數:觀察者 第三個參數:運行模式 */ CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer, kCFRunLoopDefaultMode); //NSDefaultRunLoopMode == kCFRunLoopDefaultMode //NSRunLoopCommonModes == kCFRunLoopCommonModes } - (IBAction)RunloopObserver:(id)sender { NSLog(@"處理點擊事件:%s",__func__); }
效果圖
- (IBAction)createThread:(id)sender { //1.創建線程 self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(task) object:nil]; [self.thread start]; } -(void)task { NSLog(@"task---%@",[NSThread currentThread]); // while (1) { // NSLog(@"task1---%@",[NSThread currentThread]); // } //解決方法:開runloop //1.獲得子線程對應的runloop NSRunLoop *runloop = [NSRunLoop currentRunLoop]; //保證runloop不退出 //NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //[runloop addTimer:timer forMode:NSDefaultRunLoopMode]; [runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; //2.默認是沒有開啟 [runloop run];//開啟 // [runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];//開啟Runloop,到特定時間結束 NSLog(@"---end----"); } - (IBAction)doOthertask:(id)sender { //[self.thread start]; [self performSelector:@selector(OtheTask) onThread:self.thread withObject:nil waitUntilDone:YES]; } -(void)OtheTask { NSLog(@"OtheTask---%@",[NSThread currentThread]); } -(void)run { NSLog(@"%s",__func__); } //Runloop中自動釋放池的創建和釋放 //第一次創建:啟動runloop //最後一次銷毀:runloop退出的時候 //其他時候的創建和銷毀:當runloop即將睡眠的時候銷毀之前的釋放池,重新創建一個新的
效果圖
Runloop源碼分析