1. 程式人生 > >淺談iOS中的RunLoop

淺談iOS中的RunLoop

一段 ping itl rst 使用 http ram art 分享

首先解釋下為什麽是淺談,主要是RunLoop這個東西不單單是iOS的範疇,還涉及到操作系統,我指的淺談僅僅針對ios上層應用,底層的東西概不涉及 ,所以只能淺談淺談了。

在淺談RunLoop之前我們來寫個小demo,超級簡單,一個按鈕,然後給按鈕一個斷點

技術分享

這塊標記了1,2,3,4 紅色的字;其實這是這個APP啟動的一個過程


但是說好了淺談RunLoop為啥又扯到APP的啟動了 ? 先不要在意這些細節。。。 我先來解釋下我標出的1,2,3,4分別是啥東西

1,dyld :這是啥子鬼了? the dynamic link editor (動態鏈接編輯器)

我們驚奇的發現一個APP啟動的時候是先幹這個事,我們把xcode稍作配置

技術分享 第一步

然後

技術分享


我把這個鬼復制下來 dyld_PRINT_STATISTICS 打上對勾,後面的參數設為1

然後打開另外一個開關

技術分享

一切準備好之後,我們啟動我們的APP,註意在真機上啟動。。。。 然後我們看下控制臺打印的東西


技術分享


技術分享

技術分享

技術分享

看了這些日誌輸出我們對dyld有那麽一點初級的概念了,原來這貨是加載項目所需要的開發包,第一步算介紹到這裏,然後我們看第二步


2,main() :整個APP的入口,將AppDelegate扯進來

技術分享


3,RunLoop

我們不難發現main函數執行之後並不是直接執行我們的以UI開頭的事件或方法,而是先搞一個RunLoop ,也就是RunLoop在用戶界面呈現之前就啟動了。。。。。 ,我們先繼續,我們說下第4條


4,用戶層操作

我們可以看到 UIApplication UIWindow UIControl 等我們熟悉的代碼,但是不幸的是他們卻在RunLoop之後運行。。。。 那麽RunLoop到底是個啥子。。。。





RunLoop: 從字面的意思上來看 運行循環,在我大iOS的範疇中又是個啥意思了,意如其字 ; 如果你還看不懂,那我只能點撥下了一師是個”女子“學校 ,換句話說就是 一個APP啟動的時候 相當於啟動了一個死循環,而這個死循環就是RunLoop,我們在應用裏面寫的所有代碼都在RunLoop裏面運行; 如果你搞過單片機就很好懂啦,代碼直接來個while(1){ } ,RunLoop其實就是這麽回事,解釋一下為什麽我們的代碼是在一個死循環裏面運行

1.我手機啟動一個APP,不讓手機黑屏,過一段時間(幾小時或幾天)我再點擊了,這個APP肯定響應我的動作

2.我再APP裏面寫一個圖片輪播,不管過多久他一直在輪播,誰在支撐他?

3.給個島國動作片的地址,我們手機幹別的事情了,卻能自動的下載下來。。。

我們的APP時刻保持用戶響應其實就是開啟了一個主RunLoop , 這個主RunLoop 是負責這個APP活動的心臟

技術分享

這就是RunLoop ,我們來總結下RunLoop的功能

功能一:用來處理耗時長的異步事件

mainRunLoop];

  • NSLog(@"int main %@",main.currentMode);
  • NSRunLoop *current = [NSRunLoop currentRunLoop];
  • NSLog(@"int main %@",current.currentMode);
  • CFRunLoopRef runloop = CFRunLoopGetCurrent();
  • NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runloop));
  • NSLog(@"int main %@ ",allModes);
  • CFRunLoopRef runloopm = CFRunLoopGetMain();
  • NSArray *allModesm = CFBridgingRelease(CFRunLoopCopyAllModes(runloopm));
  • NSLog(@"int main %@ ",allModesm);


技術分享

我們來看下輸出的結果。。

技術分享


我們來總結下,總結之前我們把5種模式輸出出來

  1. UITrackingRunLoopMode,
  2. GSEventReceiveRunLoopMode,
  3. kCFRunLoopDefaultMode,///默認模式
  4. UIInitializationRunLoopMode,
  5. kCFRunLoopCommonModes

技術分享

我們驚奇的發現,我們一般頁面用到兩種默認,Init模式和Default模式;也就是頁面初始化的是時候是init模式執行完ViewDidLoad方法之後RunLoop變為了Default模式了 ,註意這裏有兩個RunLoop, 一個是MainRunLoop 和CurrentRunLoop,MainRunLoop無可厚非就是整個項目的主RunLoop,Current就是當前的,因為當前只有一個線程,所以MainRunLoop 和CurrentRunLoop是一個RunLoop;為啥要說Mode了 ,我們來看一個例子。

技術分享

我啟動一個定時器,每秒中自增1, 看標題;每當我手放在ScrollView上了,定時器就不工作了。。

