Swift多執行緒之Operation:非同步載入CollectionView圖片

非典型技術宅發表於2019-01-29

文章現在基本上變成周更,其實是很辛苦的。/(ㄒoㄒ)/~~

其實俺也不想,俺也想學那些勤奮好學的小盆友們,麻利兒的日更。但是臣妾做不到啊,超有難度。就這篇還是在抗爭了無數拖延症之後,給自己下了死命令,心理想:“今天必須更新,必須更新!!”

就這樣,給自己立下的最後截稿日之後的三天,終於寫完了這個例子。

image.png

我們分享了Operation的一些基礎知識,實現了基礎的多執行緒案例,也知道了怎麼設定優先順序了。

今天的開餐小菜是看看怎麼設定一個依賴關係的Demo。然後就是一道相對豐盛的大菜,使用Operation在CollectionView上實現子執行緒載入圖片的案例。這個例子其實在生產中經常可以碰見。先把介面載入出來,然後再慢慢往item裡面載入圖片。

這個例子故意寫的稍微複雜了一點,也是為了多學習點新東西嘛。

敲黑板敲黑板敲黑板。知識點有:自定義Operation子類、map函式、Swift新增的元組資料型別。

下面是最終實現的CollectionView非同步載入圖片的例子效果:

collectionDemo.gif

1. Operation 設定依賴關係

高樓大廈從地起,我們就從今天餐前小點開始。先看看如何設定operation的依賴關係。

啥叫依賴關係?有啥用啊?

打個比方我們們要做一個聽音樂的付費App專案,需要經過登陸、付費、下載、播放四個步驟。其實一看就明白,這四個操作是有先後順序的,但假如所有的操作都是多執行緒,我們們怎麼控制順序?
通過設定“優先順序”?NO!優先順序沒有辦法幹這個事情。要是覺得設定優先順序可以實現的,請回去看看俺上一篇文章。Swift多執行緒之Operation:按優先順序載入圖片

我們可以通過設定依賴關係,建立起先後的順序。只有當一個 operation 所依賴的所有 operation 都執行完成時,這個 operation 才能開始執行。

並且,operation是可以跨佇列建立依賴關係的噢!operation是可以跨佇列建立依賴關係的噢!operation是可以跨佇列建立依賴關係的噢!說了三遍。

需要小小注意的是,要先將operation的依賴關係建立好之後再新增到佇列中。

我們們還是藉助上次的那個模板來看看。哎呀哎呀,不要逼我新寫模板了嗎,要講究複用。其實是懶得寫新的,懶死算了。

dependency.gif

看到沒?圖片是按照從上到下依次載入的,不再像之前亂七八糟的順序顯示的了吧。

程式碼很簡單,請看:

fileprivate func startDepencyDemo() {
   operationQueue.maxConcurrentOperationCount = 4
    self.activityIndicator.startAnimating()
    guard let url = URL(string: "https://placebeard.it/355/140") else {return }
    let op1 = convenienceOperation(setImageView: imageView1, withURL: url)
    let op2 = convenienceOperation(setImageView: imageView2, withURL: url)
    op2.addDependency(op1)
    let op3 = convenienceOperation(setImageView: imageView3, withURL: url)
    op3.addDependency(op2)
    let op4 = convenienceOperation(setImageView: imageView4, withURL: url)
    op4.addDependency(op3)
    
    DispatchQueue.global().async {
        [weak self] in
        self?.operationQueue.addOperations([op1,op2,op3,op4], waitUntilFinished: true)
        DispatchQueue.main.async {
            self?.activityIndicator.stopAnimating()
        }
    }
   
}
複製程式碼

好啦,接下來看看寫那個非同步載入CollectionView圖片怎麼搞。在這個之前,需要補充點前置知識。

2. 前置知識點內容

2.1 自定義Operation子類

Operation操作狀態.png
  • operation狀態是Finished的時候,是沒有辦法取消的。
  • operation成功、失敗、或者被取消,isFinished都會被設定為true。所以請不要依靠這個屬性來判斷是不是成功執行了。

2.1.1 需要重寫的地方

建立一個Operation的可以併發的子類可能稍微麻煩一點點。預設情況下,operation的子類是同步執行的,如果要建立一個能夠併發的子類,我們可能需要重寫一些方法。

  1. start: 所有並行的 Operations 都必須重寫這個方法,然後在想要執行的執行緒中手動呼叫這個方法。注意:任何時候都不能呼叫父類的start方法。

  2. main: 可選的。儘管我們可以在start方法中執行任務,但是使用main來設定執行任務的程式碼,可以讓operation的結構更加清晰。

  3. isExecuting: 必須的。是否執行中。,需要實現KVO通知機制。

  4. isFinished: 必須的。是否已完成。,需要實現KVO通知機制。

  5. isAsynchronous:必須的。該方法預設返回 false ,表示非併發執行。併發執行需要自定義並且返回 true

