runloop運行流程圖




系統默認注冊了5個Mode: kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行 UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響 UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用 GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到 kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode
CFRunLoopModeRef代表RunLoop的運行模式 一個 RunLoop 包含若干個 Mode,每個Mode又包含若干個Source/Timer/Observer 每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode 如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入 這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
定時器
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 定時器可以運行
NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 定時器無法運行
NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 定時器無法運行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
});
dispatch_async(dispatch_get_main_queue(), ^{
NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 定時器可以運行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
});
結論:如果定時器在主線程中開啟,可以正常運行;定時器在子線程中開啟,無法正常運行; 如果對應線程沒有 RunLoop 該方法也會失效,也就是說currentRunloop中 沒有timer,沒有source,也沒有OBServer,添加 [NSRunLoop currentRunLoop] run]試試; 主線程中能夠運行是因為timer添加到runloop中后,主線程runloop默認是啟動的,子線程中的runloop添加的timer,runloop需要手動啟動.
Runloop要啟動要素:1.runloop中要有timer | source | observer其中一個條件 2.runloop得自己啟動
常駐線程
實例:開啟一個線程,不讓線程退出,這個線程一直在接受任務的處理,當一有任務,線程就接受處理,沒有任務就休眠
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[HJThread alloc] initWithTarget:self selector:@selector(invoke) object:nil];
[self.thread start];
}
- (void)invoke
{
@autoreleasepool{
NSLog(@"******invoke*****%@", [NSThread currentThread]);
// 添加一個port讓runloop可以運行循環
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
NSLog(@"************");
}
- (void)touchInvoke
{
NSLog(@"*********touchInvoke*********%@", [NSThread currentThread]);
NSLog(@"%@", [NSRunLoop currentRunLoop]);
}
// 屏幕點擊
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(touchInvoke) onThread:self.thread withObject:nil waitUntilDone:NO];
}
注意:經測試子線程雖然在defaultMode,但是拖動UIScrollView時并不會阻塞當前子線程的runloop defalutMode,因為拖動的view是在主線程的模式UITrackingMode,2個線程的模式互不干擾
停止runloop
1.需要保存當前runloop
2.使用CF函數開啟運行runloop
3.使用CF函數停止runloop
#import "ViewController.h"
@interface ViewController () <NSURLConnectionDataDelegate>
/** runLoop */
@property (nonatomic, assign) CFRunLoopRef runLoop;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 如果在子線程中使用NSURLConnection發送請求是不會有效果,因為子線程的runloop沒有啟動,子線程runloop默認是不啟動的
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSURLConnection *conn = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com/images/234234324limgAB/2342lkjasdf3kkkkk.jpg"]] delegate:self];
// 決定代理方法在哪個隊列中執行
[conn setDelegateQueue:[[NSOperationQueue alloc] init]];
// 啟動子線程的runLoop
// [[NSRunLoop currentRunLoop] run];
// 保存當前runloop
self.runLoop = CFRunLoopGetCurrent();
// 啟動runLoop
CFRunLoopRun();
});
}
#pragma mark - <NSURLConnectionDataDelegate>
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(@"didReceiveResponse******%@", [NSThread currentThread]);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@"didReceiveData******%@", [NSThread currentThread]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"connectionDidFinishLoading******%@", [NSThread currentThread]);
// 停止RunLoop
CFRunLoopStop(self.runLoop);
}
@end
定時器自動調度
// 任務自動調度,無需手動fire
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:YES];
------------------2種方式等價------------------
// 任務自動調度,無需手動fire
NSTimer *timer = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 定時器可以運行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
監聽runloop運行循環的事件狀態變化,可以用以攔截一些事件的處理
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // : 即將到 runloop
kCFRunLoopBeforeTimers = (1UL << 1), // : 即將處理 timer 之前
kCFRunLoopBeforeSources = (1UL << 2),// : 即將處理 source 之前
kCFRunLoopBeforeWaiting = (1UL << 5),// : 即將休眠
kCFRunLoopAfterWaiting = (1UL << 6), // : 休眠之后
kCFRunLoopExit = (1UL << 7), // : 退出
kCFRunLoopAllActivities = 0x0FFFFFFFU// : 所有的活動
};
- (IBAction)btnClick:(id)sender {
NSLog(@"btnDidClick*****");
}
- (void)viewDidLoad {
[super viewDidLoad];
// 監聽runloop狀態變化
[self observer];
}
- (void)observer
{
// 創建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"****監聽到RunLoop狀態發生改變**%zd", activity);
});
// 添加觀察者:監聽RunLoop的狀態
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 釋放Observer
CFRelease(observer);
}
Autorelease Pool
為啥OC程序的Main函數使用autorelease pool包括了,從運行循環中也可以解釋,運行循環中不斷的在接受和處理事件,中間的會產生很多的變量和資源,產生的這些變量和資源會放到自動釋放池中,因runloop一直沒有退出,那么變量就可能沒有被釋放,但是加上了autorelease pool后,在runloop進入休眠前時,autorelease pool就會釋放臨時變量和資源,這樣內存就可以得到管理; runloop重新運行時就又會創建一個自動釋放池
自動釋放池會再在Runloop休眠前(beforeWait)釋放,又會緊接著創建一個新的自動釋放池,用以下次喚醒時使用
Runloop應用:
.NSTimer
.ImageView顯示
.PerformSelecor 可以給線程發送消息
.常駐線程,開啟一個常駐線程,讓線程不銷毀,等待其他線程發送消息,然后處理任務和事件
>在子線程中開啟一個定時器
>在子線程中進行一些長期監控,語音通話,或是傳輸數據等業務場景
.自動釋放池
.可以添加Observer監聽Runloop的狀態,這樣可以攔截一些事件處理,比如過濾器功能等.
.可以讓某些事件(行為,任務)在特定的模式下執行
總結:
>運行循環,跑圈. 可以查看源碼里面內部是一個do while循環,循環內部不斷處理任務和事件(source,timer,observer)
>創建開啟運行要素最少得要有source(消息源,source0,source1),timer中的一個條件
>一個線程對應一個Runloop(底層是通過字典保存Runloop,線程作為key,Runloop作為value),主線程的Runloop默認已經開啟,子線程的Runloop的手動啟動,通過調用[runloop run]方法啟動
>Runloop只能選擇一個Mode模式啟動,如果當前模式Mode沒有任何Source(消息源),timer,Runloop就會直接退出
>當kCFRunLoopEntry會創建新的釋放池用以Runloop被喚醒時使用,自動釋放池在runloop即將進入休眠時(kCFRunLoopBeforeWaiting)釋放或kCFRunLoopExit退出時,自動釋放池釋放
>子線程runloop默認是不啟動的,如果子線程runloop需要手動啟動
可以參考文獻:
http://www.cocoachina.com/ios/20150601/11970.html
浙公網安備 33010602011771號