runloop

一撕贞、Runloop 簡介

1. 簡介

  • RunLoop就是讓線程隨時處理事件但不退出的機制
  • 每一個線程都有一個RunLoop
  • RunLoop 實際上就是一個對象唆涝,這個對象管理了其需要處理的事件(比如button的點擊、各種手勢的的事件萝究、定時器免都、tableView的代理方法)和消息,是iOS里的一種事件處理機制帆竹。
  • 線程執(zhí)行了這個函數(shù)后绕娘,就會一直處于這個函數(shù)內(nèi)部 “接受消息->等待->處理” 的循環(huán)中,直到這個循環(huán)結(jié)束(比如傳入 quit 的消息)栽连,函數(shù)返回险领。

2. 基本作用

  • 保持程序的持續(xù)運行(比如主運行循環(huán))
  • 處理App中的各種事件(比如觸摸事件、定時器事件秒紧、Selector事件)
  • 節(jié)省CPU資源绢陌,提高程序性能:該做事時做事,該休息時休息

3. API

OSX / iOS 系統(tǒng)中熔恢,有2套API來訪問和使用 RunLoop脐湾。

  • CFRunLoopRef 是在 CoreFoundation 框架內(nèi)的,它提供了純 C 函數(shù)的 API绩聘,所有這些 API 都是線程安全的沥割。
  • NSRunLoop 是基于 CFRunLoopRef 的封裝,提供了面向?qū)ο蟮?API凿菩,但是這些 API 不是線程安全的机杜。所以要了解 RunLoop 內(nèi)部結(jié)構(gòu),需要多研究 CFRunLoopRef 層面的API(Core Foundation 層面)

NSRunLoopCFRunLoopRef 都代表著 RunLoop 對象衅谷。

4. 存在價值

main 函數(shù)中的 RunLoop (主運行循環(huán)):第14行代碼的 UIApplicationMain 函數(shù)內(nèi)部就啟動了一個 RunLoop椒拗。所以 UIApplicationMain 函數(shù)一直沒有返回,保持了程序的持續(xù)運行。這個默認啟動的 RunLoop 是跟主線程相關(guān)聯(lián)的蚀苛。

image.png

二在验、Runloop 解析

1. Runloop 運行模式

一種 Runloop 運行模式就是一個要監(jiān)控的 Input 和 Timer 事件源的集合或者是一個要通知的 Runloop 觀察者的集合。每次運 行Runloop堵未,都要指定一個運行模式(顯示地或者隱式地)腋舌。在 Runloop 的運行期間,只有和當前運行模式相關(guān)的源才能被監(jiān)控和允許發(fā)送事件渗蟹。相似的块饺,只有和當前運行模式相關(guān)的觀察者才會被通知 Runloop 的行為。和其他模式相關(guān)的源會保留新的事件直到 Runloop 運行在了合適的模式才會分發(fā)雌芽。

在我們的代碼中授艰,我們可以通過字符串來標識模式。Cocoa和Core Foundation定義了一個默認模式和幾個普通的有用的模式世落,這些模式都是用字符串來標識的淮腾。我們可以用一個字符串當做名字來自定義一個模式,雖然我們自定義模式的名字是隨意的屉佳,但是模式的內(nèi)容不是隨意的谷朝,在我們自己創(chuàng)建的要用的模式中至少要添加一個 Input 源、 Timer 源或者 Runloop 觀察者忘古。

在 Runloop 的特殊階段我們可是使用運行模式來過濾我們不想要的源的事件徘禁,大多數(shù)的情況下,Runloop 都運行在系統(tǒng)提供的默認模式下髓堪,然而 Model Panel 可能運行在“模式”模式,當運行在這個模式期間娘荡,只有和這個模式相關(guān)的事件源才會發(fā)送事件到我們的線程干旁。對于第二線程來說,我們通常使用自定義模式來阻止低優(yōu)先級的事件源在其他關(guān)鍵處理的時間內(nèi)發(fā)送事件炮沐。

