Swift多執行緒之Operation:按優先順序載入圖片

非典型技術宅發表於2018-01-23

乖乖的,俺又來了。上一個系列寫感測器,特別慶幸自己在開篇的時候就立下了一個Flag,不然那個系列估計到現在就不知道被自己偏到什麼地方去了。眾所周知的iOS有好多感測器,配合各種感測器可以做出來各種好玩的東東。

宅胖也見過很多超牛的作品用感測器和動畫相結合,那簡直了。知識的海洋太大了,只能是驚鴻一瞥。

開了一個新系列,這個系列叫做多執行緒。在寫之前還是稍稍做了一下功課,大概看了看一些前輩們的分享帖。於是果斷的決定這個系列基本上就用Swift來寫了,因為OC的已經很多很多了。如果有特別強烈要求的同學,打算重金打賞宅胖兩塊錢要求提供OC原始碼的,我也會毫無底線的答應這個無理高瞻遠矚、英明神武的要求。

image.png

這張配圖是個什麼鬼❓對啊,多執行緒就是這樣的啊,玩死玩死玩死你。╭(╯^╰)╮哼!

其實確實是因為Swift關於多執行緒的分享太少了,OC的倒是有很多。再說了,Swift和OC是好兄弟,好基友嘛~都差不多。

計劃呢還是八篇。咳咳~對,就是八篇。上個系列也是計劃八篇,最後還是無情的扔下了藍芽,只剩下了感測器小兄弟。通過宅胖的善變說明了一個問題,需求嘛,寫出來就是用來改的。誰說只有產品經理會改需求,程式設計師也會改好嘛!有本事我們們比比!

這個系列計劃是按照下面的指令碼進行的。以下目錄隨時修改僅供參考。

第一篇:Operation 基礎

第二篇:Operation 例項

第三篇:GCD 基礎

第四篇:GCD 例項

第五篇:NSThread & pthread 的介紹

第六篇:Runloop 基礎

第七篇:Runloop 實戰

第八篇:大綜合例子

嗶嗶嗶,三十分鐘過去了。

好了,現在到了需求變更時間。第一篇、第二篇的內容進行合併,變更為:

第一篇:Operation 基本應用及優先順序小案例

第二篇:Operation 依賴關係及綜合小案例

好奇怪啊,別人都是最後才分享NSOperation,你怎麼一上來就說Operation(=@__@=)? 沒錯,就是一上來就搞這個。因為最簡單,用起來最容易啊。一上來搞一堆理論知識,很快大家就都跑了,還怎麼勾搭人啊。

然後宅胖又開啟了在寫iOS動畫系列的時候準備好的多執行緒大綱,然後....然後.....又默默的合上了。居然是OC的,居然有些地方通篇都是理論。又沒稿費,寫那麼多湊字數幹啥~!@¥#%T$!@~

來吧,擼起袖子,全新開始Operation吧。

1. 程式和執行緒

1.1 程式

  • 程式:正在執行的應用程式叫程式
  • 程式之間都是獨立的,執行在專用且受保護的記憶體空間中
  • 兩個程式之間無法通訊

通俗的理解,手機上同時開啟了兩個App。這兩個App肯定是在不同的程式中的。所以這兩個App之間是獨立的,記憶體中的資料不能互相竄來竄去,兩個App之間也沒有辦法進行通訊。

等等,你說啥?兩個App之間沒有辦法進行通訊?嗯,我說的是正常情況下。當然還是有不正常情況啊,例如使用iOS提供的極少數的幾種程式間通訊的工具。

1.2 執行緒

  • 執行緒:程式想要執行任務,必須要有執行緒,每個程式至少有一條執行緒。
  • 執行緒就是用來幹活的。
  • 程式一啟動,就會啟動程式。程式預設開啟一條執行緒。

幹活的執行緒?對啊,活太多,或者不想彼此互相等著浪費時間,當然可以找幾個人同時幹了。 例如在專案上,產品經理需求都沒有完全寫完,也不妨礙先設計一個大概的資料框架啊。例如需求沒有完全定下來,不妨礙UI童鞋提前設計啊,大不了再改嘛~ HOHO~怎麼聽上去都像是黑話。

