iOS開發之異步回調的有趣玩法

? ? ? ? 最近在開發過程中總結了一些關于異步回調比較有意思的用法,給大家分享一下。不是什么高級東西,iOS老司機們看完可以給小弟指點指點,但是也不適合新手看,如果你還不太熟悉OC中的block或者Swift中的逃逸閉包,那么這篇分享你看著可能會很迷糊。文中代碼部分使用Swift,OC可以照搬,思路是一樣的。

? ? ? ? 在iOS開發中,block或者閉包(后面我就統稱為閉包了,畢竟我已經投入了Swift的懷抱)可以說無處不在,在Swift中我們使用閉包可以用來做數組排序類似的功能,把元素大小比較的實現通過閉包的形式拋出來。但更多時候,我們用閉包來實現異步回調,因此我們經常要跟逃逸閉包打交道。


? ? ? ? 舉一個很簡單的例子,圖片下載,大家可能最熟悉的框架就是SDWebImageDownloader,大家經常會在tableViewCell里去異步下載圖片,下載完成后在回調里把圖片顯示出來。

獲取圖片的簡易實現


Cell的簡易實現

? ? ? ? 這個過程看似很簡單,無非就是先找本地有沒有圖片,有就直接回調,沒有就創建一個http請求去下載,下載完成后回調,當然你就這樣去實現,沒有錯,而且應用能正常運行,大部分時候也不會有什么問題。但是我們來細想一下這個過程到底有沒有毛病。

? ? ? ? 獲取圖片的回調,可能是同步的(本地有,直接回調),也可能是異步的(本地沒有,要去下載),同步的當然是不會出任何問題的,但是異步的就不一定了。

? ? ? ? 既然圖片本地沒有要去下載,那么就有可能有好幾個cell需要的圖片下載地址一樣,或者cell被重用后又下載一次,不做任何處理的話,那么同一張圖片可能我們得下載好幾次,這樣就浪費流量浪費空間了。不過好在像SD這樣的第三方庫,已經為你做了防止重復下載的事情,只要你下載的地址相同,就只會下載一次,并且不會漏掉你的任何一個回調,你可以放心的使用。

? ? ? ? 但是,我們還可以做得更極致,你有沒有考慮過,網絡不好的時候,一張圖片要下半分鐘的情況?當用戶打開你的應用,這么多的圖片在轉圈,半天都停不下來,是不是難免會暴躁,然后明明網速就很慢下圖要下半天還非要手癢癢去上下滑一下你的tabletView,滑一下還不解氣,來回滑個幾十下,差點沒把手機給摔了。

? ? ? ? 這個時候tableViewCell的重用機制就把你帶坑里去了,當你把一個cell滑出屏幕范圍再滑回屏幕范圍的時候,cell又去執行了一次獲取圖片的方法,圖片還是沒下載完,于是乎這個cell又創建了一個回調,當你來回滑了幾十下后,一個cell可能就創建了幾十個回調等待下載圖片完成后好執行。

? ? ? 然后突然一下你的網速恢復了,那些個圖片一下子就下好了,cell的幾十次回調就能執行了,你不覺得很浪費性能嗎?我一個cell明明就顯示一張圖,就因為網速慢加上我手癢滑來滑去的就得執行幾十次繪制圖片。而且你怎么知道你哪張圖片先下完?除非下載圖片用的串行隊列,不然很可能你的cell最終顯示的圖片不是它應該顯示的圖片。當然你可以在回調的參數中多加一個url,回調的時候判斷一下當前需要顯示的url和回調中的url是不是相等,如果不相等說明這個回調是cell重用之前創建的,直接忽略掉就行了。這種方法也可以解決問題,但是我還想看看閉包可以怎么玩,從新手開始接手項目,踩過太多異步回調的坑了,下面來說說我最近想到的新的解決辦法(其實我也還是個萌新),放心,絕對簡單到輕描淡寫。


? ? ? ? 第一步,我們要解決異步任務重復的問題,有人要問了,SD已經有這個機制了啊,廢話,我用SD的庫我第二步怎么實現,而且自己實現一下也是有好處的嘛,畢竟這個真的不難啊。

稍稍改變下代碼,代碼不好截圖,分兩部分截。