我的源碼如下

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. // Do any additional setup after loading the view.
  4. self.view.backgroundColor = [UIColor whiteColor];
  5. startcount = 0;
  6. UIButton *button1 = [[UIButton alloc ] init];
  7. button1.frame = CGRectMake(10, 140, 100, 50);
  8. [button1 setTitle:@"啟動定時器" forState:UIControlStateNormal];
  9. [button1 addTarget:self action:@selector(button1) forControlEvents:UIControlEventTouchUpInside];
  10. button1.backgroundColor = [UIColor lightGrayColor];
  11. [self.view addSubview:button1];
  12. button1 = [[UIButton alloc ] init];
  13. button1.frame = CGRectMake(150, 140, 100, 50);
  14. [button1 setTitle:@"暫停定時器" forState:UIControlStateNormal];
  15. [button1 addTarget:self action:@selector(button2) forControlEvents:UIControlEventTouchUpInside];
  16. button1.backgroundColor = [UIColor lightGrayColor];
  17. [self.view addSubview:button1];
  18. UIScrollView *scroll = [[UIScrollView alloc] init];
  19. scroll.frame = CGRectMake(0, 200, 320, 300);
  20. scroll.backgroundColor = [UIColor redColor];
  21. scroll.contentSize = CGSizeMake(320, 900);
  22. scroll.delegate = self;
  23. [self.view addSubview:scroll];
  24. timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(function:) userInfo:nil repeats:YES];
  25. [timer setFireDate:[NSDate distantFuture]];
  26. }
  27. -(void)function:(id)sender{
  28. self.title = [NSString stringWithFormat:@"%d",startcount++];
  29. [self prinitMode:@"Timer"];
  30. }
  31. -(void)button1{
  32. [timer setFireDate:[NSDate distantPast]];
  33. }
  34. -(void)button2{
  35. [timer setFireDate:[NSDate distantFuture]];
  36. }
  37. -(void)scrollViewDidScroll:(UIScrollView *)scrollView{
  38. [self prinitMode:@"scrollViewDidScroll"];
  39. }
  40. -(void)prinitMode:(NSString *)modeName{
  41. NSRunLoop *main = [NSRunLoop mainRunLoop];
  42. NSLog(@"mainRunLoop %@ %@",modeName,main.currentMode);
  43. CFRunLoopRef runloopm = CFRunLoopGetMain();
  44. NSArray *allModesm = CFBridgingRelease(CFRunLoopCopyAllModes(runloopm));
  45. NSLog(@"mainRunLoop %@ ALL %@ ",modeName,allModesm);
  46. NSRunLoop *current = [NSRunLoop currentRunLoop];
  47. NSLog(@"currentMode %@ %@",modeName,current.currentMode);
  48. CFRunLoopRef runloop = CFRunLoopGetCurrent();
  49. NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runloop));
  50. NSLog(@"current %@ ALL %@ ",modeName,allModes);
  51. }


然後看下我的日誌文件

技術分享

我們發現手放下UIScrollView的時候RunLoop的模式變成了 UITrackingRunLoopMode ,定時器不起作用; 手離開時,模式變成KCFRunLoopDefaultMode ,定時器繼續工作。那麽這個就比較麻煩了,滑動的時候定時器不起作用,現實項目中這個肯定不行的。。 如何解決了 ? 剛剛我們說到了AFNetworking的源碼,我們可以模仿下源碼

  1. timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(function:) userInfo:nil repeats:YES];
  2. [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  3. [timer setFireDate:[NSDate distantFuture]];

我在timer上面加了這段代碼,驚奇的發現,現在我滑動UIScrollView,Timer並沒有收到影響;我看來下日誌,

開啟定時的時候: mainRunLoop Timer kCFRunLoopDefaultMode

滑動UIScrollView的時候: mainRunLoop scrollViewDidScroll UITrackingRunLoopMode

再次松開UIScrollView的時候:mainMode Timer UITrackingRunLoopMode

UITrackingRunLoopMode:用戶滑動Mode

GSEventReceiveRunLoopMode:用於系統接收事件Mode

kCFRunLoopDefaultMode:RunLoop默認Mode

UIInitializationRunLoopMode:初始化Mode

kCFRunLoopCommonModes:通用Mode


這就解釋了為什麽AFNetworking中使用RunLoop來開啟一個新的timer,上面提到一個項目中的主RunLoop只有一個(有點類似於主線程和子線程,主線程只有一個,子線程有很多個),每次新建一個Timer我們需要開啟一個子的RunLoop 然後加入到主RunLoop中

總結下RunLoop的一些特點

1.主線程的RunLoop在應用啟動的時候就會自動創建

2.其他線程則需要在該線程下自己啟動

3.不能自己創建RunLoop

4.RunLoop並不是線程安全的,所以需要避免在其他線程上調用當前線程的RunLoop

5.RunLoop負責管理autorelease pools

6.RunLoop負責處理消息事件,即輸入源事件和計時器事件

淺談iOS中的RunLoop