1.3 多執行緒

  • 單核CPU同一時間,CPU只能處理1個執行緒,只有1個執行緒在執行任務。

  • 多執行緒的同時執行 : 其實是CPU在多條執行緒之間快速切換(排程任務)。

  • 如果CPU排程執行緒的速度足夠快,就造成了多執行緒同時執行的假象

  • 如果執行緒非常多,CPU會在多條執行緒之間不斷的排程任務,結果就是消耗了大量的CPU資源,效率下降:

    • 每個執行緒排程的頻率會降低
    • 執行緒的執行效率會下降

iPhone手機是幾核的? A7 : iPhone 5S , 雙核 A8: iPhone 6、iPhone 6 Plus,雙核 A9:iPhone 6S、iPhone 6S Plus,雙核 A10:iPhone 7、iPhone 7 Plus,2+2核

請問,iPhone X是幾核的?

1.4 iOS中的多執行緒

剛才說了,iOS App一旦執行,預設就會開啟一條執行緒。這條執行緒,我們通常稱作為“主執行緒

主執行緒的作用:

  1. 重新整理UI
  2. 處理UI事件,例如點選、滾動、拖拽。

如果主執行緒的操作太多、太耗時,就會造成App卡頓現象嚴重。所以,通常我們都會把耗時的操作放在子執行緒中進行,獲取到結果之後,回到主執行緒去重新整理UI。

2. Operation

來來來,我們就快進到了這裡。這個是蘋果推薦使用的一種多執行緒技術,好處是不用關心執行緒及執行緒的生命週期。

image.png

看完這個導圖,是不是確實覺得Operation簡單?屬性、方法沒有那麼多。

我們來看看基礎使用:

