[網路應用] iOS App 網路資料下載 -非同步機制 GCD & GCD Cancel 方法

資策會 APP Developer 行動開發工程師養成班

陳冠名|Michelle Chen Chen
Adream4ever
8 min readJan 5, 2022

--

▍非同步機制(Async)GCD 方法

GCD 下載概念:

透過 global queue 丟到背景執行緒,再透過 main queue 執行畫面上面相關的程式碼;利用背景執行緒,在 try Data(contentsOf)下載,資料處理好、畫面建立好之後,透過主要執行緒加到畫面內

⚠️ 注意: 一但開始下載作業就停不了,因為只有ㄧ行程式碼,呼叫即下載

▍舉例:臉書動態照片的下載情境

臉書的個人動態發表文章功能內的照片是使用者上傳的,照片並不會在 Xcode 內,開發者無法預期使用者上傳的照片尺寸大小,那麼應該如何讓照片正確顯示&符合使用者預期呢?

▍cell 動態處理照片 — 同步機制(Sync)

模擬器的每一張圖片都是用 Array 來撰寫,每一個 Array 內都是 Dictionary,最外層是一個陣列,需要知道圖片以及對應的名稱、URL

預設情況下,會是同步機制,透過主要執行緒下載圖片,但這個做法會造成使用者操作的畫面停頓、卡住的情況;當下載的照片數量愈多的時候,捲動清單時,畫面卡住的情況會愈久。

程式內處理下載的時候都要特別留意,在 cellFroRowAt 這些方法內,都是主要執行緒在執行,不能讓主要執行緒去下載大量的圖片,若用同步機制在下載圖片,會變得非常慢,甚至卡住。

建議改用非同步機制,讓背景執行緒處理下載作業。

▍實作:cell 動態處理照片 — 非同步機制(Async)

檢視 cellForRowAt 程式碼段落,使用 GCD 方式下載網路圖片

首先,先檢視 cellForRowAt 程式碼段落,這邊是主要處理照片的文字部分,URL 可以得知文字要下載圖片的網址位置,使用 GCD 方式(背景執行緒)來做,直接下載 Data — 照片。

▍非同步機制(Async)GCD 方法會遇到哪些圖片下載問題?

cell 圖片未正確顯示,捲動後才陸續出現圖片

Q1:模擬器 tableview 內的 cell 圖片未正確顯示?為什麼第一次進入 NSOperationQueue 都沒有圖片?當捲動後才會陸續出現圖片?

cellForRowAt 這段藍色區域下載圖片的程式碼的機制:不曉得圖片何時會完成下載動作,所以先假設圖片都尚未下載,照片還不存在。

系統模擬器的 cell 特性:一開始若沒有指定圖片,放在 tableView 上面的時候,cell 是不會保留照片的位置的,照片的寬、高都是 0,也看不到照片。

Q1:改善作法

當照片放入時,必須呼叫一個方法;讓 cell 可以重新做一次 autolayout,把照片空間空出來,加入 cell.setNeedsLayout() 這行程式碼,讓 cell 重新計算 autolayout,發現 Imageview 裡面有 image,就會預留空間!

加入 cell.setNeedsLayout() 這行程式碼,預留空間,圖檔正確顯示!

Q2:模擬器 tableview 內的 cell 圖片狀態會跳(文字對應的照片不是對的,之後才顯示正確圖檔)?

第一張 Apple Store 的照片顯示錯誤,沒有正確對應到對的照片

Q2:改善作法

圖片跳動的現象,是因為 cell 有 reuse 機制,才會看到舊的 cell 上面的照片

cell 設計原理:為了要減少程式設計師建立新物件,透過 reuse 機制,當 cell 離開畫面時,會進入 que,讓 cell 可以反覆不斷被利用。

當下方的 cell 出來時,會從 que 裡面拉一個出來,將照片先塞入 cell,等到 async 非同步 程式段落(藍色區域)圖檔下載完成時,才會將舊的 cell 上面的照片替換成正確文字應該顯示的照片。

因為 cell 有 reuse 機制 ,在每一次顯示照片時,先將原本 cell 內的照片清空,清空後再下載,就不會看到舊的照片殘留在 cell 內

Q3:模擬器 tableview 內的 cell 圖片經過上下捲動數次後,圖片會進行多次更換?

因為 cell ㄧ直被 reuse,當捲動來回多次,cell 位置就一直變更,同個 cell 就會經過多個位置,停在每一個位置時,都會觸發下載照片的程式指令,將照片更新回給該 cell,因此 cell 就會執行好幾次照片下載的動作,當照片下載完成後,就會一格一格的把原本顯示的照片替換掉!

經由多次捲動,可以觀察第一張 Apple Store 圖片被多次更改

Q3:改善作法

程式需要另外再做判斷機制,不靠 cell 做更新,改以位置(indexPath)做更新;但也不能把 cell reuse 機制關掉喔! 下載照片時,以位置(indexPath)做更新,得知照片對應到的位置,就能做出對的更新。

好處是:使用者的畫面只需要停在某ㄧ範圍,就可以看到該區域的正確畫面

透過 IndexPath 位置更新取得 cell

Q:如何判斷下載的照片是不是正確顯示在使用者看到的畫面上,文字對應到的是正確圖片?

TableView 有個方法是 cellForRow(at: IndexPath),可以告訴 TableView cellForRow(at: IndexPath) ㄧ個位置,透過位置取得 cell。

  1. 如果位置目前在畫面上,會回傳 cell 物件
  2. 如果指定的位置不在畫面上,會回傳 optional 空值
透過 cell 位置更新照片

將程式碼中的 cell 改成 cell1(只需要管位置正確性),當找到 cell 時,就更新 cell 上面的照片。

Q4:模擬器 tableview 內的 cell 載入速度很慢的原因?

因為上下捲動的次數越多,累積要下載的照片數量就會增加,除了使用者需要用到的畫面區域,畫面以外的 cell 仍需要等待照片下載,因此部分使用者會覺得畫面顯示速度很慢。

▍總結:非同步機制(Async)GCD 方法

這個機制是不 work 的!問題太多了!

GCD 方法不適合用於 cell 動態處理照片的情境

會吃掉使用者的頻寬、佔據 CPU 的時間
舉例:已經捲過的畫面就不要下載照片了~

建議用其他機制、可以取消的方法來做,避免形成系統資源的浪費!
GCD 作業方式,比較適合處理單一照片出現的情境。

▍非同步機制(Async)GCD Cancel 下載的方法

GCD 可以支援 Cancel ,但不容易執行,要在 GCD 做 cancel ,要把 code 寫成上方圖片。

因為 GCD 無法改寫 Class,無法加入屬性,需要自己另外寫 Dictionary,對應 item 是下載哪個位置,需要另外的資料結構去維護,才會知道哪些位置需要取消。

GCD 呼叫是用 DispatchQueue 呼叫 Async,把下載作業放在後方的 block execute: workItem,會把下載照片的程式,放入 download image

可以把這段程式碼單獨出來,變成是一個物件 DispatchWorkItem 呼叫 cancel

但是 workItem 無法輕易得知下載哪一個位置的照片,如果是 operation 就可以得知下載哪個位置的照片,要取消(如下方圖片)的時候也很容易。

GCD 很重要,但是不適合用在 cell 上面顯示照片~!
適合用在 cell 上面顯示照片的方法: operation & queue
如果照片是單一顯示:就可以使用 GCD,相對 operation & queue 是比較簡單的做法

--

--

陳冠名|Michelle Chen Chen
Adream4ever

Brave Together App 產品負責人,現職物聯網科技領域的 iOS Developer,歡迎交流~!