淺談iOS中的RunLoop
首先解釋下為什麽是淺談,主要是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種模式輸出出來
- UITrackingRunLoopMode,
- GSEventReceiveRunLoopMode,
- kCFRunLoopDefaultMode,///默認模式
- UIInitializationRunLoopMode,
- kCFRunLoopCommonModes
我們驚奇的發現,我們一般頁面用到兩種默認,Init模式和Default模式;也就是頁面初始化的是時候是init模式執行完ViewDidLoad方法之後RunLoop變為了Default模式了 ,註意這裏有兩個RunLoop, 一個是MainRunLoop 和CurrentRunLoop,MainRunLoop無可厚非就是整個項目的主RunLoop,Current就是當前的,因為當前只有一個線程,所以MainRunLoop 和CurrentRunLoop是一個RunLoop;為啥要說Mode了 ,我們來看一個例子。
我啟動一個定時器,每秒中自增1, 看標題;每當我手放在ScrollView上了,定時器就不工作了。。
我的源碼如下
- - (void)viewDidLoad {
- [super viewDidLoad];
- // Do any additional setup after loading the view.
- self.view.backgroundColor = [UIColor whiteColor];
- startcount = 0;
- UIButton *button1 = [[UIButton alloc ] init];
- button1.frame = CGRectMake(10, 140, 100, 50);
- [button1 setTitle:@"啟動定時器" forState:UIControlStateNormal];
- [button1 addTarget:self action:@selector(button1) forControlEvents:UIControlEventTouchUpInside];
- button1.backgroundColor = [UIColor lightGrayColor];
- [self.view addSubview:button1];
- button1 = [[UIButton alloc ] init];
- button1.frame = CGRectMake(150, 140, 100, 50);
- [button1 setTitle:@"暫停定時器" forState:UIControlStateNormal];
- [button1 addTarget:self action:@selector(button2) forControlEvents:UIControlEventTouchUpInside];
- button1.backgroundColor = [UIColor lightGrayColor];
- [self.view addSubview:button1];
- UIScrollView *scroll = [[UIScrollView alloc] init];
- scroll.frame = CGRectMake(0, 200, 320, 300);
- scroll.backgroundColor = [UIColor redColor];
- scroll.contentSize = CGSizeMake(320, 900);
- scroll.delegate = self;
- [self.view addSubview:scroll];
- timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(function:) userInfo:nil repeats:YES];
- [timer setFireDate:[NSDate distantFuture]];
- }
- -(void)function:(id)sender{
- self.title = [NSString stringWithFormat:@"%d",startcount++];
- [self prinitMode:@"Timer"];
- }
- -(void)button1{
- [timer setFireDate:[NSDate distantPast]];
- }
- -(void)button2{
- [timer setFireDate:[NSDate distantFuture]];
- }
- -(void)scrollViewDidScroll:(UIScrollView *)scrollView{
- [self prinitMode:@"scrollViewDidScroll"];
- }
- -(void)prinitMode:(NSString *)modeName{
- NSRunLoop *main = [NSRunLoop mainRunLoop];
- NSLog(@"mainRunLoop %@ %@",modeName,main.currentMode);
- CFRunLoopRef runloopm = CFRunLoopGetMain();
- NSArray *allModesm = CFBridgingRelease(CFRunLoopCopyAllModes(runloopm));
- NSLog(@"mainRunLoop %@ ALL %@ ",modeName,allModesm);
- NSRunLoop *current = [NSRunLoop currentRunLoop];
- NSLog(@"currentMode %@ %@",modeName,current.currentMode);
- CFRunLoopRef runloop = CFRunLoopGetCurrent();
- NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runloop));
- NSLog(@"current %@ ALL %@ ",modeName,allModes);
- }
然後看下我的日誌文件
我們發現手放下UIScrollView的時候RunLoop的模式變成了 UITrackingRunLoopMode ,定時器不起作用; 手離開時,模式變成KCFRunLoopDefaultMode ,定時器繼續工作。那麽這個就比較麻煩了,滑動的時候定時器不起作用,現實項目中這個肯定不行的。。 如何解決了 ? 剛剛我們說到了AFNetworking的源碼,我們可以模仿下源碼
- timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(function:) userInfo:nil repeats:YES];
- [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- [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