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的資訊輸出,可以看到:
如果我們新增上[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
,再把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有哪些使用