由NSURLConnection的使用引發(fā)的思考

前段時間有小伙伴用 dispatch queues 來調度 NSURLConnection 去下載資源,代碼是這樣寫的:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
   self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
   [self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
   _downloading = YES;
   [self.connection start];
});

最后,發(fā)現一直收不到回調響應。要弄清楚這個問題,首先需要弄清什么是 Event Loop。

為什么要有 Event Loop ?

早期的計算機使用命令行界面來進行交互,這種程序按照一定的順序來執(zhí)行指令,并且需要記住大量的命令,對用戶來說交互不太友好。后來出現了圖形用戶界面(Graphical User Interface,簡稱 GUI),通過在屏幕特定的位置以各種美觀而不單調的視覺消息來告知用戶狀態(tài)的改變,從視覺上更容易被用戶接受。GUI 應用程序的特點是注重與用戶的交互,保持較高的響應速度,根據用戶的實時交互情況執(zhí)行指令。例如 微信QQ app 啟動之后并非一直執(zhí)行直到結束,而是在做了一些必要的初始化工作后停下來,等待用戶的下一步動作。用戶可能點擊二維碼掃描按鈕,或者打開聊天窗口,這些行為都是不確定的。而程序只能等到用戶做出交互動作之后,才會執(zhí)行相應的處理代碼。

由于 GUI 的執(zhí)行流程是由用戶的操作行為(事件)控制的,并且不可預期,為了滿足這樣的需求,所以有了事件驅動編程(event-driven programming)。事件驅動編程就是針對這種“程序的執(zhí)行由事件決定”的應用的一種編程范型。它可以實現非既定順序,隨時增加工作內容進來,執(zhí)行流程由事件決定,在沒有事件到來時,可以不做任何事情地等待(不占用 CPU),有事件到來時,讓操作系統(tǒng)喚醒睡眠的線程去干活。這種編程范式需要一種程序基礎結構來提供支持,就是 Event Loop。

Event Loop 的定義

Wikipedia 這樣定義 Event Loop

In computer science, the event loop, message dispatcher, message loop, message pump, or run loop is a programming construct that waits for and dispatches events or messages in a program.

(大意是:Event Loop是一個程序結構,用于等待和發(fā)送消息和事件。)

Event Loop(iOS 中叫 Run Loop)采用觀察者模式來設計,監(jiān)聽到事件發(fā)生時,調用預先設置好的事件處理程序來處理事件。這涉及到幾個角色:事件源、事件、事件監(jiān)聽者、事件監(jiān)聽者接口以及消息隊列。iOS Run Loop 開源代碼可以看這里,深入理解Run Loop

底層網絡

每個復雜的計算機系統(tǒng)都是構建在一個或多個抽象層之上,底層網絡也是如此。網絡的根本是 Berkley 或 BSD Sockets。它執(zhí)行大多數基礎的網絡任務:發(fā)送與接收一系列的二進制位。由于需要使用相當數量的代碼才能恰當地發(fā)送一個字節(jié),而且相同的邏輯對于每個 Socket 都要重復執(zhí)行,因此人們構建了庫來封裝該邏輯,以便重用。在 iOS 中,這個庫叫 Core Foundation networking 或 CFNetwork,它是對原始 Socket 的輕量級封裝。CFNetwork 的優(yōu)勢在于被集成到系統(tǒng)級的設置與主運行循環(huán)中。
不過對大多數場景來說顯得很笨重,后來在 CFNetwork 的基礎上封裝出了 NSStream,然后在這個基礎之上才有 NSURLConnection,也就是上例代碼中使用的類。
(摘自《iOS 網絡高級編程》 一書)

問題原因及解決方法

運行循環(huán)集成對線程來說必不可少,也是 iOS 中事件處理流的基礎。iOS 中每個線程都配置有自己的運行循環(huán),主線程循環(huán)叫主運行循環(huán)或 UI 運行循環(huán)。每個循環(huán)都會調度異步事件的處理,如果沒有事件(鍵盤、手勢、網絡事件及定時器都是事件)發(fā)生,線程就會休眠。主線程的 Run Loop 默認開啟,輔助線程的 Run Loop 需要手動顯示開啟。在輔助線程開啟 Run Loop 前,必須添加一個輸入源或定時器,否則 Run Loop 會立刻退出。

上面的代碼,在使用 dispatch queues 來調度 NSURLConnection 的時候,因為系統(tǒng)用來調度 GCD 塊或是NSOperationQueue 中的任務的輔助線程默認不開啟運行循環(huán),所以當網絡事件發(fā)生時沒有得到處理,也就不會收到回調。解決方法如下:

在 connection start 之后開啟輔助線程的 Run Loop

[self.connection start];
[[NSRunLoop currentRunLoop] run];

然后在下載完成之后或失敗的時候,停掉 Run Loop。

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"connection connectionDidFinishLoading.");
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"connection didFailWithError!");
}

不知道下載完成后會不會自動停掉,筆者沒有測試,直接調用CFRunLoopStop方法。iOS 的Run Loop可以添加觀察者來查看 Run Loop 的狀態(tài),讀者可以自己測試。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 由于文章長度限制,本文作為[譯]線程編程指南(一)后續(xù)部分。 Run Loops Run loop是與線程相關的基...
    巧巧的二表哥閱讀 1,263評論 0 5
  • 這是一篇對Run Loop開發(fā)文檔《Threading Program Guide:Run Loops》的翻譯,來...
    鴻雁長飛光不度閱讀 3,836評論 3 29
  • 什么是Run Loops RunLoops是與線程相關聯的基礎部分,一個Run Loop就是事件處理循環(huán),他是用來...
    傻傻小蘿卜閱讀 1,097評論 0 5
  • 本文將從以下幾個部分來介紹多線程。 第一部分介紹多線程的基本原理。 第二部分介紹Run loop。 第三部分介紹多...
    曲年閱讀 1,339評論 2 14
  • 在這個世界上,即使有通天徹地之能,也不能很快就找到用武之地,成就就是從微小的成績慢慢積累,并逐漸堅持,沒有所謂的一...
    般若秋雪閱讀 1,085評論 0 1

友情鏈接更多精彩內容