//    最基本的使用Operation
    private func basicOperation() {
//        第一步:建立Operation
        let op = Operation.init()
//        第二步:把要執行的程式碼放入operation中
        op.completionBlock = {
        
            print(#function,#line,Thread.current)
        }
//        第三步:建立OperationQueue
        let opQueue = OperationQueue.init()
//        第四步:把Operation加入到執行緒中
        opQueue.addOperation(op)
    }
複製程式碼

使用BlockOperation建立operatoin,並直接執行。我們們看看會在哪條執行緒執行。

//建立一個簡單的BlockOperation
private func CreatBasicBlockOperation() {
    //使用BlockOperation建立operation
    let operation = BlockOperation.init {
        //列印,看看在哪個執行緒中
        
        print(#function,#line,Thread.current)
    }
    
    //直接執行operation,看看執行在哪個執行緒中
    operation.start()
}
複製程式碼

我們來列印一下看看執行的結果:

image.png

看到了咩?這就是我們說,建立完Operation如果直接執行,就會在當前執行緒執行。也就是說,如果實在主執行緒建立並且start的,那就會在主執行緒執行;如果是在子執行緒建立並且start的,那就會在子執行緒執行。

3. Basic Demo

在這個例子裡面,需求如下:

1,在子執行緒載入每個圖片的資料

2,圖片資料下載完畢之後,顯示出來

3,開始請求資料的時候,讓指示符開始轉動

4,所有圖片下載完畢後,指示符停止轉動

3.1 執行緒最大任務數

在OperationQueue中,maxConcurrentOperationCount 這個屬性是限制同時執行的任務數. 比如,最大併發數設定成3,佇列就會保證只同時執行3個任務.從而間接的控制了執行緒的數量。

執行緒可以複用,而且線上程回收的間隙可以及時的準備執行緒保證併發性。 注意:佇列最大併發數不是執行緒數!!! 再糾結這個問題,打屁屁!

3.2 Swift中的do catch

這是什麼鬼?嗯,這個是Swift和OC不一樣的地方。Swift中出現了可選值這麼一個東西,這個不是這次的重點。

Swift 裡有四種方法來處理錯誤:

  1. 把錯誤從函式傳遞到呼叫函式的程式碼裡
  2. 使用一個 do-catch 語句來處理錯誤
  3. 把錯誤當做一個可選值來處理
  4. 斷言這個錯誤不會發生

因為Demo裡面用到了do catch,那我們們就只說do catch. 在Swift的標準try中,是要配合do catch的。

下面是do-catch語句的一般格式,如果do分句內的程式碼丟擲了一個錯誤,它就被catch分句捕獲,並判斷由哪個分句來處理此錯誤。

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
}
複製程式碼

3.3 優先順序

細心地大大們一定發現了,在思維導圖裡面出現了兩個優先順序。一個是屬於Operation 的,一個是屬於OperationQueue的。那我們們分看看看這兩個都是啥。

3.3.1 Operation中的優先順序

Operation裡面的這個叫做qualityOfService

public enum QualityOfService : Int {
    case userInteractive
    case userInitiated    
    case utility    
    case background    
    case `default`
}
複製程式碼
  • userInteractive:最高優先順序,用於使用者互動事件
  • userInitiated :次高優先順序,用於使用者需要馬上執行的事件
  • utility:普通優先順序,用於普通任務
  • background:最低優先順序,用於不重要的任務
  • default:預設優先順序,主執行緒和沒有設定優先順序的執行緒都預設為這個優先順序

3.3.2 operationQueue 裡面的優先順序

operationQueue中表示優先順序的屬性是queuePriority,表示操作在佇列中的優先順序。

public enum QueuePriority : Int {
    case veryLow
    case low
    case normal
    case high
    case veryHigh
}
複製程式碼

這些優先順序都是相對的,並不是是說必須要執行完最高的才執行次重要的。這裡面並沒有一個特別嚴格順序。只是在分配資源上有傾向性。如果佇列需要有嚴格的執行順序,還是要新增依賴關係的,這個是我們下一篇文章要分享的內容。

4. 案例實現

Operation 基本應用及優先順序小案例。 實現後效果如下:

Operation Demos.gif

還是老樣子,只說最重點的。小細節地方,大家有興趣可以去看原始碼。

這個Demo裡面,用了兩種方法建立Operation。

startBasicDemo,使用的是閉包建立Operation的方式。在startPriorityDemo裡面使用的是自定義的構造方法建立的Operation,然後把任務陣列加入到執行緒中。

基本應用:

fileprivate func startBasicDemo() {
    operationQueue.maxConcurrentOperationCount = 3
    
    
    activityIndicator.startAnimating()
    
    //        使用陣列給圖片賦值
    //        use Array set image
    for imageView in imageViews! {
        operationQueue.addOperation {
            if let url = URL(string: "https://placebeard.it/355/140") {
                do {
                    let image = UIImage(data:try Data(contentsOf: url))
                    
                    DispatchQueue.main.async {
                        imageView.image = image
                    }
                } catch {
                    print(error)
                }
            }
        }
    }
    
    
    //        global queue
    DispatchQueue.global().async {
        [weak self] in
        
        //            等待所有操作都完成了,回到主執行緒停止重新整理器。
        //            wait Until All Operations are finished, then stop animation of activity indicator
        self?.operationQueue.waitUntilAllOperationsAreFinished()
        DispatchQueue.main.async {
            
            self?.activityIndicator.stopAnimating()
        }
    }
}
複製程式碼

設定了優先順序的Demo:

fileprivate func startPriorityDemo() {
    operationQueue.maxConcurrentOperationCount = 2
    activityIndicator.startAnimating()
    
    var operations = [Operation]()
    for (index, imageView) in (imageViews?.enumerated())! {
        if let url = URL(string: "https://placebeard.it/355/140") {
            //                使用構造方法建立operation
            let operation = convenienceOperation(setImageView: imageView, withURL: url)
            
            //根據索引設定優先順序
            switch index {
            case 0:
                operation.queuePriority = .veryHigh
            case 1:
                operation.queuePriority = .high
            case 2:
                operation.queuePriority = .normal
            case 3:
                operation.queuePriority = .low
            default:
                operation.queuePriority = .veryLow
            }
            
            operations.append(operation)
        }
    }
    
    //        把任務陣列加入到執行緒中
    DispatchQueue.global().async {
        [weak self] in
        self?.operationQueue.addOperations(operations, waitUntilFinished: true)
        DispatchQueue.main.async {
            self?.activityIndicator.stopAnimating()
        }
    }
    
}
複製程式碼

最後給一下原始碼的下載地址: 終於把原始碼放在了gitHub上。有錢的大爺就點選下方打賞點賣笑錢,有力氣的就在github上給個星星✨。gitHub 麼麼噠~(~o ̄3 ̄)~ 愛你們~

相關文章