[網路應用] iOS App 網路資料下載 -Operation & Operation queue

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

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

--

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

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

▍Operation queue 方法

採用 GCD 方法下載圖片,無法取消不必要的下載
👍🏼 Operation queue 機制的好處是可以取消尚未執行的下載事項!

透過 Operation & Operation queue 可以動態從網路上把照片載入,運用 cell imageView 載入照片,每一張圖片都是 URL,把圖片透過 URL 的方式載入。

queue & Operation 的 Class 是 OperationQueue,跟 GCD 的 queue 很相似,就像 GCD 的 block 會 run 在 queue 內。

queue & Operation 還有ㄧ個類別是 Operation,Operation 內是要執行的程式,會 run 在 OperationQueue (可以放在背景裡面執行)。

▍實作: 先設定同時間最多可以下載一張照片

step1. 新增一個 queue(屬性),要使用 OperationQueue
step2. 要設定同時間執行幾個,是寫在 Init 內(可以參考下方程式碼段落註解),使用 init 時,記得要呼叫 super!
step3. 在 queue 裡面使用 operation 前,要先建立 operation class 檔案

每一個 operation 都代表要下載的那張照片,因此要建立一個 operation class 來做管理,新建立一個 swift file, 命名為 imageOperation

imageOperation 是 Operation 的子類別,是 extend (繼承)operation 是 operation 的一種,可以加到 queue 裡面

step4. 將下載照片的程式放入 Operation 的 main 方法內

下載機制:main 這個方法是定義在 Operation 副類別上

每一個作業要做什麼事情,要透過 main 方法來呼叫,將要執行的任務寫入 main(),將 do-try-catch 程式碼寫於 main() 裡面,更新位置上 cell 的照片。

▍解決步驟 4 的錯誤:

  1. 不知道要下載照片的 URL 位置
    宣告 var url : URL ,錯誤就會消失
  2. UIImage
    import Foundation,不會有 UIKit,程式碼就無法辨識 UIImage,在檔案內加上 import UIkit,錯誤就會消失
  3. 找到照片之後要更新回給 tableView,tableView 要變成屬性,才會知道要更新回哪一個 tableView
    宣告 var tableView : UITableView ,錯誤就會消失
  4. IndexPath
    屬性名稱是小寫,類別的 i 是大寫
  5. tableView、IndexPath 要加上 self
在 block 內用到的變數都要加上 self,不然會有問題

6. 加上 Init,解除 Class ImageOperation 的錯誤

var url、tableView、indexPath 三項屬性都不是 optional,因為在執行的時候都需要資料,有資料就有值,要提供 init(ImageOperation) 可以把值給這三項資料,撰寫完 init 程式碼後,要記得 call super!

▍建立 Operation 機制

每一個 Operation 要下載一筆資料圖片,就要產生一個 Operation,跟 Operation 說下載網址、更新後的位置,Main 是主要執行的方法,當下載完成後就會更新回 Main 內的 tableView,上述過程,代表機制已經建立好。

在 cellForRowAt 內建立 Operation 機制,下載的任務將透過程式內的三個參數(url, tableView, indexPath)自己執行,會一筆ㄧ筆按順序下載圖片,cell 先出現的會優先載入圖片。

▍Operation & Operation queue 方法會遇到哪些圖片下載問題?

  1. 單張顯示,會更慢的完成下載所有圖片的作業
  2. 會有上下捲動方向性顯示照片的問題(以下圖為例:順序由下至上)

採用 Operation queue 同時間只顯示下載一張照片的做法,會造成載入照片的遞延效果更明顯!

想要解決照片載入速度的問題,可以嘗試此做法:當 cell 離開使用者所在的畫面(App)範圍時,取消不必要的下載

Q:如何知道 cell 已經離開使用者的畫面?

TableView Delegate 內有方法可以做到(didenddisplaying cell)
enddisplaying cell:cell 已經離開畫面的用法

