1. 程式人生 > >iOS開發多線程-RunLoop

iOS開發多線程-RunLoop

相關 ons glob num 以及 com 開源 agen getc

一、什麽是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
  1. [NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
  2. [NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象

2)Core Foundation

[objc] view plain copy
  1. CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
  2. CFRunLoopGetMain(); // 獲得主線程的RunLoop對象

代碼示例:

[objc] view plain copy
  1. #import "ViewController.h"
  2. @interface ViewController ()
  3. @end
  4. @implementation ViewController
  5. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
  6. {
  7. // NSRunLoop 主線程對應的RunLoop對象
  8. NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
  9. NSLog(@"mainRunLoop = %@", mainRunLoop);
  10. // NSRunLoop 獲得當前方法所在線程對應的RunLoop
  11. NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
  12. NSLog(@"currentRunLoop = %@", currentRunLoop);
  13. // CFRunLoopRef 主線程對應的RunLoop對象
  14. CFRunLoopRef cfMainRunLoop = CFRunLoopGetMain();
  15. NSLog(@"cfMainRunLoop = %@", cfMainRunLoop);
  16. // CFRunLoopRef 獲得當前方法所在線程對應的RunLoop
  17. CFRunLoopRef cfCurrentRunLoop = CFRunLoopGetCurrent();
  18. NSLog(@"cfCurrentRunLoop = %@", cfCurrentRunLoop);
  19. // 開啟一條子線程
  20. NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
  21. [thread start];
  22. }
  23. - (void)run
  24. {
  25. // 註意: 如果想給子線程添加RunLoop, 不能直接alloc init
  26. // [[NSRunLoop alloc] init]; // 錯誤
  27. // 只要調用currentRunLoop方法, 系統就會自動創建一個RunLoop, 添加到當前線程中
  28. [NSRunLoop currentRunLoop]; // 這個方法是懶加載
  29. }
  30. @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
  1. #import "ViewController.h"
  2. @interface ViewController ()
  3. @property (nonatomic, strong) dispatch_source_t timer;
  4. @end
  5. @implementation ViewController
  6. - (void)viewDidLoad {
  7. [super viewDidLoad];
  8. }
  9. // 演示函數調用棧:source0(用戶主動觸發)和source1
  10. - (IBAction)btnClick:(id)sender {
  11. NSLog(@"%s", __func__);
  12. }
  13. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
  14. {
  15. // [self timer2];
  16. // 開啟後臺線程執行timer2方法
  17. // [self performSelectorInBackground:@selector(timer2) withObject:nil];
  18. [self gcdTimer];
  19. }
  20. - (void)gcdTimer
  21. {
  22. NSLog(@"%s", __func__);
  23. /*
  24. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  25. NSLog(@"------2.0秒後執行-----");
  26. });
  27. */
  28. // 創建一個定時器
  29. /**
  30. * 參數1: source的類型 DISPATCH_SOURCE_TYPE_TIMER:定時器
  31. * 參數2: 線程等信息
  32. * 參數3: 對第二個參數的描述信息
  33. * 參數4: 傳遞一個隊列, 該隊列對應了將來的回調方法在哪個線程中執行
  34. */
  35. dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
  36. // 對timer這個局部變量添加一個引用,以防止被釋放
  37. self.timer = timer;
  38. // 設置定時器開始的時間和間隔的時間, 以及精準度
  39. // 開始時間
  40. // dispatch_time_t startTime = DISPATCH_TIME_NOW;
  41. dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
  42. // 間隔時間
  43. uint64_t interval = 1.0 * NSEC_PER_SEC;
  44. /**
  45. * 參數1: 需要給哪個定時器設置
  46. * 參數2: 定時器開始的時間 / DISPATCH_TIME_NOW 立即執行
  47. * 參數3: 定時器開始之後的間隔時間
  48. * 參數4: 定時器間隔執行的精準度, 傳入0代表最精準(盡量的讓定時器精準), 傳入一個大於0的值, 代表多少秒的範圍是可以接受的
  49. * 參數4存在的意義: 主要是為了提高程序的性能
  50. * 註意: Dispatch的定時器接收的時間是納秒
  51. */
  52. dispatch_source_set_timer(timer, startTime, interval, 00 * NSEC_PER_SEC);
  53. // 指定定時器的回調方法
  54. dispatch_source_set_event_handler(timer, ^{
  55. NSLog(@"test-----%@", [NSThread currentThread]);
  56. });
  57. // 開啟定時器(恢復)
  58. dispatch_resume(timer);
  59. }
  60. - (void)timer1
  61. {
  62. NSLog(@"-----start-----");
  63. // 創建一個定時器
  64. NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
  65. // 將NSTime添加到RunLoop中,並告訴系統,當前Timer只有在RunLoop的默認模式下才有效
  66. // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  67. // // 將NSTime添加到RunLoop中,並告訴系統,當前Timer只有在RunLoop的默認模式下才有效
  68. // [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
  69. // 將NSTimer添加到RunLoop中, 並且告訴系統, 在所有被"標記"common的模式都可以運行
  70. /*
  71. common modes = <CFBasicHash 0x7fc0b8615250 [0x104be7180]>{type = mutable set, count = 2,
  72. entries =>
  73. 0 : <CFString 0x1058bae50 [0x104be7180]>{contents = "UITrackingRunLoopMode"}
  74. 2 : <CFString 0x104bc3080 [0x104be7180]>{contents = "kCFRunLoopDefaultMode"}
  75. }
  76. UITrackingRunLoopMode和kCFRunLoopDefaultMode都被標記為了common模式, 所以只需要將timer的模式設置為forMode:NSRunLoopCommonModes, 就可以在默認模式和追蹤模式都能夠運行
  77. */
  78. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  79. }
  80. - (void)timer2
  81. {
  82. // 註意: 如果是通過scheduledTimerWithTimeInterval創建的NSTimer, 默認就會添加到RunLoop得DefaultMode中 , 所以它會自動運行
  83. NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
  84. // 雖然默認已經添加到DefaultMode中, 但是我們也可以自己修改它的模式
  85. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  86. // 開啟子線程對應的RunLoop
  87. /**
  88. * 註意:
  89. * 1)如果是子線程,那麽需要手動創建子線程對應的RunLoop
  90. * 2)子線程對應的RunLoop還需要手動開啟
  91. */
  92. [[NSRunLoop currentRunLoop] run];
  93. }
  94. - (void)show
  95. {
  96. NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
  97. }
  98. @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
  1. // 創建observer
  2. CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
  3. NSLog(@"----監聽到RunLoop狀態發生改變---%zd", activity);
  4. });
  5. // 添加觀察者:監聽RunLoop的狀態
  6. CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
  7. // 釋放Observer
  8. CFRelease(observer);

