iOS多線程-RunLoop簡介

什么是RunLoop?
從字面上來看是運行循環的意思.

內部就是一個do{}while循環,在這個循環里內部不斷的處理各種任務(比如:source/timer/Observer)

RunLoop的存在其實就是為線程而存在的.線程的作用就是執行一個特定的任務,但是默認情況下線程執行完任務后就不能再次執行任務,這是因為默認情況下線程是沒有開啟RunLoop的.如果開啟RunLoop之后,線程執行完任務之后,會一直等待,直到再次接受到任務,接續執行任務.線程銷毀前,會先釋放這個線程所對應的RunLoop.

RunLoop基本作用
保持程序的持續運行,保持線程的持續運行.

處理App中的各種事件(比如觸摸事件,定時器事件,Selector事件)

節省CPU資源,提高程序性能:該做事時做事,該休息時休息

RunLoop對象
ios中有2套API來訪問和使用RunLoop

一套是Fundation(純OC的)框架中的
NSRunLoop
// 獲得當前線程的RunLoop對象[NSRunLoop currentRunLoop];// 獲得主線程的RunLoop對象[NSRunLoop mainRunLoop];

一套是Core Fundation(純C語言的)框架中的
CFRunLoopRef
// 獲得當前線程的RunLoop對象CFRunLoopGetCurrent();// 獲得主線程的RunLoop對象CFRunLoopGetMain();

NSRunLoo和CFRunLoopRef都代表著RunLoop對象.NSRunLoop是基于CFRunLoopRef的一層OC包裝

RunLoop與線程
每條線程都有唯一的一個與之對應的RunLoop對象

主線程的Runloop系統已經自動創建好了,子線程的RunLoop需要手動創建

RunLoop在第一次獲取時由系統自動創建,在線程結束時銷毀

如果想給子線程創建RunLoop,不能直接alloc&init,只要調用獲取當前線程RunLoop方法即可,系統會自動放回當前線程的RunLoop,如果當前線程沒有RunLoop,系統會自動創建.

RunLoop相關類

Core Fundation中關于RunLoop的5個類
CFRunLoopRef: RunLoop對象

CFRunLoopModeRef: RunLoop運行模式.

CFRunLoopSoruceRef: 事件源(輸入源)

CFRunLoopTimerRef:基于時間的觸發器.

CFRunLoopObserverRef: 觀察者,能夠監聽RunLoop的狀態改變

CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop運行模式

一個RunLoop對象包含若干個Mode(模式),每個Mode又包含若干個 source/Timer/Observer

RunLoop運行時,只能指定一個Mode, 這個Mode又稱之為CurrentMode,然后RunLoo就執行CurrentMode中的source/Timer/Observer

如果需要切換Mode,只能退出RunLoop,再重新指定一個Mode進入,這樣做是為了分隔開不同組的Source/Timer/Observer,讓其不受影響

系統默認注冊了5個Mode:
NSDefaultRunLoopMode: App的默認Mode,通常主線程實在這個模式下運行

UITrackingRunLoopMode:界面跟蹤Mode,用于界面控件(ScrollView,tableView等等)追蹤觸摸滑動,保證界面滑動時不受其他Mode影響

UIInitializationRunLoopMode:在剛啟動App是進入的第一個Mode,啟動完成后就不再使用

GSEventReceiveRunLoopMode:接收系統事件的內部Mode,通常用不到

NSRunLoopCommonMode:這是一個占位的Mode,不是一種真正的Mode,(可以看成模式組,默認情況下包括了NSDefaultRunLoopMode,UITrackingRunLoopMode)兩種模式.

CFRunLoopTimerRef
CFRunLoopTimerRef是基于時間的觸發器

CFRunLoopTimerRef基本上說的就是NSTimer,它受RunLoop的Mode影響

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //創建一個NSTimer定時器,默認情況下NSTimer是不會執行的,只有把NSTimer添加到RunLoop中,由RunLoop管理執行
    NSTimer * timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(show) userInfo:nil repeats:YES];
    // 在當前線程中RunLoop添加一個timer, 并告訴runLoop, 這個timer只能在NSDefaultRunLoopMode模式下才能觸發
    // runLoop會找到NSDefaultRunLoopMode,然后把timer添加NSDefaultRunLoopMode中的Timer數組中
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    //在當前線程中RunLoop添加一個timer, 并告訴runLoop, 這個timer只能在NSRunLoopCommonModes模式下才能觸發
    //runLoop會找到NSDefaultRunLoopMode和UITrackingRunLoopMode
    //然后把timer添加NSDefaultRunLoopMode和UITrackingRunLoopMode中的Timer數組中
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    //利用此方法創建的NSTimer, 系統會自動放入當前線程中的currentRunLoop中,并且只能在NSDefaultRunLoop模式下才能觸發
    NSTimer * timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    //雖然通過類方法scheduledTimerWithTimeInterval創建NSTimer,會自動添加到NSDefaultRunLoopMode模式中
    //但我們還是可以修改它的模式
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