撰寫 離開畫面的 operation 方法 程式碼,self.operations 是個陣列,要找到 IndexPath 是同樣的;ImageOperation 檔案內有紀錄 IndexPath 位置的屬性,會跟 tableView 內的 IndexPath 參數做比對判斷。

可以使用 for 迴圈,尋找所有的 operation,if 判斷式如下:

override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {         
//取消離開畫面的operation
for op in self.queue.operations {
//轉型成 imageOpetation
let imageOperation = op as! ImageOperation

//如果 imageOperation&tableView 內的 indexPath 位置相同,就要取消執行
if imageOperation.indexPath.compare(indexPath) == .orderedSame {
imageOperation.cancel()
//將下載圖檔的作業清除,每次離開就刪除一次
break
}
}
}

在 operation 最前方要判斷下載作業是不是已經被取消了,如果已經取消,就要 return,下方下載就都不做了

呼叫 cancel 不是真的把下載作業取消掉,只是在 Operation 上面將 isCancelled 屬性標記成 true

isCancelled 屬性,標記成 true

Q:知道 cell 已經離開使用者的畫面,如何取得 cell 位置?

在 tableView 捲動過程,會發現捲動過程會加速,最後停下來,在停下來的這個當下,其實有個方法存在於 tableView 的 superView,稱為 UIScrollView
ScrollView 上,有非常多的 Delegate,在捲動的過程中,也可以收到。

scrollViewDidEndDecelerating

執行 scrollViewDidEndDecelerating 程式碼前,要先到 imageOperation 將 print 該行的程式碼註解,註解完成後,scrollViewDidEndDecelerating 程式碼 是在畫面捲動完成後,才會被呼叫。

當執行完成後,會看到 scrollViewDidEndDecelerating 文字訊息產生
當畫面停止移動時,留下畫面上存在的 cell 的位置,其他都不需要

在 scrollViewDidEndDecelerating 方法內,撰寫程式碼(非delegate 機制,是scrollView 機制)

.indexPathsForVisibleRows 可以知道目前畫面上有 show 出來的 cell 位置
.indexPathsForVisibleRows 是 optional,前方要使用 if let

如果畫面上的 indexPath 可以取得,要在這邊保留有在畫面上的 operation,取消那些不在畫面上的,透過 for 迴圈,loop 所有 operation,記得要將 operation 再次作轉換~
若該作業的處理位置不在畫面上,就取消下載作業

透過 for 迴圈,loop 所有 operation

注意 indexPaths 前方的驚嘆號!
將 didEndDisPalying 的方法移除,已經無作用

若希望載入速度變快,可以設定一次載入多組圖片~
畫面上的照片就會跳出的比原先設定 1 個的快

.maxConcurrentOperationCount = 2(可以自行調整數字)

▍總結: OperationQueue VS GCD

第一次做:用 GCD 方式

因為 GCD 不容易取消,實作變得比較麻煩~
GCD 也有 cancel 的做法,只是不容易做

另外 GCD 這種格式的 code 沒有辦法被 reuse,因為不是在單一的 class
若能夠單獨變成一支 class,就比較容易使用,可以在另一個程式 new operation

GCD 好處:會自動根據『系統資源』決定適合進行的作業數量,安排要下載一張還是兩張圖片

第二次做:Operation 方式(本文)

Operation 之間有所謂的相依性(dependency),會按照設定跑的順序執行
可以設定某ㄧ個作業(operation),operation 要先做完,才能再做另一個作業

可以讓作業具備順序性,而不是根據放入 queue 內的順序
具備 cancel 機制,若還沒開始下載,可以呼叫 cancel
被 cancel 的話,程式就不往下執行;若已經開始下載就無法取消

用 operation 改善 OperationViewController(當使用者捲動畫面時,載入照片的方式)
OperationQueue 底層也是用 GCD 做的,包裝過後比較好使用

--

--

陳冠名|Michelle Chen Chen
Adream4ever

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