Runloop 多執行緒

小樓一夜聽風來發表於2018-05-15
Runloop 的使命:
1 負責程式不退出
2 負責監聽IOS所有的事件,如:觸控,時鐘,網路事件

3 如果沒有事件傳送,會讓程式進入休眠狀態

Runloop 的兩種模式:NSDefaultRunLoopMode+NSRunLoopCommonModes
NSDefaultRunLoopMode:時鐘+網路事件

NSRunLoopCommonModes:使用者互動

來看看這段程式碼~思考下:以下程式碼的輸出是什麼?

int main(int argc, char * argv[]) {

    @autoreleasepool {   

    NSLog(@"來了");

     nil 就相當於UIApplication

     int a = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

    NSLog(@"come here");

     return a;

    }

}

想好了嗎?揭曉答案了哦~答案是“來了”,你猜對了嗎?why?因為UIApplicationMain 有個死迴圈(runloop迴圈)

死迴圈的目的何在?

- 保證去程式不退出

- 負責監聽事件,觸控,時鐘,網路事件

- 如果沒有事件發生,會讓程式進入休眠狀態(也會消耗cpu的效能)

Runloop 的五種模式

1.UITrackingRunLoopMode UI模式

2.NSDefaultRunLoopMode 預設模式

3.NSRunLoopCommonModes 佔位模式相當於 UI模式+ 預設模式

4.UIInitializationRunLoopMode:在剛啟動App時進入的第一個Mode,啟動完成後不再使用

5.GSEventReceiveRunLoopMode接受系統事件的內部Mode,通常用不到

有這樣一個場景 :介面上有個textview,當滾動textview,viewdidload 裡的timer就不工作了

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeMethod) userInfo:nil repeats:YES];

 [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];

我們來解釋下原因,請看下圖


此時的timer在預設模式下,蘋果的一貫注重使用者體驗,所以當滾動textview (使用者操作UI)的時候,runloop 會優先處理UI模式下的source。如果這時吧計時器加入到UI模式下,timeMethod 會正常進行,但當使用者停止操作UI,那麼UI模式下的runloop 就會進行休眠,所以timeMethod也會停止執行。此時我們可以吧timer加入到UI模式+預設模式。這時蘋果為了優化,產生了一個NSRunLoopCommonModes 佔位模式,但是它並不屬於Runloop的五大模式之一。


RunLoop && 多執行緒

- (void)viewDidLoad {

    [super viewDidLoad];

    NSThread *thread = [[NSThread alloc]initWithBlock:^{

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeMethod) userInfo:nil repeats:YES];

        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];

        NSLog(@"來了1");

    }];

    [thread start];

}

- (void)timeMethod {

    NSLog(@"來了2");

    [NSThread sleepForTimeInterval:1];

    NSLog(@"%@",[NSThread currentThread]);

}

2018-05-16 10:05:46.915347+0800 測試完[841:26856] 來了1


原因:執行緒走了,想要保證執行緒的命,就要讓執行緒有執行不完的任務,執行緒就不會釋放了~

- (void)viewDidLoad {

    [super viewDidLoad];

    NSThread *thread = [[NSThread alloc]initWithBlock:^{

 NSTime*timer=                    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeMethod) userInfo:nilrepeats:YES];

        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];

       //這個迴圈只能保證執行緒的命,但是timeMethod 還是不能呼叫

       // while(true)

       //{

             //從事件佇列中取出事件來處理!!(但是平沒有開源這個,但是封裝了一個NSRunLoop 來處理這個事件

       // }

       //RunLoop -- 一條執行緒上面的RunLoop模式是不迴圈的

        //CFRunLoop -- currentRunLoop ()  第一次獲取RunLoop的時候,建立RunLoop

        [[NSRunLoop currentRunLoop]run];//死迴圈

       NSLog(@"來了1");

    }];

    [thread start];

}

- (void)timeMethod {

    NSLog(@"來了2");

    [NSThread sleepForTimeInterval:1];

    NSLog(@"%@",[NSThread currentThread]);

}

列印結果如下:

2018-05-11 10:06:44.594863+0800 RunloopTest[1300:42091] 來了2

2018-05-11 10:06:45.599998+0800 RunloopTest[1300:42091] <NSThread: 0x60400047df80>{number = 4, name = (null)}

主執行緒與子執行緒做的事情都是一樣的,唯一的區別是:UI介面在主線上面的;App啟動的第一條執行緒

蘋果為啥要吧UI介面放在主執行緒?為了安全+效率

三,CFRunloop 的一個用法


#import "ViewController.h"


typedef void(^RunloopBlock)(void);


@interface ViewController ()

@property(strong,nonatomic)NSMutableArray *tasks;

@end


@implementation ViewController

//這裡在cell 上載入圖片的耗時操作沒寫

/*分析卡頓的原因:

所有的Cell的載入都在主執行緒的一次Runloop迴圈中,UI渲染也屬於Runloop的事情,但是一次渲染18張圖片,渲染太多。導致卡頓

解決思路:一次Runloop迴圈,只載入一張圖片

步驟:

 1.觀察(observer)Runloop的迴圈

 2.一次Runloop迴圈,載入一張圖片

  |-Cell載入圖片的方法放到陣列裡

  |-Runloop迴圈 一次,就從陣列取出一個圖片載入

*/

- (void)viewDidLoad {

    [self addRunloopObserver];

    self.tasks = NSMutableArray.array;

    [self addTask:^{

       

    }];

}

- (void)addTask:(RunloopBlock)task{

    //儲存任務到陣列

    [self.tasks addObject:task];

    if (self.tasks.count == 0) {

        return;

    }

    if (self.tasks.count>18) {

        [self.tasks removeObjectAtIndex:0];

    }

}

- (void)addRunloopObserver{

    //獲取當前的runloop

    CFRunLoopRef runloop = CFRunLoopGetCurrent();

    //定義上下文

    /*

     typedef struct {

     CFIndex    version;

     void *    info;

     const void *(*retain)(const void *info);

     void    (*release)(const void *info);

     CFStringRef    (*copyDescription)(const void *info);

     } CFRunLoopObserverContext;

     */

    

    CFRunLoopObserverContext context = {

        0,

        (__bridge void *)self,

        &CFRetain,

        &CFRelease,

        NULL

    };

    

    //建立觀察者

    //c 中create new copy 堆區開闢記憶體空間。需要釋放

    CFRunLoopObserverRef  runLoopObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &callback, &context);

    CFRunLoopAddObserver(runloop, runLoopObserver, kCFRunLoopCommonModes);

    //釋放

    CFRelease(runloop);

}


void callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){

    ViewController *vc =  (__bridge ViewController *)info;

    if (vc.tasks.count == 0) {

        return;

    }

    RunloopBlock task = vc.tasks.firstObject;

    task();

    [vc.tasks removeObjectAtIndex:0];

};






相關文章