CFRunLoopSoruceRef
按照官方文檔,source的分類:
Port-Based Sources:基于端口的事件源:監聽程序響應的端口,基于端口事件是由系統內核自動發送的.

Custom Input Sources: 自定義輸入源:監聽自定義事件源,而自定義的輸入源是需要人工從其他線程發送

Cocoa Perfrom Selector Source: selector事件源

按照源碼函數調用棧,source的分類:
Source0:非基于Prot(端口)的,是用戶主動觸發的事件

Source1:基于Prot(端口)的,通過內核和其他線程相互發送消息

CFRunLoopObserverRef
CFRunLoopObserverRef:觀察者對象,可以監聽RunLoop的狀態

RunLoop狀態:
kCFRunLoopEntry 即將進入runLoop

kCFRunLoopBeforeTimers 即將處理Timer

kCFRunLoopBeforeSources 即將處理source(事件源)

kCFRunLoopBeforeWaiting 即將進入休眠

kCFRunLoopAfterWaiting 即將從休眠中醒來

kCFRunLoopExit 即將退出runLoop

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 
    //創建一個CFRunLoopObserverRef
    /*第一個參數: CFRunLoopObserverRef(觀察者)分配內存空間方式第二個參數: 監聽那些狀態 kCFRunLoopAllActivities(監聽所有狀態)第三個參數: 是否每次都要監聽第四個參數: 優先級第五個參數: 回調函數*/
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES,0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { 
    // observer監聽對象 
    //activity Runloop當前狀態
    });
    /*第一個參數: 為那個線程下的RunLoop添加CFRunLoopObserverRef(觀察者)第二個參數: 需要添加的CFRunLoopObserverRef(觀察者)第三個參數: 把監聽添加到RunLoop那個模式中
    */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    //記得內存管理,因為Core Foundation不在ARC管理范圍內
    //帶有Create、Copy、Retain等字眼的函數,創建出來的對象,都需要在最后做一次release
    //銷毀對象函數:CFRelease對象CFRelease(observer);
}

RunLoop處理邏輯

如果RunLoop中沒有Timer或source,RunLoop就會立刻退出

每次運行RunLoop,RunLoop會自動處理之前未處理的消息,并通知相關觀察者.具體順序如下:
1.通知觀察者RunLoop已經啟動

2.通知觀察者即將開始啟動定時器

3.通知觀察者即將啟動非基于端口的事件源

4.啟動任何準備好的非基于端口的事件源

5.如果基于端口的事件源準備好并處于等待得狀態,立即啟動.并進入步驟9

6.通知觀察者線程進入休眠

7.將線程置于休眠直到任意下面的事件發生:
某一事件到達基于端口的源

定時器啟動

RunLoop設置的時間已經超時.(系統底層會給RunLoop設置一個超時時間,源碼中設置的是:9999999999.0)

RunLoop被手動喚醒

8.通知觀察者線程將被喚醒.

9.處理未處理的事件
如果用戶定義的定時器啟動,處理定時器事件并重啟RunLoop.進入步驟2

如果事件源啟動,傳遞相應的消息

如果RunLooop被顯示喚醒而且時間還沒超時,重啟RunLoop.進入步驟2

10.通知觀察者RunLoop結束.

如何讓子線程成為常駐線程(讓一個子線程不進入消亡狀態,等待其他線程發來消息,處理其他事件)

(#)import "ViewControllerRunLoop.h"
@interface ViewControllerRunLoop ()
@property(nonatomic,strong)NSThread * thread;
@end
@implementation ViewControllerRunLoop
-(void)viewDidLoad { 
    //創建子線程執行任務 self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];    [self.thread start];
}
-(void)run { 
    NSLog(@"跑起來"); 
    //默認情況下,子線程是不會常駐的 
    //只有子線程中runloop啟動,并且runloop中有source或timer,才會常駐 
    //只有常駐線程才能再次執行任務,因為線程中有runloop來處理事件了 
    //子線程的runloop是需要手動創建的, 并且需要手動啟動 
    NSRunLoop * rl = [NSRunLoop currentRunLoop]; 
    //如果子線程的runloop沒有 source / timer 的話, 哪么子線程的runloop會立即關閉 
    //在runLoop中添加一個timer 
    [NSTimer scheduledTimerWithTimeInterval:2 target:self selector: @selector(timerRun) userInfo:nil repeats:YES]; 
    //啟動runloop [rl run]; 
    //如果線程成為了常駐線程,你會發現,不會執行到這行代碼 
    //也就是說這個方法不會執行完, 
    NSLog(@"end");
}
-(void)timerRun{ 
    NSLog(@"%s",__func__);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 
    // 讓子線程再次執行任務 
    [self performSelector:@selector(againRun) onThread:self.thread withObject:nil waitUntilDone:NO];
}
-(void)againRun{ 
    NSLog(@"再次跑起來");
}
@end
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容