[網路應用] iOS App 網路資料下載 -Operation & Operation queue
資策會 APP Developer 行動開發工程師養成班
▍舉例:臉書動態照片的下載情境
臉書的個人動態發表文章功能內的照片是使用者上傳的,照片並不會在 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 的錯誤:
- 不知道要下載照片的 URL 位置
宣告 var url : URL ,錯誤就會消失 - UIImage
import Foundation,不會有 UIKit,程式碼就無法辨識 UIImage,在檔案內加上 import UIkit,錯誤就會消失 - 找到照片之後要更新回給 tableView,tableView 要變成屬性,才會知道要更新回哪一個 tableView
宣告 var tableView : UITableView ,錯誤就會消失 - IndexPath
屬性名稱是小寫,類別的 i 是大寫 - tableView、IndexPath 要加上 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 方法會遇到哪些圖片下載問題?
- 單張顯示,會更慢的完成下載所有圖片的作業
- 會有上下捲動方向性顯示照片的問題(以下圖為例:順序由下至上)
採用 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
Q:知道 cell 已經離開使用者的畫面,如何取得 cell 位置?
在 tableView 捲動過程,會發現捲動過程會加速,最後停下來,在停下來的這個當下,其實有個方法存在於 tableView 的 superView,稱為 UIScrollView
ScrollView 上,有非常多的 Delegate,在捲動的過程中,也可以收到。
執行 scrollViewDidEndDecelerating 程式碼前,要先到 imageOperation 將 print 該行的程式碼註解,註解完成後,scrollViewDidEndDecelerating 程式碼 是在畫面捲動完成後,才會被呼叫。
當執行完成後,會看到 scrollViewDidEndDecelerating 文字訊息產生
當畫面停止移動時,留下畫面上存在的 cell 的位置,其他都不需要
在 scrollViewDidEndDecelerating 方法內,撰寫程式碼(非delegate 機制,是scrollView 機制)
.indexPathsForVisibleRows 可以知道目前畫面上有 show 出來的 cell 位置
.indexPathsForVisibleRows 是 optional,前方要使用 if let
如果畫面上的 indexPath 可以取得,要在這邊保留有在畫面上的 operation,取消那些不在畫面上的,透過 for 迴圈,loop 所有 operation,記得要將 operation 再次作轉換~
若該作業的處理位置不在畫面上,就取消下載作業
注意 indexPaths 前方的驚嘆號!
將 didEndDisPalying 的方法移除,已經無作用
若希望載入速度變快,可以設定一次載入多組圖片~
畫面上的照片就會跳出的比原先設定 1 個的快
▍總結: 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 做的,包裝過後比較好使用