Swift-Operation使用筆記

在公司項目開發(fā)中有碰到這樣一個問題,請求圖片數(shù)據(jù)參數(shù)以POST方式提交缰猴,返回的json中帶有圖片數(shù)據(jù)格式如下:


POST方式返回圖片json.png

其中data中的數(shù)據(jù)是一個類似[-1,-40,-1,-32,0,16,74]的數(shù)組.data數(shù)據(jù)即為圖片的數(shù)據(jù)。由于圖片很多,原有邏輯是采用Alamofire一張一張圖片進行下載,現(xiàn)在有個想法就是做成多線程下載的方式嘀韧。于是想到了GCDOperation,下面來看看他們的優(yōu)缺點:

GCD

GCD有一個問題無法控制最大并發(fā)數(shù)缠捌,而且對隊列的管理也并不完善,比如我們要下載100個文件译蒂,如果同時下載的話開辟100個線程曼月,那肯定是不行的,先不說移動設(shè)備是否支持(最多70個左右)柔昼,即使支持了那這個開銷太大哑芹。雖說GCD的話可以使用信號量進行線程控制,但是每個線程的暫停啟動之類的又是問題捕透,而且畢竟是曲線救國的方法聪姿。

OperationQueue

OperationOperationQueue是基于GCD封裝的對象,作為對象可以提供更多操作選擇乙嘀,可以用方法或Block實現(xiàn)多線程任務(wù)末购,同時也可以利用繼承、類別等進行一些其他操作虎谢;但同時實現(xiàn)代碼相對復雜一些盟榴。但是他畢竟不像GCD那樣使用C語言實現(xiàn),所以效率會相比GCD低一些婴噩。但是對線程的控制的靈活性要遠高于GCD擎场,對于下載線程來說可以優(yōu)先選擇這個。

自定義Operation實現(xiàn)思路

Swift里面我們可以使用BlockOperation(InvocationOperation已不存在了)他的閉包則是需要執(zhí)行的下載任務(wù)几莽,然后我們把他添加進OperationQueue中便開始執(zhí)行了任務(wù)迅办。但是這里,我選擇自定義Operation來實現(xiàn)章蚣。我們把每一個下載任務(wù)封裝成一個Operation站欺。注意Operation不能直接使用,我們需要使用他的子類。Operation中有兩個方法镊绪,我們來了解下:

    open func start()
    open func main()

startmain匀伏。按照官方文檔所說,如果是非并發(fā)就使用main蝴韭,并發(fā)就使用start够颠。那現(xiàn)在并發(fā)和非并發(fā)已經(jīng)沒有區(qū)別了,startmain的區(qū)別在哪里呢榄鉴?
main方法的話履磨,如果main方法執(zhí)行完畢,那么整個Operation就會從隊列中被移除庆尘。如果你是一個自定義的operation并且它是某些類的代理剃诅,這些類恰好有異步方法,這是就會找不到代理導致程序出錯了驶忌。
然而start方法就算執(zhí)行完畢矛辕,它的finish屬性也不會變,因此你可以控制這個Operation的生命周期了付魔。
然后在任務(wù)完成之后手動cancel掉這個Operation即可聊品。

方式一、實現(xiàn)main()

//TKMainOperation.swift
class TKMainOperation: Operation { 
    override func main() {
        print("current thread: \(Thread.current)")
        let queue = DispatchQueue.global()
        queue.async {
            print("sleep current thread: \(Thread.current)")
            sleep(3)
            print("我睡醒了")
        }
        print("這里先執(zhí)行几苍,因為上面異步開辟線程需要消耗時間")
    }
}
//ViewController.swift
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        testMainOperation()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    
    func testMainOperation() {
        let op = TKMainOperation()
        op.completionBlock = {
            print("操作執(zhí)行完了")
        }
        op.start()
//        let queue = OperationQueue()
//        queue.addOperation(op)
        
    }

}
current thread: <NSThread: 0x60400006e100>{number = 1, name = main}
數(shù)據(jù)請求 current thread: <NSThread: 0x604000464c40>{number = 3, name = (null)}
這里先執(zhí)行翻屈,因為上面異步開辟線程需要消耗時間
操作執(zhí)行完了
我睡醒了

采用op.start()來執(zhí)行的話,main里面是在當前線程執(zhí)行的妻坝,也就是說不會重新開辟新的線程伸眶,然后采用加入到OperationQueue的方式來做的話,打印如下:

