<output id="brrn1"><ruby id="brrn1"></ruby></output>
<sub id="brrn1"></sub>
    <sub id="brrn1"></sub>

      <sub id="brrn1"><ruby id="brrn1"><noframes id="brrn1"><big id="brrn1"><del id="brrn1"></del></big>

      <th id="brrn1"><ruby id="brrn1"></ruby></th>

          <delect id="brrn1"><meter id="brrn1"></meter></delect>
        
        

          <output id="brrn1"></output>

            開發

            淺析RunLoop原理及其應用

            廣告
            廣告

            微信掃一掃,分享到朋友圈

            淺析RunLoop原理及其應用
            0 0

            引言:一個APP的啟動與結束都是伴隨著RunLoop循環往復的,不斷的循環、不斷的往復。當線程被殺掉、APP退出后被系統以占用內存為由殺掉,RunLoop就消失了。但平時開發中很少見到RunLoop,為何它如此神秘?本文跟大家分享一下RunLoop的相關知識。  

            轉載本文需注明出處:微信公眾號EAWorld,違者必究。

            目錄:

            1、RunLoop的概念  

            2、RunLoop與線程的關系  

            3、RunLoop的常用模式

            4、RunLoop的應用??

            1.RunLoop的概念??

            將英文拆解不難理解其實RunLoop表示一直在運行著的循環或者從上面的定義源碼中可以看出就是一個do..while..循環。當啟動一個iOS APP時主線程啟動與其對應的RunLoop也已經開啟。如果不殺掉APP則APP一直運行,就是因為RunLoop循環一直為開啟狀態保證主線程不會被摧毀。這也是RunLoop的作用之一保證線程不退出。RunLoop在循環過程中監聽事件,當前線程有任務時,喚醒當當線程去執行任務,任務執行完成以后,使當前線程進入休眠狀態。當然這里的休眠不同于我們自己寫的死循環(while(1);),它在休眠時幾乎不會占用系統資源,當然這是由操作系統內核去負責實現的。

            UIApplicationMain()函數方法會默認為主線程設置一個NSRunLoop對象,這個循環會隨時監聽屏幕上由用戶觸摸所帶來的底層消息并將其傳遞給主線程去處理,當點擊一個button事件的傳遞從圖上的調用棧可以看出。(監聽的范圍還包含時鐘/網絡)RunLoop循環與While循環的區別在于,RunLoop會在沒有事件發生時進入休眠狀態從而不占用CPU消耗,有事件發生才會去找對應的 Handler 處理事件,而While則會一直占用。在 Cocoa 程序的線程中都可以通過代碼NSRunLoop *runloop = [NSRunLoop currentRunLoop];來獲取到當前線程的Runloop對象。

            RunLoop共有兩套API接口 :1. Foundation框架NSRunLoop 2. Core Foundation框架CFRunLoopRef。NSRunLoop和CFRunLoopRef都代表著RunLoop對象,它們是等價的,可以互相轉換。

            NSRunLoop是基于CFRunLoopRef的一層OC包裝,所以要了解RunLoop內部結構,需要多研究CFRunLoopRef層面的API(Core Foundation層面)。

            2.RunLoop與線程之間的關系

            RunLoop和線程是相輔相成的,一個Runloop對應著一條唯一的線程,可以這樣說RunLoop是為了線程而生,沒有線程,它也沒有存在的必要。RunLoop是線程的基礎架構部分, Cocoa 和 CoreFundation 都提供了RunLoop對象方便配置和管理線程的 RunLoop。每個線程,包括程序的主線程( main thread )都有與之相對應的 RunLoop對象。上圖從 input source 和 timer source 接受事件,然后在線程中處理事件都是由RunLoop推動完成。

            注意:開一個子線程創建runloop,不是通過alloc init方法創建,而是直接通過調用currentRunLoop方法來創建,它本身是一個懶加載的。在子線程中,如果不主動獲取Runloop的話,那么子線程內部是不會創建Runloop的。

            3.RunLoop的常用模式

            RunLoop 的模式有五種。圖上列出了其中兩種分別是 NSDefaultRunLoopMode(默認模式) 和 UITRackingRunLoopMode(UI模式) 、NSRunLoopCommonModes(占位模式)。其實占位模式不是一個真正的模式,它相當于上面兩種模式之和。蘋果公開提供的 Mode 有兩個NSDefaultRunLoopMode(kCFRunLoopDefaultMode) NSRunLoopCommonModes(kCFRunLoopCommonModes)。

            4.RunLoop的應用

            例如創建一個比較常見的注冊頁面,里面用NSTimer來自處理常見的驗證碼倒計時,每秒處理一下,如果NSTimer添加到的是默認模式的RunLoop這時候注冊頁面有一個展示注冊協議的UITextView當用戶滑動UITextView時驗證碼的倒計時是停止的,這是因為主線程的RunLoop模式是UI模式這個時候RunLoop循環是優先處理UI模式的任務而忽略了默認模式的計時器。此時解決上面的問題就需要用到NSRunLoopCommonModes(占位模式),這個模式相當于把NSTimer在兩種模式下都添加了,這就不難理解為什么NSRunLoopCommonModes是一個復數形式了。這個模式下滑動UITextView或停止的時候RunLoop是在UITRacking和default模式下切換的(從打印日志中可以看出)。如果覺得NSTimer設置RunLoop模式很復雜可以嘗試用GCD的Timer用法很簡便。

            RunLoop在TableView中的應用(解決滑動卡頓問題)。

            如圖代碼展示,當加載高清大圖渲染屏幕,而此時不得不在主線程操作,會引起滑動的卡頓。

            tableview 在加載 cell 時如果遇到多個耗時操作會有點卡頓。將耗時操作放到 DefaultMode 里只能解決滑動時流暢,但是停止時需要加載耗時,仍然會有卡頓的感覺。正確方法是采用 RunLoop 監聽,將多個耗時操作分開執行,在每次 RunLoop 喚醒時去做一個耗時任務。

            阻塞原因:kCFRunLoopDefaultMode時候 多張圖片(特別是高清大圖)一起加載(耗時)loop不結束無法BeforeWaiting(即將進入休眠) 切換至UITrackingRunLoopMode來處理等候的UI刷新事件造成阻塞。

            解決辦法:每次RunLoop循環只加載一張圖片 這樣loop就會很快進入到BeforeWaiting處理后面的UI刷新(UITrackingRunLoopMode 優先處理)或者沒有UI刷新事件繼續處理下一張圖片。

            RunLoop 監聽添加Observer (監聽RunLoop的beforeWaiting)當處理完一張圖片即將進入到beforeWaiting時處理數組里的tasks,這些任務就在callback里面做處理。

            callBack拿到task處理了一部分就進入到了休眠 比如拿到18個任務只處理了7個就不處理了。

            此處添加Timer是讓RunLoop一直處于活躍狀態 保證即使處理完所有task還是一直活躍狀態。

            注意:當CFRunLoopAddObserver(runloop, observer , kCFRunLoopDefaultMode); 添加到觀察者時模式為kCFRunLoopDefaultMode 這樣的的話只能監聽到一般模式的BeforeWaiting,即不滑動的時候。所以圖上的加載只在拖動結束時,而拖動UI時無任何加載。如下圖:

            所以這里可以再次優化,將模式改為kCFRunLoopCommonModes,這樣的話滑動或者不滑動都可以加載圖片渲染屏幕,而且是在不影響屏幕流暢性的基礎上。如以下GIF:

            源碼:

            #import "ViewController.h"
             
            @interface ViewController ()<UITableViewDelegate, UITableViewDataSource>
            @property (weak, nonatomic) IBOutlet UITableView *tableView;
             
            @property (nonatomic, strong) NSTimer *timer;
            @property (nonatomic, strong) NSMutableArray *tasks;
            @property (nonatomic, assign) NSInteger maxTaskNumber;
             
            @end
             
            void callBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
                //C語言與OC的交換用到橋接 __bridge
                //處理控制器加載圖片的事情
                ViewController *VC = (__bridge ViewController *)(info);
                if (VC.tasks.count == 0) {
                    return;
                }
                void(^task)() = [VC.tasks firstObject];
                task();
                [VC.tasks removeObject:task];
                NSLog(@"COUNT:%ld",VC.tasks.count);
                 
            }
             
            @implementation ViewController
             
            - (void)viewDidLoad {
                [super viewDidLoad];
                 
                 
                [self addRunloopOvserver];
                 
                self.maxTaskNumber = 18;
                self.tasks = [NSMutableArray array];
                 
                 
                [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
                 
            }
            -(void)timerMethod{
                 
            }
            -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
                ViewController2 *vc2 = [ViewController2 new];
                [self presentViewController:vc2 animated:YES completion:^{
                     
                }];
            }
             
            - (void)addRunloopOvserver{
                //獲取當前的RunLoop
                CFRunLoopRef runloop = CFRunLoopGetCurrent();
                //上下文 (此處為C語言 對OC的操作需要上下文)將(__bridge void *)self 傳入到Callback
                CFRunLoopObserverContext context = {0, (__bridge void *)self, &CFRetain, &CFRelease};
                //創建觀察者 監聽BeforeWaiting 監聽到就調用回調callBack
                CFRunLoopObserverRef observer = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &callBack, &context);
                //添加觀察者到當前runloop kCFRunLoopDefaultMode可以改為kCFRunLoopCommonModes
                CFRunLoopAddObserver(runloop, observer , kCFRunLoopCommonModes);
                //C語言中 有create就需要release
                CFRelease(observer);
            }
             
             
            - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
                return 30000;
            }
             
            - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
                UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"identity" forIndexPath:indexPath];
                NSLog(@"---run---%@",[NSRunLoop currentRunLoop].currentMode);
                //以下兩個循環的UI操作在必須放在主線程,但是弊端就是太多圖片的處理會阻塞tableview的滑動流暢性
                for (int i = 1; i < 4; i++) {
                    UIImageView *imageView = [cell.contentView viewWithTag:i];
                    [imageView removeFromSuperview];
                }
                for (int i = 1; i < 4; i++) {
                /*
                 阻塞模式
                */
                //        CGFloat leading = 10, space = 20, width = 103, height = 87, top = 15;
                //        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake((i - 1) * (width + space) + leading, top, width, height)];
                //        [cell.contentView addSubview:imageView];
                //        imageView.tag = i;
                //        imageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image" ofType:@"png"]];
             
                     
                 
                //阻塞原因:kCFRunLoopDefaultMode時候 多張圖片一起加載(耗時)loop不結束無法BeforeWaiting(即將進入休眠) 切換至UITrackingRunLoopMode來處理等候的UI刷新事件造成阻塞
                //解決辦法:每次RunLoop循環只加載一張圖片 這樣loop就會很快進入到BeforeWaiting處理后面的UI刷新(UITrackingRunLoopMode 優先處理)或者沒有UI刷新事件繼續處理下一張圖片
                 
                    /*
                     流暢模式
                    */
                    //下面只是把任務放到數組 不消耗性能
                    void(^task)() = ^{
                        CGFloat leading = 10, space = 20, width = 103, height = 87, top = 15;
                        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake((i - 1) * (width + space) + leading, top, width, height)];
                        [cell.contentView addSubview:imageView];
                        imageView.tag = i;
                        imageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image" ofType:@"png"]];
                    };
                    [self.tasks addObject:task];
                    //保證只拿最新的18個任務處理
                    if (self.tasks.count > self.maxTaskNumber) {
                        [self.tasks removeObjectAtIndex:0];
                    }
                }
                return cell;
            }
             
             
             
            - (void)didReceiveMemoryWarning {
                [super didReceiveMemoryWarning];
            }
            

            關于作者:熱河,普元移動端開發工程師,互聯網技術愛好者,專注于iOS開發。目前參與Mobile 8.0項目的開發,主要接觸RN技術的應用,黏合前端代碼與iOS底層之間的交互。

            關于EAWorld:微服務,DevOps,數據治理,移動架構原創技術分享。

            我還沒有學會寫個人說明!

            如何進行I/O評估、監控、定位和優化?

            上一篇

            價值100億美元!微軟剛剛擊敗亞馬遜,拿下美國國防部十年云計算基建訂單

            下一篇

            你也可能喜歡

            淺析RunLoop原理及其應用

            長按儲存圖像,分享給朋友

            ITPUB 每周精要將以郵件的形式發放至您的郵箱


            微信掃一掃

            微信掃一掃
            亚洲黄色片视频,光棍电影韩国伦理网,女神吧,伊人电影在线观看