RunLoop 總結:RunLoop的應用情境(一),runloop情境

來源:互聯網
上載者:User

RunLoop 總結:RunLoop的應用情境(一),runloop情境
參考資料 

好的書籍都是值得反覆看的,那好的文章,好的資料也值得我們反覆看。我們在不同的階段來相同的文章或資料或書籍都能有不同的收穫,那它就是好文章,好書籍,好資料。
關於iOS 中的RunLoop資料非常的少,以下這些資料都是非常好的。

  • CF架構源碼(這是一份很重要的源碼,可以看到CF架構的每一次迭代,我們可以下載最新的版本來分析,或與以下文章對比學習。目前最新的是CF-1153.18.tar.gz)
  • RunLoop官方文檔(學習iOS的任何技術,官方文檔都是入門或深入的極好手冊;我們也可以在Xcode--->Help--->Docementation and API Reference --->搜尋RunLoop---> Guides(59)--->《Threading Programming Guide:Run Loops》這篇即是)
  • 深入理解RunLoop(不要看到右邊捲軸很長,其實文章占篇幅2/5左右,下面有很多的評論,可見這篇文章的火熱)
  • RunLoop個人小結 (這是一篇總結的很通俗容易理解的文章)
  • sunnyxx線下分享RunLoop(這是一份關於線下分享與討論RunLoop的視頻,備用地址:https://pan.baidu.com/s/1pLm4Vf9)
  • iPhonedevwiki中的CFRunLoop(commonModes中其實包含了三種Mode,我們通常知道兩種,還有一種是啥,你知道嗎?)
  • 維基百科中的Event loop(可以看看這篇文章瞭解一下事件迴圈)
說明

因為RunLoop 裡有很多新的平時基本很難接觸到的概念或者對象,所以如果從RunLoop是啥,裡麵包含啥,為什麼是這樣講起,難免太迷茫,太晦澀難懂。大多數關於RunLoop 的文章也是從基礎講起的,文章也比較長,可能看了三分之一,就已經懵了,沒了技術看下去的動力。所以我決定先從RunLoop的使用情境和用法講起,看到了一些用法和現象,再去看它的實現就要容易理解的多了。
文章中的範例程式碼,我會在文章末提供一個關於RunLoop的樣本Demo。

RunLoop的使用情境

下面介紹一下,可以使用RunLoop的幾個使用情境(本想一篇寫完,無奈一個使用情境就讓文章很長了,還是分幾篇來講吧)。

1.保證線程的長時間存活

在iOS開發過程中,有時候我們不希望一些花費時間比較長的操作阻塞主線程,導致介面卡頓,那麼我們就會建立一個子線程,然後把這些花費時間比較長的操作放在子線程中來處理。可是當子線程中的任務執行完畢後,子線程就會被銷毀掉。
怎麼來驗證上面這個結論呢?
首先,我們建立一個HLThread類,繼承自NSThread,然後重寫dealloc 方法。

@interface HLThread : NSThread@end@implementation HLThread- (void)dealloc{    NSLog(@"%s",__func__);}@end

然後,在控制器中用HLThread建立一個線程,執行一個任務,觀察任務執行完畢後,線程是否被銷毀。

- (void)viewDidLoad {    [super viewDidLoad];    // 1.測試線程的銷毀    [self threadTest];}- (void)threadTest{    HLThread *subThread = [[HLThread alloc] initWithTarget:self selector:@selector(subThreadOpetion) object:nil];    [subThread start];}- (void)subThreadOpetion{    NSLog(@"%@----子線程任務開始",[NSThread currentThread]);    [NSThread sleepForTimeInterval:3.0];    NSLog(@"%@----子線程任務結束",[NSThread currentThread]);}

控制台輸出的結果如下:

2016-12-01 16:44:25.559 RunLoopDemo[4516:352041] <HLThread: 0x608000275680>{number = 4, name = (null)}----子線程任務開始2016-12-01 16:44:28.633 RunLoopDemo[4516:352041] <HLThread: 0x608000275680>{number = 4, name = (null)}----子線程任務結束2016-12-01 16:44:28.633 RunLoopDemo[4516:352041] -[HLThread dealloc]

當子線程中的任務執行完畢後,線程就被立刻銷毀了。如果程式中,需要經常在子線程中執行任務,頻繁的建立和銷毀線程,會造成資源的浪費。這時候我們就可以使用RunLoop來讓該線程長時間存活而不被銷毀。

我們將上面的範例程式碼修改一下,修改後的代碼過程為,建立一個子線程,當子線程啟動後,啟動runloop,點擊視圖,會在子線程中執行一個耗時3秒的任務(其實就是讓線程睡眠3秒)。

修改後的代碼如下:

@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // 1.測試線程的銷毀    [self threadTest];}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{    [self performSelector:@selector(subThreadOpetion) onThread:self.subThread withObject:nil waitUntilDone:NO];}- (void)threadTest{    HLThread *subThread = [[HLThread alloc] initWithTarget:self selector:@selector(subThreadEntryPoint) object:nil];    [subThread setName:@"HLThread"];    [subThread start];    self.subThread = subThread;}/** 子線程啟動後,啟動runloop */- (void)subThreadEntryPoint{    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];    //如果注釋了下面這一行,子線程中的任務並不能正常執行    [runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];    NSLog(@"啟動RunLoop前--%@",runLoop.currentMode);    [runLoop run];}/** 子線程任務 */- (void)subThreadOpetion{    NSLog(@"啟動RunLoop後--%@",[NSRunLoop currentRunLoop].currentMode);    NSLog(@"%@----子線程任務開始",[NSThread currentThread]);    [NSThread sleepForTimeInterval:3.0];    NSLog(@"%@----子線程任務結束",[NSThread currentThread]);}@end

先看控制台輸出結果:

2016-12-01 17:22:44.396 RunLoopDemo[4733:369202] 啟動RunLoop前--(null)2016-12-01 17:22:49.285 RunLoopDemo[4733:369202] 啟動RunLoop後--kCFRunLoopDefaultMode2016-12-01 17:22:49.285 RunLoopDemo[4733:369202] <HLThread: 0x60000027cb40>{number = 4, name = HLThread}----子線程任務開始2016-12-01 17:22:52.359 RunLoopDemo[4733:369202] <HLThread: 0x60000027cb40>{number = 4, name = HLThread}----子線程任務結束2016-12-01 17:22:55.244 RunLoopDemo[4733:369202] 啟動RunLoop後--kCFRunLoopDefaultMode2016-12-01 17:22:55.245 RunLoopDemo[4733:369202] <HLThread: 0x60000027cb40>{number = 4, name = HLThread}----子線程任務開始2016-12-01 17:22:58.319 RunLoopDemo[4733:369202] <HLThread: 0x60000027cb40>{number = 4, name = HLThread}----子線程任務結束

有幾點需要注意:
1.擷取RunLoop只能使用 [NSRunLoop currentRunLoop] 或 [NSRunLoop mainRunLoop];
2.即使RunLoop開始運行,如果RunLoop 中的 modes 為空白,或者要執行的mode裡沒有item,那麼RunLoop會直接在當前loop中返回,並進入睡眠狀態。
3.自己建立的Thread中的任務是在kCFRunLoopDefaultMode這個mode中執行的。

注意點一解釋
RunLoop官方文檔中的第二段中就已經說明了,我們的應用程式並不需要自己建立RunLoop,而是要在合適的時間啟動runloop。
CF架構源碼中有CFRunLoopGetCurrent(void)CFRunLoopGetMain(void),查看源碼可知,這兩個API中,都是先從全域字典中取,如果沒有與該線程對應的RunLoop,那麼就會幫我們建立一個RunLoop(建立RunLoop的過程在函數_CFRunLoopGet0(pthread_t t)中)。

注意點二解釋
這一點,可以將範例程式碼中的[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];,可以看到注釋掉後,無論我們如何點擊視圖,控制台都不會有任何的輸出,那是因為mode 中並沒有item任務。經過NSRunLoop封裝後,只可以往mode中添加兩類item任務:NSPort(對應的是source)、NSTimer,如果使用CFRunLoopRef,則可以使用C語言API,往mode中添加source、timer、observer。
如果不添加 [runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];,我們把runloop的資訊輸出,可以看到:


添加port前的RunLoop

如果我們添加上[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];,再把RunLoop的資訊輸出,可以看到:


添加port後的RunLoop

注意點三解釋
怎麼確認自己建立的子線程上的任務是在kCFRunLoopDefaultMode這個mode中執行的呢?
我們只需要在執行任務的時候,列印出該RunLoop的currentMode即可。
因為RunLoop執行任務是會在mode間切換,只執行該mode上的任務,每次切換到某個mode時,currentMode就會更新。源碼請下載:CF架構源碼
CFRunLoopRun()方法中會調用CFRunLoopRunSpecific()方法,而CFRunLoopRunSpecific()方法中有這麼兩行關鍵代碼:

CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);......這中間還有好多邏輯代碼CFRunLoopModeRef previousMode = rl->_currentMode;rl->_currentMode = currentMode;...... 這中間也有一堆的邏輯rl->_currentMode = previousMode;

我測試後,控制台輸出的是:

2016-12-02 11:09:47.909 RunLoopDemo[5479:442560] 啟動RunLoop後--kCFRunLoopDefaultMode2016-12-02 11:09:47.910 RunLoopDemo[5479:442560] <HLThread: 0x608000270a80>{number = 4, name = HLThread}----子線程任務開始2016-12-02 11:09:50.984 RunLoopDemo[5479:442560] <HLThread: 0x608000270a80>{number = 4, name = HLThread}----子線程任務結束
AFNetworking中的RunLoop案例

在AFNetworking 2.6.3之前的版本,使用的還是NSURLConnection,可以在AFURLConnectionOperation中找到使用RunLoop的源碼:

+ (void)networkRequestThreadEntryPoint:(id)__unused object {    @autoreleasepool {        [[NSThread currentThread] setName:@"AFNetworking"];        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];        [runLoop run];    }}+ (NSThread *)networkRequestThread {    static NSThread *_networkRequestThread = nil;    static dispatch_once_t oncePredicate;    dispatch_once(&oncePredicate, ^{        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];        [_networkRequestThread start];    });    return _networkRequestThread;}