current thread: <NSThread: 0x60400046da00>{number = 3, name = (null)}
這里先執(zhí)行刽宪,因為上面異步開辟線程需要消耗時間
數(shù)據(jù)請求 current thread: <NSThread: 0x600000470640>{number = 4, name = (null)}
操作執(zhí)行完了
我睡醒了

好了到這里厘贼,不知道有沒有工友看出來有問題。在main中有異步操作的時候纠屋。并不會等待異步操作執(zhí)行完(打印我睡醒了)涂臣,才會執(zhí)行CompletionBlock.而是執(zhí)行完操作,不去管異步操作是否完成售担,就執(zhí)行完回調(diào)的block赁遗。這顯然不是我們想要的結(jié)果。我們想要的結(jié)果是等我打印睡醒了再去執(zhí)行操作的回調(diào)族铆。那么如何解決這個問題呢岩四?信號量機制。代碼如下:

//TKMainOperation.swift
class TKMainOperation: Operation {
    override func main() {
        print("current thread: \(Thread.current)")
        let queue = DispatchQueue.global()
        //創(chuàng)建一個新的信號量哥攘,參數(shù)value代表信號量資源池的初始數(shù)量剖煌。
        //    value < 0材鹦, 返回NULL
        //    value = 0, 多線程在等待某個特定線程的結(jié)束。
        //    value > 0, 資源數(shù)量耕姊,可以由多個線程使用桶唐。
        let semaphore = DispatchSemaphore(value: 0)
        
        queue.async {
            print("sleep current thread: \(Thread.current)")
            sleep(3)
            print("我睡醒了")
            // 釋放一個資源。返回值為0表示沒有線程等待這個信號量茉兰;返回值非0表示喚醒一個等待這個信號量的線程尤泽。如果線程有優(yōu)先級,則按照優(yōu)先級順序喚醒線程规脸,否則隨機選擇線程喚醒坯约。
            semaphore.signal()
            
        }
        semaphore.wait()
        print("這里先執(zhí)行,因為上面異步開辟線程需要消耗時間")
    }
}

方式二莫鸭、實現(xiàn)start()

代碼如下:

//TKStartOperation.swift
let FINISHED  = "isFinished"
let CANCELLED = "isCancelled"
let EXECUTING = "isExecuting"


protocol TKStartOperationDelegate: NSObjectProtocol {
    
    /// 下載完數(shù)據(jù)回調(diào)
    ///
    /// - Parameters:
    ///   - taskId: 記錄唯一值
    ///   - success: 是否成功
    ///   - data: 數(shù)據(jù)
    func dataDownloadFinished(_ taskId: String,success: Bool, data: Data?)
}

class TKStartOperation: Operation {
    // 標記當前Operation
    var taskId: String = ""
    
    weak var operationDelegate: TKStartOperationDelegate?
    
    /// 操作是否完成
    private var operationFinished: Bool = false {
        willSet {
            willChangeValue(forKey: FINISHED)
        }
        
        didSet {
            didChangeValue(forKey: FINISHED)
        }
    }
    
    /// 操作是否取消
    var operationCancelled: Bool = false {
        willSet {
            willChangeValue(forKey: CANCELLED)
        }
        didSet {
            didChangeValue(forKey: CANCELLED)
        }
    }
    
    /// 操作是否正在執(zhí)行
    var operationExecuting: Bool = false {
        willSet {
            willChangeValue(forKey: EXECUTING)
        }
        didSet {
            didChangeValue(forKey: EXECUTING)
        }
    }
    
    
    init(_ taskIdentifier:String) {
        taskId = taskIdentifier
    }
    
    
    override func start() {
        print("current thread: \(Thread.current)")
        if isCancelled { // 如果取消了 將狀態(tài)更改
            operationFinished = true
            operationExecuting = false
            return
        }
        operationExecuting = true
        // 開始請求
        requestData()
    }
    
    
    private func requestData() {
        let queue = DispatchQueue.global()
        queue.async {
            print("數(shù)據(jù)請求 current thread: \(Thread.current)")
            sleep(3)// 假裝網(wǎng)絡(luò)請求數(shù)據(jù)
            DispatchQueue.main.async {
                self.operationDelegate?.dataDownloadFinished(self.taskId, success: true, data: nil)
                self.finish() //標記操作完成 狀態(tài)更改
            }
            
        }
    }
    
    
    override func cancel() {
        // 當前任務(wù)未完成才執(zhí)行取消(完成了就沒必要取消了)
        guard !operationFinished else {
            return
        }
    
        // 如果有請求任務(wù) 需要在這進行取消
        
        if operationExecuting {
            operationExecuting = false
        }
        
        if !operationFinished {
            operationFinished = true
        }
        operationCancelled = true

        super.cancel()
    }
    
    
    private func finish() {
        operationFinished = true
        operationExecuting = false
    }
    
