[網路應用] iOS App 網路資料下載 -非同步機制 GCD & GCD Cancel 方法
資策會 APP Developer 行動開發工程師養成班
▍非同步機制(Async)GCD 方法
GCD 下載概念:
透過 global queue 丟到背景執行緒,再透過 main queue 執行畫面上面相關的程式碼;利用背景執行緒,在 try Data(contentsOf)下載,資料處理好、畫面建立好之後,透過主要執行緒加到畫面內
⚠️ 注意: 一但開始下載作業就停不了,因為只有ㄧ行程式碼,呼叫即下載
▍舉例:臉書動態照片的下載情境
臉書的個人動態發表文章功能內的照片是使用者上傳的,照片並不會在 Xcode 內,開發者無法預期使用者上傳的照片尺寸大小,那麼應該如何讓照片正確顯示&符合使用者預期呢?
▍cell 動態處理照片 — 同步機制(Sync)
模擬器的每一張圖片都是用 Array 來撰寫,每一個 Array 內都是 Dictionary,最外層是一個陣列,需要知道圖片以及對應的名稱、URL
預設情況下,會是同步機制,透過主要執行緒下載圖片,但這個做法會造成使用者操作的畫面停頓、卡住的情況;當下載的照片數量愈多的時候,捲動清單時,畫面卡住的情況會愈久。
程式內處理下載的時候都要特別留意,在 cellFroRowAt 這些方法內,都是主要執行緒在執行,不能讓主要執行緒去下載大量的圖片,若用同步機制在下載圖片,會變得非常慢,甚至卡住。
建議改用非同步機制,讓背景執行緒處理下載作業。
▍實作:cell 動態處理照片 — 非同步機制(Async)
首先,先檢視 cellForRowAt 程式碼段落,這邊是主要處理照片的文字部分,URL 可以得知文字要下載圖片的網址位置,使用 GCD 方式(背景執行緒)來做,直接下載 Data — 照片。
▍非同步機制(Async)GCD 方法會遇到哪些圖片下載問題?
Q1:模擬器 tableview 內的 cell 圖片未正確顯示?為什麼第一次進入 NSOperationQueue 都沒有圖片?當捲動後才會陸續出現圖片?
cellForRowAt 這段藍色區域下載圖片的程式碼的機制:不曉得圖片何時會完成下載動作,所以先假設圖片都尚未下載,照片還不存在。
系統模擬器的 cell 特性:一開始若沒有指定圖片,放在 tableView 上面的時候,cell 是不會保留照片的位置的,照片的寬、高都是 0,也看不到照片。
Q1:改善作法
當照片放入時,必須呼叫一個方法;讓 cell 可以重新做一次 autolayout,把照片空間空出來,加入 cell.setNeedsLayout() 這行程式碼,讓 cell 重新計算 autolayout,發現 Imageview 裡面有 image,就會預留空間!
cell.setNeedsLayout()
Q2:模擬器 tableview 內的 cell 圖片狀態會跳(文字對應的照片不是對的,之後才顯示正確圖檔)?
Q2:改善作法
圖片跳動的現象,是因為 cell 有 reuse 機制,才會看到舊的 cell 上面的照片
cell 設計原理:為了要減少程式設計師建立新物件,透過 reuse 機制,當 cell 離開畫面時,會進入 que,讓 cell 可以反覆不斷被利用。
當下方的 cell 出來時,會從 que 裡面拉一個出來,將照片先塞入 cell,等到 async 非同步 程式段落(藍色區域)圖檔下載完成時,才會將舊的 cell 上面的照片替換成正確文字應該顯示的照片。
因為 cell 有 reuse 機制 ,在每一次顯示照片時,先將原本 cell 內的照片清空,清空後再下載,就不會看到舊的照片殘留在 cell 內
cell.imageView?.image = nil
Q3:模擬器 tableview 內的 cell 圖片經過上下捲動數次後,圖片會進行多次更換?
因為 cell ㄧ直被 reuse,當捲動來回多次,cell 位置就一直變更,同個 cell 就會經過多個位置,停在每一個位置時,都會觸發下載照片的程式指令,將照片更新回給該 cell,因此 cell 就會執行好幾次照片下載的動作,當照片下載完成後,就會一格一格的把原本顯示的照片替換掉!
Q3:改善作法
程式需要另外再做判斷機制,不靠 cell 做更新,改以位置(indexPath)做更新;但也不能把 cell reuse 機制關掉喔! 下載照片時,以位置(indexPath)做更新,得知照片對應到的位置,就能做出對的更新。
好處是:使用者的畫面只需要停在某ㄧ範圍,就可以看到該區域的正確畫面
Q:如何判斷下載的照片是不是正確顯示在使用者看到的畫面上,文字對應到的是正確圖片?
TableView 有個方法是 cellForRow(at: IndexPath),可以告訴 TableView cellForRow(at: IndexPath) ㄧ個位置,透過位置取得 cell。
- 如果位置目前在畫面上,會回傳 cell 物件
- 如果指定的位置不在畫面上,會回傳 optional 空值
將程式碼中的 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 是比較簡單的做法