2.1.2 程式碼實現

fileprivate var _executing : Bool = false

override var isExecuting: Bool {
    get { return _executing }
    set {
        if newValue != _executing {
            willChangeValue(forKey: "isExecuting")
            _executing = newValue
            didChangeValue(forKey: "isExecuting")
        }
    }
}

fileprivate var _finished : Bool = false
override var isFinished: Bool {
    get { return _finished }
    set {
        if newValue != _finished {
            willChangeValue(forKey: "isFinished")
            _finished = newValue
            didChangeValue(forKey: "isFinished")
        }
    }
}



override var isAsynchronous: Bool {
    get {
        return true
    }
}

override func start() {
    if !isCancelled {
        isExecuting = true
        isFinished = false
        startOperation()
    } else {
        isFinished = true
    }
複製程式碼

有童鞋大概很奇怪,為什麼要定義_executing_finished

image.png

看到了嗎?只給了get方法,沒有給set方法。所以沒有辦法直接使用這個屬性。

2.1.3 取消操作的說明

operation不是說把屬性isCancelled設定一下就好了。其實這個屬性起到的作用只是一個標識,我們在寫程式碼的時候需要定期檢查isCancelled這個值,如果是ture,我們需要立即停止執行接下來的任務。

2.2 map函式

map是幹嘛的吶?先舉個例子,會更容易理解一下下哈。

看看程式碼:

let testNumberArray = [1,2,3,4,5,6,7,8,9]
print("沒有使用map之前的列印結果:(testNumberArray)")

let newArray = testNumberArray.map{$0 + 2}
print("newArray的列印結果:(newArray)")


let stringArray = testNumberArray.map { (number) -> String in
  return "No.(number.description)"
}
print("stringArray的列印結果:(stringArray)")
複製程式碼

有點懵是不是?沒關係,我們來看看列印結果是什麼:

image.png

有沒有很神奇?一個陣列,簡簡單單就變成了兩個陣列。

Swift是支援一門函數語言程式設計的語言,Map是針對集合型別的操作。map方法會遍歷呼叫者,對陣列中的每一個元素執行閉包中定義的操作。

我們們newArray執行的操作就是把testNumberArray陣列中每一個元素都加了2。

stringArray執行的操作就是把testNumberArray陣列中每一個元素變成字串,前面加上“No.”

What`s the fxxk! 厲不厲害?厲不厲害?針對集合的操作還有FlatMap,Filter,Reduce,有興趣的童鞋請自行研究哈。

2.3 Swift新增的元組資料型別

元組其實是一個複合值。簡單的而說,就是使用圓括號把多個值組合成一個複合值。元組內的值可以使用任意型別,元組並不要求元組內的值具有相同的型別。

let (day, content) = (30,"今天天氣不錯")
複製程式碼

上面這個就是最簡單的一個元組定義。

這是進階一點的。

        let week = (name : "星期一" , order : 1)
        
//        可以很快捷的取出數值
        let weekName = week.name
        let weekOrder = week.order
複製程式碼

元組不是我們們今天的重點,只是在demo裡面用到了,就要稍微提一下。

元組可以與Switch大牌進行復雜條件的判斷;可以作為方法的返回值,來返回多個數值;可以假裝成結構體使用;

3. CollectionView中圖片進行非同步載入

來看一下思維導圖:

image.png

原始碼各位可以自行下載觀看,只有Swift版本的下載

非典型技術宅胖好像說了句廢話,因為程式碼中用了Swift特有的資料格式,當然提供不了Objective-C的原始碼了。而且文章的標題就是“Swift多執行緒之Operation”。請原諒我在文章裡面加入了非典型技術宅,俺的ID。實在是有些童鞋在轉載的時候完全不署名,╮(╯▽╰)╭哎

給item賦值圖片的重點地方的程式碼:

override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    let cell = cell as! CollectionCell
    let (item, operation) = imageOps[indexPath.row]
    //        只是以防萬一,我們先停止一下操作
    operation?.cancel()
    weak var weakCell = cell
    
    let newOp = ImageLoadOperation(forItem: item) { (image) in
        DispatchQueue.main.async {
            weakCell?.imageView.image = image
        }
    }
    
    imageLoadQueu.addOperation(newOp)
    imageOps[indexPath.row] = (item, newOp)
}
複製程式碼

感謝各位收看,今天的新聞聯播到此結束。

有錢的大爺就點選下方打賞點賣笑錢,有力氣的就在github上給個星星✨。或者在評論裡面我們們聊聊天,吹吹牛也行。hiahia~

噢,預告一下。按照之前的計劃,下一篇應該是GCD基礎。O~M~G,好枯燥。

  • 最後,所有的程式碼都放在這裡了:gitHub 下載後給顆Star吧~ 麼麼噠~(~o ̄3 ̄)~ 愛你們~

相關文章