iOS開發多線程-RunLoop
一、什麽是RunLoop
1.從字面意思看
1)運行循環
2)跑圈
2.基本作用
1)保持程序的持續運行
2)處理App中的各種事件(比如觸摸事件、定時器事件、Selector事件)
3)節省CPU資源,提高程序性能:該做事時做事,該休息時休息
4)......
3.如果沒有RunLoop
說明:沒有RunLoop的情況下,程序執行到第3行後程序就結束了
4.如果有了RunLoop
說明:有RunLoop的情況下,由於main函數裏面啟動了個RunLoop,所以程序並不會馬上退出,保持持續運行狀態
5.main函數中的RunLoop
1)第14行代碼UIApplicationMain函數內部就啟動了一個RunLoop,
2)所以UIApplicationMain函數一直沒有返回, 保持了程序的持續運行,
3)這個默認啟動的RunLopp是跟主線程相關聯的
二、RunLoop對象
1.iOS中有2tAPI來訪問和使用RunLoop
1)Foundation 框架
NSRunLoop
2)Core Foundation
CFRunLoopRef
2.NSRunLoop和CFRunLoopRef都代表著RunLoop對象
3.NSRunLoop是基於CFRunLoopRef的一層OC包裝, 所以要了解RunLoop內部結構,需要多研究CFRunLoopRef層面的API (Core Foundation 層面)
三、RunLoop資料
1.蘋果官方文檔
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
2.CFRunLoopRef是開源的
http://opensource.apple.com/source/CF/CF-1151.16/
四、RunLoop與線程
1.每條線程都有唯一的一個與之對應的RunLoop對象
2.主線程的RunLoop已經自動創建好了,子線程的RunLoop需要主動創建
3.RunLoop在第一次獲取時創建,在線程結束時銷毀
4.獲取RunLoop對象
1)Foundation
[objc] view plain copy
- [NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
- [NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
2)Core Foundation
[objc] view plain copy
- CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
- CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
代碼示例:
[objc] view plain copy
- #import "ViewController.h"
- @interface ViewController ()
- @end
- @implementation ViewController
- - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- // NSRunLoop 主線程對應的RunLoop對象
- NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
- NSLog(@"mainRunLoop = %@", mainRunLoop);
- // NSRunLoop 獲得當前方法所在線程對應的RunLoop
- NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
- NSLog(@"currentRunLoop = %@", currentRunLoop);
- // CFRunLoopRef 主線程對應的RunLoop對象
- CFRunLoopRef cfMainRunLoop = CFRunLoopGetMain();
- NSLog(@"cfMainRunLoop = %@", cfMainRunLoop);
- // CFRunLoopRef 獲得當前方法所在線程對應的RunLoop
- CFRunLoopRef cfCurrentRunLoop = CFRunLoopGetCurrent();
- NSLog(@"cfCurrentRunLoop = %@", cfCurrentRunLoop);
- // 開啟一條子線程
- NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
- [thread start];
- }
- - (void)run
- {
- // 註意: 如果想給子線程添加RunLoop, 不能直接alloc init
- // [[NSRunLoop alloc] init]; // 錯誤
- // 只要調用currentRunLoop方法, 系統就會自動創建一個RunLoop, 添加到當前線程中
- [NSRunLoop currentRunLoop]; // 這個方法是懶加載
- }
- @end
5.RunLoop相關類
CoreFoundation中關於RunLoop的5個類 1)CFRunLoopRef 2)CFRunLoopModeRef 3)CFRunLoopSourceRef 4)CFRunLoopTimerRef 5)CFRunLoopObserverRef
6.CFRunLoopModeRef
6.1 CFRunLoopModeRef代表RunLoop的運行模式 1)一個 RunLoop包含若幹個 Mode,每個Mode又包含若幹個Source/Timer/Observer 2)每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode
3)如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入,這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
6.2系統默認註冊了5個Mode:
1)kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行 2)UITrackingRunLoopMode:界面跟蹤 Mode,用於 ScrollView追蹤觸摸滑動,保證界面滑動時不受其他Mode 影響 3)UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成後就不再使用 4)GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到 5)kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode代碼示例:
[objc] view plain copy
- #import "ViewController.h"
- @interface ViewController ()
- @property (nonatomic, strong) dispatch_source_t timer;
- @end
- @implementation ViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- }
- // 演示函數調用棧:source0(用戶主動觸發)和source1
- - (IBAction)btnClick:(id)sender {
- NSLog(@"%s", __func__);
- }
- - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- // [self timer2];
- // 開啟後臺線程執行timer2方法
- // [self performSelectorInBackground:@selector(timer2) withObject:nil];
- [self gcdTimer];
- }
- - (void)gcdTimer
- {
- NSLog(@"%s", __func__);
- /*
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- NSLog(@"------2.0秒後執行-----");
- });
- */
- // 創建一個定時器
- /**
- * 參數1: source的類型 DISPATCH_SOURCE_TYPE_TIMER:定時器
- * 參數2: 線程等信息
- * 參數3: 對第二個參數的描述信息
- * 參數4: 傳遞一個隊列, 該隊列對應了將來的回調方法在哪個線程中執行
- */
- dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
- // 對timer這個局部變量添加一個引用,以防止被釋放
- self.timer = timer;
- // 設置定時器開始的時間和間隔的時間, 以及精準度
- // 開始時間
- // dispatch_time_t startTime = DISPATCH_TIME_NOW;
- dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
- // 間隔時間
- uint64_t interval = 1.0 * NSEC_PER_SEC;
- /**
- * 參數1: 需要給哪個定時器設置
- * 參數2: 定時器開始的時間 / DISPATCH_TIME_NOW 立即執行
- * 參數3: 定時器開始之後的間隔時間
- * 參數4: 定時器間隔執行的精準度, 傳入0代表最精準(盡量的讓定時器精準), 傳入一個大於0的值, 代表多少秒的範圍是可以接受的
- * 參數4存在的意義: 主要是為了提高程序的性能
- * 註意: Dispatch的定時器接收的時間是納秒
- */
- dispatch_source_set_timer(timer, startTime, interval, 00 * NSEC_PER_SEC);
- // 指定定時器的回調方法
- dispatch_source_set_event_handler(timer, ^{
- NSLog(@"test-----%@", [NSThread currentThread]);
- });
- // 開啟定時器(恢復)
- dispatch_resume(timer);
- }
- - (void)timer1
- {
- NSLog(@"-----start-----");
- // 創建一個定時器
- NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
- // 將NSTime添加到RunLoop中,並告訴系統,當前Timer只有在RunLoop的默認模式下才有效
- // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
- // // 將NSTime添加到RunLoop中,並告訴系統,當前Timer只有在RunLoop的默認模式下才有效
- // [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
- // 將NSTimer添加到RunLoop中, 並且告訴系統, 在所有被"標記"common的模式都可以運行
- /*
- common modes = <CFBasicHash 0x7fc0b8615250 [0x104be7180]>{type = mutable set, count = 2,
- entries =>
- 0 : <CFString 0x1058bae50 [0x104be7180]>{contents = "UITrackingRunLoopMode"}
- 2 : <CFString 0x104bc3080 [0x104be7180]>{contents = "kCFRunLoopDefaultMode"}
- }
- UITrackingRunLoopMode和kCFRunLoopDefaultMode都被標記為了common模式, 所以只需要將timer的模式設置為forMode:NSRunLoopCommonModes, 就可以在默認模式和追蹤模式都能夠運行
- */
- [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- }
- - (void)timer2
- {
- // 註意: 如果是通過scheduledTimerWithTimeInterval創建的NSTimer, 默認就會添加到RunLoop得DefaultMode中 , 所以它會自動運行
- NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
- // 雖然默認已經添加到DefaultMode中, 但是我們也可以自己修改它的模式
- [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- // 開啟子線程對應的RunLoop
- /**
- * 註意:
- * 1)如果是子線程,那麽需要手動創建子線程對應的RunLoop
- * 2)子線程對應的RunLoop還需要手動開啟
- */
- [[NSRunLoop currentRunLoop] run];
- }
- - (void)show
- {
- NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
- }
- @end
7.CFRunLoopSourceRef
7.1 CFRunLoopSourceRef是事件源(輸入源) 7.2 以前的分法 1) Port-BasedSources 2) Custom InputSources 3) Cocoa PerformSelector Sources 7.3 現在的分法 1)Source0:非基於Port的,用於用戶主動觸發的事件 2)Source1:基於Port的,通過內核和其它線程相互發送消息
8.CFRunLoopTimerRef
1)CFRunLoopTimerRef是基於時間的觸發器 2)基本上說的就是NSTimer,它會受到runloop的mode的影響 3)GCD的定時器不受Runloop的mode的影響
9.CFRunLoopObserverRef
CFRunLoopObserverRef是觀察者,能夠監聽RunLoop的狀態改變 可以監聽的時間點有以下幾個
10. CFRunLoopObserverRef
10.1 添加Observer
[objc] view plain copy
- // 創建observer
- CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
- NSLog(@"----監聽到RunLoop狀態發生改變---%zd", activity);
- });
- // 添加觀察者:監聽RunLoop的狀態
- CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
- // 釋放Observer
- CFRelease(observer);
代碼示例:
[objc] view plain copy
- #import "ViewController.h"
- @interface ViewController ()
- @end
- @implementation ViewController
- // 當手指觸摸控制器View的時候,調用該方法
- - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- // 創建Observer
- /**
- * 參數1: 指定如果給Observer分配存儲空間
- * 參數2: 需要監聽的狀態類
- * kCFRunLoopEntry = (1UL << 0), 即將啟動(進入)的時候
- * kCFRunLoopBeforeTimers = (1UL << 1), 即將處理timer事件
- * kCFRunLoopBeforeSources = (1UL << 2), 即將處理source事件
- * kCFRunLoopBeforeWaiting = (1UL << 5), 即將進入睡眠
- * kCFRunLoopAfterWaiting = (1UL << 6), RunLoop被喚醒
- * kCFRunLoopExit = (1UL << 7), RunLoop退出
- * kCFRunLoopAllActivities = 0x0FFFFFFFU 監聽所有狀態
- *
- */
- 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(@"即將處理source");
- break;
- case kCFRunLoopBeforeWaiting:
- NSLog(@"即將進入睡眠");
- break;
- case kCFRunLoopAfterWaiting:
- NSLog(@"RunLoop剛從睡眠中喚醒");
- break;
- case kCFRunLoopExit:
- NSLog(@"RunLoop即將退出");
- break;
- default:
- break;
- }
- });
- // 給主線程的RunLoop添加一個觀察者,要監聽的是RunLoop的哪種運行模式
- /**
- * 參數1: 需要給哪個RunLoop添加觀察者
- * 參數2: 需要添加的Observer對象
- * 參數3: 在哪種模式下可以監聽 kCFRunLoopDefaultMode == NSDefaultRunLoopMode
- */
- CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
- // 釋放對象
- CFRelease(observer);
- [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
- }
- - (void)show{
- NSLog(@"%s", __func__);
- }
- @end
打印查看:
五、CF的內存管理(CoreFoundation)
1. 凡是帶有Create、Copy、Retain等字眼的函數,創建出來的對象,都需要在最後做一次release 比如CFRunLoopObserverCreate release函數:CFRelease(對象);
六、RunLoop處理邏輯
1.官方版本
2.網友整理版本
七、RunLoop應用
1.NSTimer
2.ImageView顯示
3.PerformSelector
4.常駐線程
5.自動釋放池
代碼示例: [objc] view plain copy
- #import "ViewController.h"
- @interface ViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *imageView;
- /** 引用線程 */
- @property (nonatomic, strong) NSThread *thread;
- @end
- @implementation ViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- //RunLoop的自動釋放池子
- /*
- 自動釋放池什麽時候創建和釋放
- 1.第一次創建, 是在RunLoop進入的時候創建 對應的狀態 = kCFRunLoopEntry
- 2.最後一次釋放, 是在RunLoop退出的時候 對應的狀態 = kCFRunLoopExit
- 3.其它創建和釋放
- * 每次睡覺的時候都會釋放前自動釋放池, 然後再創建一個新的
- _wrapRunLoopWithAutoreleasePoolHandler activities = 0x1,
- 1 = kCFRunLoopEntry 進入RunLoop 創建自動釋放池
- _wrapRunLoopWithAutoreleasePoolHandler activities = 0xa0,
- 160 = kCFRunLoopBeforeWaiting 即將進入睡眠 ,先釋放上一次創建的自動釋放池, 然後再創建一個新的釋放池
- +
- kCFRunLoopExit 即將退出RunLoop 釋放自動釋放池
- */
- NSLog(@"%s", __func__);
- // 添加到RunLoop的默認運行模式下inModes:@[NSDefaultRunLoopMode,UITrackingRunLoopMode]
- // [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20151128_3"] afterDelay:3.0];
- // 指定運行模式進行特定的操作
- [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20151128_3"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode,UITrackingRunLoopMode]];
- }
- // 執行任務1的按鈕(創建線程執行任務)
- - (IBAction)oneBtnClick {
- // 創建線程並且執行任務1
- self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(operation1) object:nil];
- [self.thread start];
- // 註意:默認情況下一個線程只能使用一次, 也就是說只能執行一個操作, 執行完畢之後就不能使用了
- }
- // 執行任務2的按鈕(指定按鈕1創建的線程來執行)
- - (IBAction)twoBtnClick {
- // 指定子線程執行任務2
- [self performSelector:@selector(operation2) onThread:self.thread withObject:nil waitUntilDone:YES];
- }
- // 任務1
- - (void)operation1
- {
- NSLog(@"---start---");
- // 保證子線程不被銷毀
- // 采用死循環的方式是行不通的
- // while (1) {
- // NSLog(@"%s---%@", __func__, [NSThread currentThread]);
- // }
- // 子線程的NSRunLoop 需要手動創建
- // 子線程的NSRunLoop 需要手動開啟
- // 如果子線程的NSRunLoop沒有設置source or timer,那麽子線程的NSRunLoop會立即關閉
- NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
- // [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
- NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
- [runLoop addTimer:timer forMode:NSRunLoopCommonModes];
- // 啟動RunLoop
- [runLoop run];
- // 註意點: NSRunLoop只會檢查有沒有source和timer, 沒有就關閉, 不會檢查observer
- // CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
- //
- // });
- //
- // CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
- //
- // // 釋放對象
- // CFRelease(observer);
- // 該代碼永遠不會被執行
- NSLog(@"---end---");
- }
- // 任務2
- - (void)operation2
- {
- NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
- }
- - (void)test
- {
- NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
- }
- @end
執行效果請自行演示
八、RunLoop面試題
1. 什麽是RunLoop? 1)從字面意思看:運行循環、跑圈 2)其實它內部就是do-while循環,在這個循環內部不斷地處理各種任務(比如Source、Timer、Observer) 3)一個線程對應一個RunLoop,主線程的RunLoop默認已經啟動,子線程的RunLoop得手動啟動(調用run方法) 4)RunLoop只能選擇一個Mode啟動,如果當前Mode中沒有任何Source(Sources0、Sources1)、Timer,那麽就直接退出RunLoop 2.自動釋放池什麽時候釋放? 通過Observer監聽RunLoop的狀態 3.在開發中如何使用RunLoop?什麽應用場景? 3.1 開啟一個常駐線程(讓一個子線程不進入消亡狀態,等待其他線程發來消息,處理其他事件) 1)在子線程中開啟一個定時器 2)在子線程中進行一些長期監控 3.2 可以控制定時器在特定模式下執行 3.3可以讓某些事件(行為、任務)在特定模式下執行 3.4可以添加Observer監聽RunLoop的狀態,比如監聽點擊事件的處理(在所有點擊事件之前做一些事情)
iOS開發多線程-RunLoop