注意:運行模式不是根據(jù)事件類型劃分的争群,而是根據(jù)事件源劃分的。我們不能通過模式來匹配鼠標按下事件或者鍵盤事件大年,但是我們可以用運行模式來監(jiān)聽一組不同的Port换薄、暫時掛起Timers或者改變當前被監(jiān)控的事件源和Runloop觀察者。

下面列舉了一些Cocoa和Core Foundation定義的標準模式:

  • NSDefaultRunLoopMode:默認的運行模式翔试,用于大部分操作轻要,除了NSConnection對象事件。
  • NSConnectionReplyMode:用來監(jiān)控NSConnection對象的回復的垦缅,很少能夠用到冲泥。
  • NSModalPanelRunLoopMode:用于標明和Mode Panel相關(guān)的事件。
  • NSEventTrackingRunLoopMode:用于跟蹤觸摸事件觸發(fā)的模式(例如UIScrollView上下滾動)。
  • NSRunLoopCommonModes:是一個模式集合凡恍,當綁定一個事件源到這個模式集合的時候就相當于綁定到了集合內(nèi)的每一個模式志秃。Cocoa 應(yīng)用默認包含 Default、Panel嚼酝、Event Tracking 模式浮还,Core Foundation 只包含 Default 模式,我們可以通過 CFRunLoopAddCommonMode 添加模式闽巩。

2. Runloop 處理邏輯

Runloop接收來自兩種源的事件:

  1. 輸入源(Input sources):傳遞異步消息碑定,通常來自于其他線程或者程序。
  2. 定時源(Timer sources):傳遞同步消息又官,在設(shè)定好的時間或者循環(huán)間斷地發(fā)生的事件延刘。

這兩種事件源都是使用應(yīng)用指定的事件處理方法來處理到達的事件。

下面的圖顯示了Runloop和事件源的概念結(jié)構(gòu)六敬。 Input sources異步的分發(fā)事件到響應(yīng)的處理器碘赖,然后引起runUntilDate:(由線程相關(guān)的Runloop對象調(diào)用)方法退出。 Timer sources同步分發(fā)事件到相應(yīng)的處理器但是不會引起Runloop退出外构。

1.png
2.png
3.png

備注:

  • 輸入源:每一個需要Runloop處理事件的對象都有一個輸入源(InputSource),并且把這個輸入源添加到Runloop里普泡,每產(chǎn)生一個事件(比如用戶做了一個手勢、點了一個button审编、滑動了一下tableview撼班、定時器到時)就把這個事件放到對應(yīng)的輸入源。Runloop運行時循環(huán)檢查每一個輸入源是否有事件需要處理垒酬,如果有事件要處理Runloop就就調(diào)用這個事件的處理方法(通過addTargetxxx指定的方法或者是代理的方法)砰嘁。如果Runloop里所有的輸入源都沒有事件要處理,Runloop會休眠勘究。如果Runloop里一個輸入源都沒有(對象銷毀前會把它之前添加的那個輸入源取消)矮湘,Runloop(runUntilDate:這個方法)就退出來了。
  • 除了處理輸入源的事件口糕,Runloop也會生成Runloop行為的通知缅阳。注冊Runloop的觀察者可以收到這些消息,然后在線程內(nèi)用他們做一些額外的處理景描。我們只能使用Core Foundation接口來注冊線程的Runloop觀察者

3. Input Sources