    // MARK: -  以下四個方法是必須實現(xiàn)的
    override var isExecuting: Bool {
        return operationExecuting
    }
    
    override var isFinished: Bool {
        return operationFinished
    }
    
    override var isCancelled: Bool {
        return operationCancelled
    }
    
    override var isAsynchronous: Bool {
        return true
    }    
}
//ViewController.swift
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
//        testMainOperation()
        
        testStartOperation()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    
    func testMainOperation() {
        let op = TKMainOperation()
        op.completionBlock = {
            print("操作執(zhí)行完了")
        }
//        op.start()
        let queue = OperationQueue()
        queue.addOperation(op)
        
    }
    
    
    func testStartOperation() {
        let op = TKStartOperation("1111")
        op.operationDelegate = self
        op.completionBlock = {
            print("操作執(zhí)行完了")
        }
        //        op.start()
        let queue = OperationQueue()
        queue.addOperation(op)
    }

}


// MARK: -  下載數(shù)據(jù)回調(diào)
extension ViewController: TKStartOperationDelegate {
    func dataDownloadFinished(_ taskId: String, success: Bool, data: Data?) {
        print("數(shù)據(jù)請求回來了")
    }
}
current thread: <NSThread: 0x60400027b840>{number = 3, name = (null)}
數(shù)據(jù)請求 current thread: <NSThread: 0x60400027bd00>{number = 4, name = (null)}
數(shù)據(jù)請求回來了
操作執(zhí)行完了

到這一步闹丐,基本上基礎(chǔ)部分已經(jīng)講完了。
接下來需要將請求塞進去被因。這塊有用到Alamofire進行請求卿拴,應(yīng)該是有緩存的原因,導致下載下來的圖片數(shù)據(jù)(一般有4-5M)會導致內(nèi)存暴增(測試過有兩百張圖氏身,目測會開辟500-600M的內(nèi)存巍棱,而一張一張采用Alamofire下載的話只有20-50M左右)。后來采用的是URLSession來進行處理(并發(fā)數(shù)3,內(nèi)存消耗在100M左右)蛋欣,此處還有待研究。不知道有沒有工友知道更好的解決方案如贷。歡迎賜教陷虎,不甚感謝!

參考:
NSOperation中start與main的區(qū)別
AlamoFire的使用(下載隊列杠袱,斷點續(xù)傳)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尚猿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子楣富,更是在濱河造成了極大的恐慌凿掂,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纹蝴,死亡現(xiàn)場離奇詭異庄萎,居然都是意外死亡,警方通過查閱死者的電腦和手機塘安,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門糠涛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兼犯,你說我怎么就攤上這事忍捡〖” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵砸脊,是天一觀的道長具篇。 經(jīng)常有香客問我,道長凌埂,這世上最難降的妖魔是什么驱显? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮侨舆,結(jié)果婚禮上秒紧,老公的妹妹穿的比我還像新娘。我一直安慰自己挨下,他們只是感情好熔恢,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著臭笆,像睡著了一般叙淌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上愁铺,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天鹰霍,我揣著相機與錄音,去河邊找鬼茵乱。 笑死茂洒,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的瓶竭。 我是一名探鬼主播督勺,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼斤贰!你這毒婦竟也來了智哀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤荧恍,失蹤者是張志新(化名)和其女友劉穎瓷叫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體送巡,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡摹菠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了授艰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辨嗽。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖淮腾,靈堂內(nèi)的尸體忽然破棺而出糟需,到底是詐尸還是另有隱情屉佳,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布洲押,位于F島的核電站武花,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏杈帐。R本人自食惡果不足惜体箕,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挑童。 院中可真熱鬧累铅,春花似錦、人聲如沸站叼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尽楔。三九已至投储,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阔馋,已是汗流浹背玛荞。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呕寝,地道東北人勋眯。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像下梢,于是被迫代替她去往敵國和親凡恍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容