1. 程式人生 > >RunLoop 總結: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個人小結 (這是一篇總結的很通俗容易理解的文章)
  • iPhonedevwiki中的CFRunLoop(commonModes中其實包含了三種Mode,我們通常知道兩種,還有一種是啥,你知道麼?)

說明

因為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
{
    @autoreleasepool {
        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
{
    @autoreleasepool {
        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後--kCFRunLoopDefaultMode
2016-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後--kCFRunLoopDefaultMode
2016-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中執行的。
4.在子執行緒建立好後,最好所有的任務都放在AutoreleasePool中。

注意點一解釋
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後--kCFRunLoopDefaultMode
2016-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}----子執行緒任務結束

注意點四解釋
關於AutoReleasePool的官方文件中有提到:

If you spawn a secondary thread.
You must create your own autorelease pool block as soon as the thread begins executing; 
otherwise, your application will leak objects. (See Autorelease Pool Blocks and Threads for details.)

Each thread in a Cocoa application maintains its own stack of autorelease pool blocks. 
If you are writing a Foundation-only program or if you detach a thread, you need to create your own autorelease pool block.
If your application or thread is long-lived and potentially generates a lot of autoreleased objects, 
you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); 
otherwise, autoreleased objects accumulate and your memory footprint grows. 
If your detached thread does not make Cocoa calls, you do not need to use an autorelease pool block.

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是在每一個時間片切換到不同的執行緒去跑一會,呈現出的多執行緒效果)。

相關推薦

RunLoop 總結RunLoop應用場景

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

iOS 多執行緒在專案中的應用場景

獲取線上版本號是一件很耗時的操作,所以開闢一個子執行緒,程式碼如下 //檢測新版本 //說明:開闢子執行緒執行耗時程式碼塊,然後在主執行緒中重新整理和顯示 dispatch_async(dispatch_get_global_queue(0, 0

RunLoop 總結RunLoop應用場景

上一篇講了使用RunLoop保證子執行緒的長時間存活,而不是執行完任務後就立刻銷燬的應用場景。這一篇就講述一下RunLoop如何保證NSTimer在檢視滑動時,依然能正常運轉。 參考資料 好的書籍都是值得反覆看的,那好的文章,好的資料也值得我們反覆看。我

RunLoop總結RunLoop 與GCD 、Autorelease Pool之間的關係

如果在面試中問到RunLoop相關的知識,很有可能也會問到RunLoop與GCD、Autorelease Pool有沒有關係,哪些地方用到了GCD、Autorelease Pool等。 So,本文就總結一下RunLoop與GCD和 Autorelease Pool 之間的關係,看看在R

RunLoop總結RunLoop基礎知識

沒有實際應用場景,很難理解一些抽象空洞的東西,所以前面幾篇文章先介紹了RunLoop的幾個使用場景。 另外AsyncDisplayKit中也有大量使用RunLoop的示例。 關於實際的使用RunLoop 的案例和使用場景就不總結了,今天總結一點RunLoop的基礎知識和概念。 什

Android應用開發網路工具——Volley

引言 網路一直是我個人的盲點,前一陣子抽出時間學習了一下Volley網路工具的使用方法,也透過原始碼進行了進一步的學習,有一些心得想分享出來。在Android開發中,成熟的網路工具不少,Android自帶了HttpClient,還有okhttp,還有koush大神建立的i

深度學習應用系列——計算機視覺表面缺陷檢測

Fully Convolutional Networks for Surface Defect Inspection in Industrial Environment 佔坑 一種思路:使用語義分割

淺談Android應用保護Android應用逆向的基本方法

對於未進行保護的Android應用,有很多方法和思路對其進行逆向分析和攻擊。使用一些基本的方法,就可以打破對應用安全非常重要的機密性和完整性,實現獲取其內部程式碼、資料,修改其程式碼邏輯和機制等操作。這篇文章主要介紹一些基本的應用逆向和分析方法,演示Android應用的程式

Arduino 高階教程 02用 Visual Studio 2015 開發 Arduino 應用程式

Arduino IDE 的缺點 雖然 Arduino 很流行很火爆,但是 Arduino IDE 卻非常、非常、非常弱。編寫程式碼很不方便,只能說提供了一個最基本的寫程式碼的工具而已,除錯程式碼也很不方便。而且,Arduino IDE 中寫程式碼,只能是把

SpringBoot應用自定義starter

一、碼前必備知識 1、SpringBoot starter機制   SpringBoot中的starter是一種非常重要的機制,能夠拋棄以前繁雜的配置,將其統一整合進starter,應用者只需要在maven中引入starter依賴,SpringBoot就能自動掃描到要載入的資訊並啟動相應的預設配置。sta

Dubbo框架應用--服務體系

white 部件 esp 恢復 fonts resp auto zh-cn 通過 Dubbo 是阿裏巴巴公司開源的一個高性能優秀的服務框架,使得應用可通過高性能的 RPC 實現服務的輸出和輸入功能,能夠和 Spring框架無縫集成,也是一個很全面的

圖書管理系統總結——數據庫操作

set 結果集 static () block csdn .com oid 取數據 由於經常在晚上回宿舍寫代碼,沒有網,故而沒用老師提供的Oracle數據庫服務器,在自己電腦上裝了一個MySQL。而且JAVA配MySQL還是很方便的。 數據庫連接: 參考了http://b

去哪網實習總結開發定時任務JavaWeb

pri simple mod 節點 easy run dsm 16px 發送郵件 本來是以做數據挖掘的目的進去哪網的,結構卻成了系統開發。。。 只是還是比較認真的做了三個月,老師非常認同我的工作態度和成果。。。 實習立即就要結束了。總結一下幾點之前沒有註意過的變成

構建用於C#應用程序的應用商店

開發工具 曾經 我只 info 代碼 bsp 環境 管理員 winform 我在就職的公司開發工具型軟件,桌面版的,我們公司有各種工具軟件的需求。現在我已經記不清我生產了多少了。我相信再過一段時間,也許幾個月,也許一年後,我也會記不住之前開發過什麽,或許有一定的類別的印象,

編程題#2奇偶排序

ios -- cout 如果 cin art 數組 n) bool 描述 輸入十個整數,將十個整數按升序排列輸出,並且奇數在前,偶數在後。 輸入 輸入十個整數 輸出 按照奇偶排序好的十個整數 #include <iostream> using name