第一部分
第二部分

? ? ? ? 如果圖片本地不存在,那么就需要下載,下載完成后就需要異步回調,我們可以把url保存在一個集合里表示這個url地址下的圖片正在下載,把回調已可變數組的形式保存在一個字典里,以url作為key。每當又有一次下載該url的任務,就可以不用再下載一次,只需要把回調添加到該url對應的回調數組中就行了。當下載完成時,根據url找到對應的回調數組遍歷執行一次就可以了。是不是很簡單,對啊,就是這個簡單啊,沒有什么高大上的技術。


? ? ? ? 第二步,我們要解決cell重用的問題,也就是回調重復多余的問題,這個問題理解起來有點困難,需要你對block或者逃逸閉包很熟悉。

? ? ? ? 很多時候我們只知道調用一個函數,定義好回調需要執行什么任務,就什么都不管了,等著函數去調用我定義好的回調任務就行了,很少去關注閉包在函數本身中是怎么執行的,包括閉包的生命周期是怎么樣的。

? ? ? ? 對于Swift來說,涉及到異步執行的閉包必須聲明為逃逸閉包。普通閉包只能同步執行,他們的生命周期不一樣(OC中的block不做這樣的區分,可以統一看做逃逸閉包)。普通閉包的生命周期隨著函數結束而結束,也就是說你沒法寫一個異步任務去執行普通閉包,也沒法把普通閉包保存到其他地方去想什么時候執行就什么時候執行,說形象點,普通閉包像是一個被剝奪了自由的奴隸,它沒法從函數的生命周期中逃逸出來。而逃逸閉包就像是一個上流社會的達官貴人,想去哪里就去哪里,只有當沒有任何一個地方需要他的時候,他的生命周期才結束,所以你可以把逃逸閉包保存起來看情況使用。

? ? ? ? 逃逸閉包有一個缺點就是,如果我把它保存在一個數組中,我就再也不能精確地定位到它,更無法準確地刪除它。它像是一個對象又不完全是一個對象,它天生的值拷貝屬性加上無法進行哈希匹配,導致我們無法在數組中對其進行定位,最簡單的辦法就是把逃逸閉包封裝在一個類中,通過對象去定位逃逸閉包。

? ? ? ? 回到剛剛那個問題,當cell消失后等待被重用時,怎么把它創建的逃逸閉包給釋放掉?在第一步中,我把逃逸閉包以可變數組的形式保存在字典中,上面我們也分析了,我們沒法在數組中去定位一個逃逸閉包把它刪掉,因此,我們需要定義一個類,把逃逸閉包封裝起來,再存在數組里,這樣我們就可以釋放指定的逃逸閉包了。最終的代碼就是下面這個樣子。

第一部分
第二部分



? ? ? ? 總結,很多語言都支持閉包這種表達式,但是OC或者Swift的閉包應該是目前所有語言的閉包實現中最好用的,你可以在閉包中隨意操作上下文變量,而不需要去擔心上下文變量的生命周期,你只需要注意循環引用的問題就行了,而很多其他語言的閉包是不能捕獲上下文變量的。閉包在異步任務中使用非常廣泛,你可以把閉包保存起來,這感覺就像我把一串代碼實現給保存了起來,待異步任務完成時看情況去調用或者釋放閉包,如果你需要定位某一個保存起來的閉包,你需要把閉包封裝在類中使用。

? ? ? ? 第一次寫東西,瞎寫的,有錯誤的地方望指正。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 序 本文主要解析一下apache common pools下的GenericObjectPool的參數設置 Gen...
    go4it閱讀 5,052評論 0 0
  • 為了向The ABCs of Death致敬,我打算寫26個小故事,故事與字母無關,僅作計數。 ---------...
    九大人閱讀 249評論 0 0
  • 它長勢開始歪的時候 身旁的楊柳樹恰好開出了初春的第一朵小花 它的脖子探入春江水暖的時候 身邊的櫻桃樹恰好悄悄落下第...
    卻悔閱讀 617評論 2 0
  • 阿福童---最開始接觸這個名詞是在2015年3月的某一天。李校長通知我們十幾個骨干班主任調出兩天的課程做一個關于阿...
    蕾蕾lcm閱讀 351評論 0 0