Input Sources 異步地分發(fā)事件到線程十办。大概有兩種類型的 Input Sources,Port-based類型的輸入源監(jiān)控著應(yīng)用的Mach端口超棺,自定義的輸入源監(jiān)控著自定義的事件源向族。NSRunloop不關(guān)心輸入源的類型。兩種輸入源唯一的不同是輸入源的觸發(fā)方式说搅,Port-based輸入源是由系統(tǒng)內(nèi)核觸發(fā)的炸枣,而自定義的輸入源要我們自己觸發(fā)。創(chuàng)建輸入源的時候我們就給給輸入源添加指定的模式。下面是一些輸入源:

  • Port-Based Sources
    Cocoa 和 Core Foundation 提供了類和接口用來創(chuàng)建 Port-Based 源适肠,Cocoa 只要創(chuàng)建 NSPort 對象霍衫,并添加到 NSRunloop 中就可以啦,NSPort負責輸入源的創(chuàng)建和配置侯养。Core Foundation 需要手動的常見 port 和輸入源敦跌。

  • Custom Input Sources
    我們要用到CFRunLoopSourceRef函數(shù)創(chuàng)建輸入源,并定義幾個回調(diào)函數(shù)用于配置輸入源逛揩、處理事件和刪除輸入源柠傍。事件的觸發(fā)機制要我們自己定義。

  • Cocoa Perform Selector Sources
    Cocoa定義了可以在任何線程上執(zhí)行方法的事件源辩稽,在想要執(zhí)行的線程上執(zhí)行方法是順序執(zhí)行的惧笛,避免了多個方法在線程上執(zhí)行的同步問題。Perform Selector Sources在方法執(zhí)行完之后就會自己從NSRunloop中刪除逞泄。
    Perform Selector Sources要求目標線程的NSRunloop必須是運行的患整,主線程默認是運行的。NSRunloop在一次迭代過程中會處理所有的Perform Selector調(diào)用喷众,而不是一次迭代處理一個Perform Selector調(diào)用各谚。NSObject中定義的Perform Selector方法如下

    • performSelectorOnMainThread:withObject:waitUntilDone:
    • performSelectorOnMainThread:withObject:waitUntilDone:modes:
    • performSelector:onThread:withObject:waitUntilDone:
    • performSelector:onThread:withObject:waitUntilDone:modes:
    • performSelector:withObject:afterDelay:
    • performSelector:withObject:afterDelay:inModes:
    • cancelPreviousPerformRequestsWithTarget:
    • cancelPreviousPerformRequestsWithTarget:selector:object:

    延遲執(zhí)行是在NSRunloop的下一次迭代中過了指定的延遲事件才執(zhí)行。取消操作是針對延遲執(zhí)行方法的到千。

4. Timer Sources

Timer Sources 同步地在將來的一個確定的時間分發(fā)事件到我們的線程昌渤。Timers 可以讓線程通知自己去處理一些事情曹锨。Timers 不是一個實時的機制娃弓,當 Timers 觸發(fā)的時候 NSrunloop 剛好正在執(zhí)行處理函數(shù)爽丹,Timer s會等待 NSRunloop 調(diào)用自己的處理函數(shù)奴潘。

Timers 可以創(chuàng)建一次性的和重復性的事件,當創(chuàng)建重復性的事件的時候冬耿,Timers 只會根據(jù)規(guī)劃好的觸發(fā)時間來重新規(guī)劃觸發(fā)時間蚀腿,而不是根據(jù)確切的觸發(fā)時間宣羊。而且由于延遲觸發(fā)丟失了幾次觸發(fā)的話斟览,Timers 只會補充一次觸發(fā)。

5. NSRunloop 觀察者

不像是事件源一樣在事件觸發(fā)的時候執(zhí)行處理函數(shù)辑奈。NSRunloop 觀察者是在 NSRunloop 幾個執(zhí)行的特定的點觸發(fā)苛茂。NSRunloop 可以觀察的幾個事件是:

  • 進入 NSRunloop
  • NSRunloop 將要處理 Timer 事件
  • NSRunloop 將要處理 Input 事件
  • NSRunloop 將要進入睡眠
  • NSRunloop 被喚醒,但是是在處理事件之前
  • 退出 NSRunloop

創(chuàng)建觀察者的方法是 CFRunLoopObserverRef鸠窗,我們可以通 過Core Foundation 方法添加到指定的 NSRunloop妓羊。觀察者也可以創(chuàng)建一次性的和重復性的。一次性的觀察者觸發(fā)之后就會從 NSRunloo p中刪除稍计。

三躁绸、RunLoop 相關(guān)類

Core Foundation 中關(guān)于 RunLoop 的5個類

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef 注:RunLoop 如果沒有這些東西會直接退出

1. CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的運行模式:一個 RunLoop 包含若干個 Mode,每個Mode又包含若干個 Source/Timer/Observer
每次RunLoop啟動時,只能指定其中一個 Mode净刮,這個Mode被稱作 CurrentMode 如果需要切換 Mode剥哑,只能退出 Loop,再重新指定一個 Mode 進入 這樣做主要是為了分隔開不同組的 Source/Timer/Observer淹父,讓其互不影響株婴。

5.png

系統(tǒng)默認注冊了5個Mode:(前兩個跟最后一個常用)

  • kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
  • UITrackingRunLoopMode:界面跟蹤 Mode暑认,用于 ScrollView 追蹤觸摸滑動困介,保證界面滑動時不受其他 Mode 影響
  • UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
  • GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode蘸际,通常用不到
  • kCFRunLoopCommonModes: 這是一個占位用的Mode座哩,不是一種真正的Mode

2. CFRunLoopSourceRef 事件源(輸入源)

按照官方文檔的分類:

  • Port-Based Sources (基于端口,跟其他線程交互,通過內(nèi)核發(fā)布的消息)
  • Custom Input Sources (自定義)
  • Cocoa Perform Selector Sources (performSelector...方法)

按照函數(shù)調(diào)用棧的分類

  • Source0:非基于Port的,event事件粮彤,只含有回調(diào)根穷,需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理驾诈,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop缠诅。
  • Source1:基于Port的,包含了一個 mach_port 和一個回調(diào)乍迄,被用于通過內(nèi)核和其他線程相互發(fā)送消息,能主動喚醒 RunLoop 的線程管引。

函數(shù)調(diào)用棧


6.png

3. CFRunLoopTimerRef

CFRunLoopTimerRef 是基于時間的觸發(fā)器,基本上說的就是 NSTimer (CADisplayLink 也是加到 RunLoop),它受 RunLoop 的 Mode 影響闯两。
GCD的定時器不受 RunLoop 的 Mode 影響褥伴。

4. CFRunLoopObserverRef

CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變 可以監(jiān)聽的時間點有以下幾個


8.png

使用

 - (void)observer {
     // 創(chuàng)建observer
     CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
         NSLog(@"----監(jiān)聽到RunLoop狀態(tài)發(fā)生改變---%zd", activity);
     });
     // 添加觀察者:監(jiān)聽RunLoop的狀態(tài)
     CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
     // 釋放Observer
     CFRelease(observer);
 }
特別注意
 /*
     CF的內(nèi)存管理(Core Foundation)
     1.凡是帶有Create漾狼、Copy重慢、Retain等字眼的函數(shù),創(chuàng)建出來的對象逊躁,都需要在最后做一次release
     * 比如CFRunLoopObserverCreate
     2.release函數(shù):CFRelease(對象);
  */
復制代碼

四似踱、runloop應(yīng)用

  • NSTimer
  • PerformSelector
  • ImageView顯示
  • 需要讓線程執(zhí)行周期性的工作(常駐線程)
  • 自動釋放池
  • 需要使用 Port 或者自定義 Input Source 與其他線程進行通訊
  • NSURLConnection 在子線程中發(fā)起異步請求

1. NSTimer (最常見RunLoop使用)