第五章循環結構

如何 如何使用 滿足 為什麽 發現 每日 生活 打印機 結構 第五章:循環結構(一) 1.什麽是循環結構 在日常生活中,會有很多需要反復執行的事情,比如:每一年的 4個季節,每一周的7天,每日的3餐,打印機每份文檔打印50 份,一圈跑道400米跑3圈,都是在反復執行的。 2

碼海拾遺Linux常用命令

spa /usr 使用 virtual 復制文件 壓縮包 文件中 常用 目錄   一、Linux系統安裝   系統安裝可以分兩類:實體機安裝Linux,虛擬機(常用虛擬機軟件有兩種:VMware和VirtualBox)安裝Linux。   安裝過程網上有很多教程,這裏就不贅

第二章之集成運算放大器的線性應用基礎

+= 比例 com 作用 什麽是 開始 應用電路 ID 通過   第一章已經介紹了有關放大器的基本概念以及最重要的思想——負反饋思想。而第二章就開始介紹最重要的放大部件——集成運算放大器的性質和應用電路 分別從模型(基本電路),電壓傳輸特性(性質),線性和非線性運用(實踐)

Android項目實戰十六QQ空間實現—— 展示說說中的評論內容並有相應點擊事件

con toast short demo append 集合 obj parent 自帶 原文:Android項目實戰(十六):QQ空間實現(一)—— 展示說說中的評論內容並有相應點擊事件大家都玩QQ空間客戶端,對於每一個說說,我們都可以評論,那麽,對於某一條評論:

iOS底層原理總結 - 探尋block的本質

release nss 命令 static 斷點 fix 生成 什麽是 block類型 面試題 block的原理是怎樣的?本質是什麽? __block的作用是什麽?有什麽使用註意點? block的屬性修飾詞為什麽是copy?使用block有哪些使用