AFNetworking都是通過調用 [NSObject performSelector:onThread:..] 將這個任務扔到了後台線程的 RunLoop 中。

- (void)start {    [self.lock lock];    if ([self isCancelled]) {        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];    } else if ([self isReady]) {        self.state = AFOperationExecutingState;        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];    }    [self.lock unlock];}

我們在使用NSURLConnection 或者NSStream時,也需要考慮到RunLoop問題,因為預設情況下這兩個類的對象產生後,都是在當前線程的NSDefaultRunLoopMode模式下執行任務。如果是在主線程,那麼就會出現滾動ScrollView以及其子視圖時,主線程的RunLoop切換到UITrackingRunLoopMode模式,那麼NSURLConnection或者NSStream的回調就無法執行了。

要解決這個問題,有兩種方式:
第一種方式是建立出NSURLConnection對象或者NSStream對象後,再調用 - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSRunLoopMode)mode,設定RunLoopMode即可。需要注意的是NSURLConnection必須使用其初始化構造方法- (nullable instancetype)initWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate startImmediately:(BOOL)startImmediately來建立對象,設定Mode才會起作用。

第二種方式,就是所有的任務都在子線程中執行,並保證子線程的RunLoop正常運行即可(即上面AFNetworking的做法,因為主線程的RunLoop切換到UITrackingRunLoopMode,並不影響其他線程執行哪個mode中的任務,電腦CPU是在每一個時間片切換到不同的線程去跑一會,呈現出的多線程效果)。

文中的範例程式碼都來自:RunLoopDemos中的RunLoopDemo01

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.