場景還原:拖拽時模式由 NSDefaultRunLoopMode 進入 UITrackingRunLoopMode ,NSTimer 不再響應(yīng)圖片停止輪播稽煤,將計時器改成 NSRunLoopCommonModes 模式下兩種模式都可運行核芽。

 - (void)timer {
     NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
     // 定時器只運行在NSDefaultRunLoopMode下,一旦RunLoop進入其他模式酵熙,這個定時器就不會工作
     // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
     // 定時器只運行在UITrackingRunLoopMode下轧简,一旦RunLoop進入其他模式,這個定時器就不會工作
     // [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
     // 定時器會跑在標記為common modes的模式下
     // 標記為common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode兼容
     [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
 }
 - (void)timer2 {
     // 調(diào)用了scheduledTimer返回的定時器匾二,已經(jīng)自動被添加到當前runLoop中哮独,而且是NSDefaultRunLoopMode
     NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
     // 修改模式
     [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
 }
復制代碼

2. ImageView

需求:當用戶在拖拽時(UI交互時)不顯示圖片,拖拽完成時顯示圖片

  • 方法1 監(jiān)聽UIScrollerView滾動 (通過UIScrollViewDelegate監(jiān)聽,此處不再舉例)

  • 方法2 RunLoop 設(shè)置運行模式

    // 只在NSDefaultRunLoopMode模式下顯示圖片
    // inModes:設(shè)置運行模式
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
    復制代碼
    

3. 常駐線程 (重要)

應(yīng)用場景: 經(jīng)常在后臺進行耗時操作,如:監(jiān)控聯(lián)網(wǎng)狀態(tài)拳芙,掃描沙盒等 不希望線程處理完事件就銷毀,保持常駐狀態(tài)

  • 第一種(推薦)
    開啟

     - (void)run {
       //addPort:添加端口(就是source)  forMode:設(shè)置模式
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
       //啟動RunLoop
         [[NSRunLoop currentRunLoop] run];
      /*
       //另外兩種啟動方式
         [NSDate distantFuture]:遙遠的未來  這種寫法跟上面的run是一個意思
         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
         不設(shè)置模式
         [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
       */
     }
    復制代碼
    

    退出-退出當前線程

     [NSThread exit];
    復制代碼
    
  • 第二種(奇葩法)
    優(yōu)點:退出RunLoop比較方便-定義個標記 while(flag){...}

     - (void)run {
         while (1) {
             [[NSRunLoop currentRunLoop] run];
         }
     }
    復制代碼
    

4. 自動釋放池

在休眠前(kCFRunLoopBeforeWaiting)進行釋放皮璧,處理事件前創(chuàng)建釋放池舟扎,中間創(chuàng)建的對象會放入釋放池。
特別注意:在啟動 RunLoop 之前建議用 @autoreleasepool {...} 包裹恶导。
意義:創(chuàng)建一個大釋放池,釋放 {} 期間創(chuàng)建的臨時對象浆竭,一般好的框架的作者都會這么做。

7.png
- (void)execute {
     @autoreleasepool {
         NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
         [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
         [[NSRunLoop currentRunLoop] run];
}
復制代碼

5. 補充: GCD定時器

一般的NSTimer定時器因為受到RunLoop惨寿,會存在時間不準時的情況邦泄。 上文有提到GCD不受RunLoop影響,下面簡單的說一下它的使用

 /** 定時器(這里不用帶*,因為 dispatch_source_t 就是個類裂垦,內(nèi)部已經(jīng)包含了*) */
 @property (nonatomic, strong) dispatch_source_t timer;
 int count = 0;
 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
     // 獲得隊列
     // dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
     dispatch_queue_t queue = dispatch_get_main_queue();
     // 創(chuàng)建一個定時器(dispatch_source_t本質(zhì)還是個OC對象)
     self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
     // 設(shè)置定時器的各種屬性(幾時開始任務(wù)顺囊,每隔多長時間執(zhí)行一次)
     // GCD的時間參數(shù),一般是納秒 NSEC_PER_SEC(1秒 == 10的9次方納秒)
     // 何時開始執(zhí)行第一個任務(wù)
     // dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC) 比當前時間晚3秒
     dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
     uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
     dispatch_source_set_timer(self.timer, start, interval, 0);
     // 設(shè)置回調(diào)
     dispatch_source_set_event_handler(self.timer, ^{
         NSLog(@"------------%@", [NSThread currentThread]);
         count++;
 //        if (count == 4) {
 //            // 取消定時器
 //            dispatch_cancel(self.timer);
 //            self.timer = nil;
 //        }
     });
     // 啟動定時器
     dispatch_resume(self.timer);
 }
復制代碼

五蕉拢、runloop 與線程

每條線程都有唯一的一個與之對應(yīng)的 RunLoop 對象;
主線程的 RunLoop 已經(jīng)自動創(chuàng)建好了特碳,子線程的RunLoop需要主動創(chuàng)建;
RunLoop在第一次獲取時創(chuàng)建,在線程結(jié)束時銷毀;

  • 獲取RunLoop對象

    // 工作線程 需要程序員手工寫代碼讓runloop運行起來
    [NSRunLoop currentLoop]runUntilDate:]
    // Foundation
    [NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
    [NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
    // Core Foundation
    CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
    CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
    復制代碼
    
  • 線程安全性
    基于 Cocoa 的接口不是線程安全的晕换,基于 Core Foundation 的接口是線程安全的午乓。

六、RunLoop 面試題

  1. 什么是RunLoop闸准?

    • 其實它內(nèi)部就是do-while循環(huán),在這個循環(huán)內(nèi)部不斷的處理各種任務(wù)(比如Source益愈、Timer、Observer)夷家。
    • 一個線程對應(yīng)一個RunLoop,主線程的RunLoop默認已經(jīng)啟動,子線程的RunLoop需要手動啟動(調(diào)用run方法) 蒸其。
    • RunLoop只能選擇一個Mode啟動,如果當前Mode中沒有任何Soure、Timer库快、Observer,那么就直接退出RunLoop摸袁。
  2. 在開發(fā)中如何使用RunLoop?什么應(yīng)用場景义屏?

    • 開啟一個常駐線程(讓一個子線程不進入消亡狀態(tài),等待其他線程發(fā)來消息,處理其他事件)
    • 在子線程中開啟一個定時器
    • 在子線程中進行一些長期監(jiān)控
    • 可以控制定時器在特定模式下執(zhí)行
    • 可以讓某些事件(行為靠汁、任務(wù))在特定模式下執(zhí)行
    • 可以添加 Observer 監(jiān)聽 RunLoop 的狀態(tài),比如監(jiān)聽點擊事件的處理(在所有點擊事件之前做一些事情)
  3. 在異步線程中下載很多圖片。如果失敗了闽铐,該如何處理膀曾?請結(jié)合runloop來談?wù)劷鉀Q方案?
    答:(提示:在異步線程中啟動一個runloop重新發(fā)送網(wǎng)絡(luò)圖片)
    (1)重新下載圖片
    (2)利用 runloop 的輸入源回到主線程刷新 UIImageView阳啥。

轉(zhuǎn)自:https://juejin.im/post/5cb745dde51d456e6479b463

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市财喳,隨后出現(xiàn)的幾起案子察迟,更是在濱河造成了極大的恐慌斩狱,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扎瓶,死亡現(xiàn)場離奇詭異所踊,居然都是意外死亡,警方通過查閱死者的電腦和手機概荷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門秕岛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人误证,你說我怎么就攤上這事继薛。” “怎么了愈捅?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵遏考,是天一觀的道長。 經(jīng)常有香客問我蓝谨,道長灌具,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任譬巫,我火速辦了婚禮咖楣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芦昔。我一直安慰自己诱贿,他們只是感情好,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布烟零。 她就那樣靜靜地躺著瘪松,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锨阿。 梳的紋絲不亂的頭發(fā)上宵睦,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音墅诡,去河邊找鬼壳嚎。 笑死,一個胖子當著我的面吹牛末早,可吹牛的內(nèi)容都是我干的烟馅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼然磷,長吁一口氣:“原來是場噩夢啊……” “哼郑趁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起姿搜,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤寡润,失蹤者是張志新(化名)和其女友劉穎捆憎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梭纹,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡躲惰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了变抽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片础拨。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绍载,靈堂內(nèi)的尸體忽然破棺而出诡宗,到底是詐尸還是另有隱情,我是刑警寧澤逛钻,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布僚焦,位于F島的核電站,受9級特大地震影響曙痘,放射性物質(zhì)發(fā)生泄漏芳悲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一边坤、第九天 我趴在偏房一處隱蔽的房頂上張望名扛。 院中可真熱鬧,春花似錦茧痒、人聲如沸肮韧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弄企。三九已至,卻和暖如春区拳,著一層夾襖步出監(jiān)牢的瞬間拘领,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工樱调, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留约素,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓笆凌,卻偏偏與公主長得像圣猎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子乞而,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355