RunLoop簡介
運行循環(huán),在程序運行過程中循環(huán)做一些事情,如果沒有Runloop程序執(zhí)行完畢就會立即退出,如果有Runloop程序會一直運行,并且時時刻刻在等待用戶的輸入操作。RunLoop可以在需要的時候自己跑起來運行,在沒有操作的時候就停下來休息。充分節(jié)省CPU資源,提高程序性能。
RunLoop作用
1、保持程序持續(xù)運行
2、處理App中的各種事件
3、節(jié)省CPU資源,提高程序性能
RunLoop和線程間的關系
1、每條線程都有唯一的一個與之對應的RunLoop對象
2、RunLoop保存在一個全局的Dictionary里,線程作為key,RunLoop作為value
3、主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建
4、RunLoop在第一次獲取時創(chuàng)建,在線程結束時銷毀
RunLoop的結構
主要的成員變量:
CFMutableSetRef _sources0;//基于Port的線程間通信
CFMutableSetRef _sources1;//觸摸事件,PerformSelectors
CFMutableArrayRef _observers;//定時器,NSTimer
CFMutableArrayRef _timers;//監(jiān)聽器,用于監(jiān)聽RunLoop的狀態(tài)一個RunLoop包含若干個Mode,每個Mode又包含若干個Source0/Source1/Timer/Observer
RunLoop相關類及作用
CFRunLoopRef:獲得當前RunLoop和主RunLoop
CFRunLoopModeRef:RunLoop 運行模式,只能選擇一種,在不同模式中做不同的操作
CFRunLoopSourceRef: 事件源,輸入源
CFRunLoopTimerRef:定時器時間
CFRunLoopObserverRef:觀察者
CFRunLoopModeRef的5種模式
1、kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
2、UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
3、UIInitializationRunLoopMode:在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用,會切換到kCFRunLoopDefaultMode
4、GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
5、kCFRunLoopCommonModes:這是一個占位用的Mode,作為標記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一種真正的Mode
CFRunLoopModeRef的應用
解決在ScrollView中,滑動控制器時候定時器失效的方案
/*
創(chuàng)建定時器
*/
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
/*
把定時器加入到runloop中
*重點是Mode*
1、如果Mode使用NSDefaultRunLoopMode:在ScrollView滑動的時候,定時器就會失效,停止滑動的時候又會恢復
2、如果Mode使用UITrackingRunLoopMode:在ScrollView滑動的時候,定時器是正常的,停止滑動的時候就會失效
原因是:當ScrollView滑動的時候,RunLoop的Mode會自動切換成UITrackingRunLoopMode模式,因此timer失效;當停止滑動,RunLoop又會切換回NSDefaultRunLoopMode模式,因此timer又會重新啟動。
為什么使用NSRunLoopCommonModes模式就可以滑動和不滑動的時候都正常了呢?因為NSRunLoopCommonModes是一個占位用的Mode,用來標記UITrackingRunLoopMode和kCFRunLoopDefaultMode。因此UITrackingRunLoopMode和kCFRunLoopDefaultMode被標記之后的作用就相同了。
*/
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopObserverRef的應用
CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變
//創(chuàng)建監(jiān)聽者
/*
第一個參數(shù) CFAllocatorRef allocator:分配存儲空間 CFAllocatorGetDefault()默認分配
第二個參數(shù) CFOptionFlags activities:要監(jiān)聽的狀態(tài) kCFRunLoopAllActivities 監(jiān)聽所有狀態(tài)
第三個參數(shù) Boolean repeats:YES:持續(xù)監(jiān)聽 NO:不持續(xù)
第四個參數(shù) CFIndex order:優(yōu)先級,一般填0即可
第五個參數(shù) :回調(diào) 兩個參數(shù)observer:監(jiān)聽者 activity:監(jiān)聽的事件
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop進入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要處理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要處理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒來了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 給RunLoop添加監(jiān)聽者
/*
第一個參數(shù) CFRunLoopRef rl:要監(jiān)聽哪個RunLoop,這里監(jiān)聽的是主線程的RunLoop
第二個參數(shù) CFRunLoopObserverRef observer 監(jiān)聽者
第三個參數(shù) CFStringRef mode 要監(jiān)聽RunLoop在哪種運行模式下的狀態(tài)
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
/*
CF的內(nèi)存管理(Core Foundation)
凡是帶有Create、Copy、Retain等字眼的函數(shù),創(chuàng)建出來的對象,都需要在最后做一次release
GCD本來在iOS6.0之前也是需要我們釋放的,6.0之后GCD已經(jīng)納入到了ARC中,所以我們不需要管了
*/
CFRelease(observer);
- CFOptionFlags activities系統(tǒng)提供的枚舉
kCFRunLoopEntry = (1UL << 0), // 即將進入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7),// 即將退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
RunLoop退出
主線程銷毀RunLoop退出
Mode中有一些Timer 、Source、 Observer,這些保證Mode不為空時保證RunLoop沒有空轉(zhuǎn)并且是在運行的,當Mode中為空的時候,RunLoop會立刻退出
我們在啟動RunLoop的時候可以設置什么時候停止
子線程開啟RunLoop
/*
創(chuàng)建子線程并開啟
*/
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(show) object:nil];
[thread start];
/*
在子線程中創(chuàng)建RunLoop
*/
-(void)show{
// 注意:打印方法一定要在RunLoop創(chuàng)建開始運行之前,如果在RunLoop跑起來之后打印,RunLoop先運行起來,已經(jīng)在跑圈了就出不來了,進入死循環(huán)也就無法執(zhí)行后面的操作了。
// 但是此時點擊Button還是有操作的,因為Button是在RunLoop跑起來之后加入到子線程的,當Button加入到子線程RunLoop就會跑起來
NSLog(@"%s",__func__);
// 1.創(chuàng)建子線程相關的RunLoop,在子線程中創(chuàng)建即可,并且RunLoop中要至少有一個Timer 或 一個Source 保證RunLoop不會因為空轉(zhuǎn)而退出,因此在創(chuàng)建的時候直接加入
// 添加Source [NSMachPort port] 添加一個端口
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
// 添加一個Timer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//創(chuàng)建監(jiān)聽者
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop進入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要處理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要處理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒來了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 給RunLoop添加監(jiān)聽者
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 2.子線程需要開啟RunLoop
[[NSRunLoop currentRunLoop]run];
CFRelease(observer);
}
自動釋放池
RunLoop內(nèi)部有一個自動釋放池,當RunLoop開啟時,就會自動創(chuàng)建一個自動釋放池,當RunLoop在休息之前會釋放掉自動釋放池的東西,然后重新創(chuàng)建一個新的空的自動釋放池,當RunLoop被喚醒重新開始跑圈時,Timer,Source等新的事件就會放到新的自動釋放池中,當RunLoop退出的時候也會被釋放。
重點
創(chuàng)建子線程相關的RunLoop,RunLoop中要至少有一個Timer 或 一個Source 保證RunLoop不會因為空轉(zhuǎn)而退出,因此在創(chuàng)建的時候直接加入,如果沒有加入Timer或者Source,或者只加入一個監(jiān)聽者,運行程序會崩潰
- 特別鳴謝:http://www.itdecent.cn/p/de752066d0ad
- 本文內(nèi)容摘自上面的大神