代碼示例:

[objc] view plain copy
  1. #import "ViewController.h"
  2. @interface ViewController ()
  3. @end
  4. @implementation ViewController
  5. // 當手指觸摸控制器View的時候,調用該方法
  6. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
  7. {
  8. // 創建Observer
  9. /**
  10. * 參數1: 指定如果給Observer分配存儲空間
  11. * 參數2: 需要監聽的狀態類
  12. * kCFRunLoopEntry = (1UL << 0), 即將啟動(進入)的時候
  13. * kCFRunLoopBeforeTimers = (1UL << 1), 即將處理timer事件
  14. * kCFRunLoopBeforeSources = (1UL << 2), 即將處理source事件
  15. * kCFRunLoopBeforeWaiting = (1UL << 5), 即將進入睡眠
  16. * kCFRunLoopAfterWaiting = (1UL << 6), RunLoop被喚醒
  17. * kCFRunLoopExit = (1UL << 7), RunLoop退出
  18. * kCFRunLoopAllActivities = 0x0FFFFFFFU 監聽所有狀態
  19. *
  20. */
  21. CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
  22. switch (activity) {
  23. case kCFRunLoopEntry:
  24. NSLog(@"即將進入RunLoop");
  25. break;
  26. case kCFRunLoopBeforeTimers:
  27. NSLog(@"即將處理timer");
  28. break;
  29. case kCFRunLoopBeforeSources:
  30. NSLog(@"即將處理source");
  31. break;
  32. case kCFRunLoopBeforeWaiting:
  33. NSLog(@"即將進入睡眠");
  34. break;
  35. case kCFRunLoopAfterWaiting:
  36. NSLog(@"RunLoop剛從睡眠中喚醒");
  37. break;
  38. case kCFRunLoopExit:
  39. NSLog(@"RunLoop即將退出");
  40. break;
  41. default:
  42. break;
  43. }
  44. });
  45. // 給主線程的RunLoop添加一個觀察者,要監聽的是RunLoop的哪種運行模式
  46. /**
  47. * 參數1: 需要給哪個RunLoop添加觀察者
  48. * 參數2: 需要添加的Observer對象
  49. * 參數3: 在哪種模式下可以監聽 kCFRunLoopDefaultMode == NSDefaultRunLoopMode
  50. */
  51. CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
  52. // 釋放對象
  53. CFRelease(observer);
  54. [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
  55. }
  56. - (void)show{
  57. NSLog(@"%s", __func__);
  58. }
  59. @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
  1. #import "ViewController.h"
  2. @interface ViewController ()
  3. @property (weak, nonatomic) IBOutlet UIImageView *imageView;
  4. /** 引用線程 */
  5. @property (nonatomic, strong) NSThread *thread;
  6. @end
  7. @implementation ViewController
  8. - (void)viewDidLoad {
  9. [super viewDidLoad];
  10. //RunLoop的自動釋放池子
  11. /*
  12. 自動釋放池什麽時候創建和釋放
  13. 1.第一次創建, 是在RunLoop進入的時候創建 對應的狀態 = kCFRunLoopEntry
  14. 2.最後一次釋放, 是在RunLoop退出的時候 對應的狀態 = kCFRunLoopExit
  15. 3.其它創建和釋放
  16. * 每次睡覺的時候都會釋放前自動釋放池, 然後再創建一個新的
  17. _wrapRunLoopWithAutoreleasePoolHandler activities = 0x1,
  18. 1 = kCFRunLoopEntry 進入RunLoop 創建自動釋放池
  19. _wrapRunLoopWithAutoreleasePoolHandler activities = 0xa0,
  20. 160 = kCFRunLoopBeforeWaiting 即將進入睡眠 ,先釋放上一次創建的自動釋放池, 然後再創建一個新的釋放池
  21. +
  22. kCFRunLoopExit 即將退出RunLoop 釋放自動釋放池
  23. */
  24. NSLog(@"%s", __func__);
  25. // 添加到RunLoop的默認運行模式下inModes:@[NSDefaultRunLoopMode,UITrackingRunLoopMode]
  26. // [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20151128_3"] afterDelay:3.0];
  27. // 指定運行模式進行特定的操作
  28. [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20151128_3"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode,UITrackingRunLoopMode]];
  29. }
  30. // 執行任務1的按鈕(創建線程執行任務)
  31. - (IBAction)oneBtnClick {
  32. // 創建線程並且執行任務1
  33. self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(operation1) object:nil];
  34. [self.thread start];
  35. // 註意:默認情況下一個線程只能使用一次, 也就是說只能執行一個操作, 執行完畢之後就不能使用了
  36. }
  37. // 執行任務2的按鈕(指定按鈕1創建的線程來執行)
  38. - (IBAction)twoBtnClick {
  39. // 指定子線程執行任務2
  40. [self performSelector:@selector(operation2) onThread:self.thread withObject:nil waitUntilDone:YES];
  41. }
  42. // 任務1
  43. - (void)operation1
  44. {
  45. NSLog(@"---start---");
  46. // 保證子線程不被銷毀
  47. // 采用死循環的方式是行不通的
  48. // while (1) {
  49. // NSLog(@"%s---%@", __func__, [NSThread currentThread]);
  50. // }
  51. // 子線程的NSRunLoop 需要手動創建
  52. // 子線程的NSRunLoop 需要手動開啟
  53. // 如果子線程的NSRunLoop沒有設置source or timer,那麽子線程的NSRunLoop會立即關閉
  54. NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
  55. // [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
  56. NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
  57. [runLoop addTimer:timer forMode:NSRunLoopCommonModes];
  58. // 啟動RunLoop
  59. [runLoop run];
  60. // 註意點: NSRunLoop只會檢查有沒有source和timer, 沒有就關閉, 不會檢查observer
  61. // CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
  62. //
  63. // });
  64. //
  65. // CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
  66. //
  67. // // 釋放對象
  68. // CFRelease(observer);
  69. // 該代碼永遠不會被執行
  70. NSLog(@"---end---");
  71. }
  72. // 任務2
  73. - (void)operation2
  74. {
  75. NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
  76. }
  77. - (void)test
  78. {
  79. NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
  80. }
  81. @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