1. 程式人生 > >基於下載的各種坑,面試絕對是寶典

基於下載的各種坑,面試絕對是寶典

講真的我覺得這篇文章寫得真好,很多人都對下載暈頭轉向

導語

  • 當前市面上的APP,凡有涉及到視訊、期刊、或其它大型檔案傳輸、瀏覽等用途的,新增下載或快取至本地的功能以避免網速的限制及依賴,毫無疑問都將給使用者帶來更好的體驗。而談到下載技術,就又不得不牽扯到了斷點續傳,佇列任務等老生常談的問題。這不,本人當前的專案,就恰好遇到了這樣的需求。然而在經過大量調研之後,本人竟無法找到一篇總結得很好的文件,對此進行全面的介紹;能夠尋到的一些活躍度並不高的開源專案,卻又不能恰如其分並抱之以信賴滿足專案的需求。所以仔細斟酌後,不得不選擇自己動手,豐衣足食。鑽研的過程中遇到了不少坑、不少困難,有些個別的地方真是不用不知道,一用才知道是如此得蹩腳,難怪很少有人對此進行系統完整的介紹。現將本人在實現下載模組時所用到的技術總結如下,相信本文中所蘊涵的乾貨一定不會令費心閱讀的你感到失望

  • 話休絮煩。首先,說下載就離不開網路請求。而當今iOS開發技術當中,最廣泛使用的網路請求框架無疑要屬AFNetworking。經過對其進行簡單研究後,你就會尋到最適合用來完成下載這件“小事”的元件,叫做AFHTTPRequestOperation

現假定我們的需求是最常見,也是最能體現技術問題的一個,叫做:

  • 下載佇列在某一時刻,最多僅能有一個下載任務處於正在下載的狀態中!
-- 敘述的節奏似乎稍稍快了些

那就先來看下實現佇列下載、斷點續傳等需求的關鍵示例程式碼吧!

    NSError * error = nil;

    // 建立下載佇列
    NSOperationQueue * downloadOperationQueue = [[NSOperationQueue alloc]init];
    //  規定operationQueue中,最大可以同時執行的operation數量為1
    downloadOperationQueue.maxConcurrentOperationCount = 1;

    // 建立單個下載任務(訪問已下載部分的檔案,實現斷點續傳)
    NSMutableURLRequest * downloadRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:DOWNLOAD_URL_STRING]];
    [[NSURLCache sharedURLCache] removeCachedResponseForRequest:downloadRequest];

    AFHTTPRequestOperation * downloadOperation = [[AFHTTPRequestOperation alloc]initWithRequest:downloadRequest];

    unsigned long long downloadedPartFileSize = 0;

    if ([[NSFileManager defaultManager] fileExistsAtPath:DOWNLOADED_PART_FILE_PATH]) {

        NSDictionary * fileAttributes = [[NSFileManager defaultManager]attributesOfItemAtPath:DOWNLOADED_PART_FILE_PATH error:&error];
        downloadedPartFileSize = [fileAttributes fileSize];
        NSString * headerRangeFieldValue = [NSString stringWithFormat:@"bytes=%llu-", downloadedPartFileSize];
        [downloadRequest setValue:headerRangeFieldValue forHTTPHeaderField:@"Range"];
    }

    downloadOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:DOWNLOADED_PART_FILE_PATH append:YES];

    [downloadOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
        NSLog(@"%lld/%lld", totalBytesRead + downloadedPartFileSize, totalBytesExpectedToRead + downloadedPartFileSize);
    }];

    [downloadOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"downloadOperation completion block invoked");
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"downloadOperation failure block invoked");
    }];

    //  將單個下載任務加入到下載隊列當中
    [downloadOperationQueue addOperation:downloadOperation];

    //  暫停某下載任務
    [downloadOperation pause];

    //  繼續某下載任務
    [downloadOperation resume];

    //  取消某下載任務(同時應將其已下載部分的檔案刪除)
    [downloadOperation cancel];
    [[NSFileManager defaultManager] removeItemAtPath:DOWNLOADED_PART_FILE_PATH error:&error];

    //  取消全部下載任務
    [downloadOperationQueue cancelAllOperations];

    //  此外還有若干方法用以判斷相應一見其名便知其義的狀態...
    downloadOperation.isReady
    downloadOperation.isExecuting
    downloadOperation.isPaused
    downloadOperation.isCancelled
    downloadOperation.isFinished

    //  判斷downloadOperation是否存在在downloadOperationQueue當中
    [downloadOperationQueue.operations containsObject:downloadOperation]

  • 以上程式碼建立了一個AFHTTPRequestOperation物件作為單個下載任務,並將其加入到NSOperationQueue中。仿照上例,我們可以建立多個AFHTTPRequestOperation物件並加入到NSOperationQueue中,即形成了下載佇列
  • 做到這裡,你是不是認為已經沒有神馬技術問題啦?只要把operation一個個地新增到queue裡, 下載任務就可以一個接一個地自動執行了!而如果我們將上一個operation暫停、取消,或是它自然地下載完成了,又或是它下載中途失敗了,下一 operation就會聰明地自動啟動,繼續其下載任務了!!?
  • 錯!!!!

    • 接下來筆者將要告訴你的,就是本文最最核心的乾貨,絕對顛覆你的想象!!
    • 只要你親手動手試一試,就會發現如下大跌眼球的驚恐現象!!

    • 驚人事實 1: 對queue中前一個下載operation執行pause方法,下一個operation並不能自動啟動進入正在執行的狀態!!

    • 驚人事實 2: 如果queue中前一個下載operation執行失敗了(可用下載中途斷網進行模擬),它將從queue中自動地被移除掉!!

    • 驚人事實 3: 注意到程式碼裡那個failure回撥的block了沒?它不僅僅將在operation執行失敗的時候被呼叫,還會在operation被cancel的時候被呼叫!!所以對於神馬叫做“operation的失敗”,你要重新建立起你的世界觀了!!

    • 驚人事實 4: 如果對一個正處於pause狀態的operation執行cancel會怎麼樣?答案是這個operation還保留在queue中!!並且仍然保持著pause狀態!!僅有的一點變化,是它的isCancelled屬性,變成了YES!!

    • ......未完待續,本文要令你感到驚詫的,還有很多

    Index Description isReady isExecuting isPaused isCancelled isFinished in Queue success block invoked failure block invoked 與預期不相符的事實 可能的解決方案
    1 加入queue前 YES
    2 加入queue後 YES YES
    3 自然結束後 YES YES
    4 正在下載時執行pause方法後 YES YES
    5 正在下載時執行cancel方法後 YES YES YES 被標記finished;fail block被呼叫 不能僅憑fail block被呼叫判定某operation執行失敗;必須同時判定isCancelled才是真正的失敗,否則只是被人為地取消了
    6 正在暫停時執行resume方法後 YES YES
    7 正在暫停時執行cancel方法後 YES YES YES Paused狀態未被取消;未能從queue中被移除 如想將暫停時的operation取消並從operationQueue中清除掉,必須首先執行[downloadOperation resume]後,再執行[downloadOperation cancel]
    8 中途意外失敗後 YES YES 被標記為finished;從queue中被移除 必須用另外的方式記錄執行中途意外失敗的downloadOperation以實現中途失敗的下載任務斷點續傳
    9 等待上一任務結束時 YES YES
    10 上一任務中途意外失敗後 YES YES
    11 上一任務正在執行時被暫停後 YES YES 上一operation被正在執行時被暫停後不能自動使下一operation開始執行 前一個operation被暫停後,必須手動啟動下一operation
    12 上一任務正在執行時被取消後 YES YES
    13 上一任務正在暫停時被取消後 YES YES 上一operation被正在暫停時被取消後不能自動使下一operation開始執行 前一個operation被暫停後,必須手動啟動下一operation
    14 佇列被執行cancelAllOperations YES YES YES 被標記finished;fail block被呼叫 不能僅憑fail block被呼叫判定某operation執行失敗;必須同時判定 isCancelled才是真正的失敗,否則只是被人為地取消了

    • 有木有感到AFHTTPRequestOperationNSOperationQueue是個多麼坑爹的東東?為何就不能像我們想象中一樣用得舒爽?

    • 原因就在於AFHTTPRequestOperation的父類NSOperation,在設計之處就不是為了下載的操作而生的!人家開始就僅僅是用來處理多執行緒的啊!!所以造成了AFNetworking在擴充套件這個類的時候,可用的資源、介面等等就非常少。對於什麼下載任務暫停/繼續,下載中途失敗等等情況,很多問題幾乎就是沒有辦法理想地解決的,只好用NSOperation中僅有的幾種狀態予以並不貼切的表示。於是乎就出現了上表中種種詭異的情況

    補充幾點乾貨。然後告訴你一個本文之前偷偷誤導了你的大坑!!

    • 驚人事實 5:如果一個queue中有一個下載operation正在執行,此時對另一處在isReady狀態的operation執行start方法會怎麼樣?你很可能會說:“沒用的,因為之前設了queue.maxConcurrentOperationCount = 1嘛!” 可事實恰好相反,這個operation也會立刻被啟動執行!!於是乎你不忍心看到的事情就出現了,這時queue將會有兩個任務被同時執行!!maxConcurrentOperationCount完全失效了!!

    • 驚人事實 6:承接上一點,如果此時另一條的狀態不是isReady,而是isPaused暫停狀態,你對其執行resume方法,此時會怎麼樣呢?哈哈,沒錯,你吸取了上一條的經驗,終於猜對了!這個operation也會立刻啟動被執行,不管當前的queue有沒有另一個operation正在被執行!!從中我們就可以意識到,maxConcurrentOperationCount這個屬性,只能管得自動啟動每一operation時,先檢查下是否正在執行的 operation的數量已經超過那個數字了;可是如果你要手動start某一operation,對不起,這條限制半點都沒有用處了......

    • 驚人事實 7:從上表中我們可以看到,無論是一個operation自然地執行完畢,還是中途失敗,還是被執行了cancel方法,都會被標記為 isFinished,從operation中被移除掉,operation所認為的“完成”可完全不像我們想象中的那麼狹義!問題來了,此時如果再對這個operation執行start方法會怎麼樣?對不起!沒有任何用處!:sob:所以你如果想要讓一個已失敗的operation從斷點處繼續再開始執行下載該怎麼辦?不好意思,只好新建operation重新再來了......

    基於實驗我們又可以得出了這樣的一張流程圖:
    • Flow
    • 頭痛、抓狂得很啊!!本人剛開始實現下載模組相關需求的時候,就被這些問題坑了個體無完膚。最後得出了本文最大的關鍵結論,也就是前面所說的“大坑”:

    不能夠使用NSOperationQueue來進行多下載任務的管理!!!

    理由如下:
    1. 你無法妥善地實現“佇列中最多僅能有一個下載任務正在進行”這條產品經理臆測會讓開發變簡單的需求!!比方說,你讓 NSOperationQueue中一個operation暫停後,下一個任務並不會自動啟動啊!有人說可以手動去start下一個operation,如果這個姑且算做可以接受,可是問題又來了:我們沒有辦法手動將一個operation置為isReady狀態啊!!處於isReady狀態的 operation,要麼是還未加入queue,要麼是加入了還未輪到執行,但是它只要一執行,就再也回不到isReady的狀態了!那我們要讓暫停的 operation恢復到等待下載狀態該怎麼搞?此時可能還有另一operation正在執行啊!!反之筆者搞了半天,是無能為力了

    2. 下載是需要一定時間的過程,需要不停地向伺服器進行請求,那麼就永遠避免不了因為網路等原因中途會失敗的問題。可要命的是,一旦下載失敗,operation就會毫不妥協地從queue中被移除掉啊!!你能在這時候讓你的下載任務從UI介面上消失掉嗎?顯然大BOSS是不會允許你這麼幹的。有人說可以重建operation再加入到queue中,可那樣你只能將operation插到隊尾,列表順序就被打亂了啊!!你去瞧瞧看,operationQueue.operations,那可只是一個只讀屬性啊!!

    3. ......自己去體會吧,反正坑多的已經無力吐槽,再堅持下去也是枉費心思了。

    • 不幸的事情來了。筆者最後只得放棄NSOperationQueue,使用古老原始的工具--NSMutableArray來進行多下載任務的管理。這樣的話所有operation的啟動、移除等操作都必須依靠手動來執行。這個辦法雖然辦法土了些,可是起碼對於每個operation的控制權又重新回到了我們手裡。有得必有失嘛!當能恰當地實現了專案需求的時候,這點犧牲也就算不上神馬了
    在使用AFHTTPRequestOperation時我們還需要注意以下幾點:
    1. 對isReady狀態的operation執行resume、pause、cancel等方法是沒有任何用處的,所以為了確保執行正確,在對 operation執行resume、pause、cancel前,都要首先執行[operation start]。(對已經start過的operation執行start不會造成任何影響)
    2. 對處於isPaused的operation執行cancel方法是無法得到正確結果的,所以每次執行cancel方法前,都要先執行一下 [operation resume]。 (同樣對於正處於isExecuting狀態的operation來說,執行resume方法也是不會造成任何影響的)
    3. 對於下載模組這個糾結之處來說,本地持久化下載記錄的相關資料也是必不可少的,理由如下:
        a.  AFHTTPRequestOperation、NSMutableArray這些都是執行時的東西,一關掉app,這些東西自然也都消失得無影無蹤了。我們能讓下載記錄就此消失得無影無蹤麼?NO!顯然是不能接受的  
        b.  我們下載得到的那個檔案,可能是已下載完成的,可能是隻下載了部分的;而只下載了部分這種的,又可能是下載中途暫停了的,失敗的,被取消的等等情況。請問單憑這個檔案如何判斷它是屬於哪種情況?而且這還不夠,有些下載任務根本可能就還未生成相應的下載檔案,app就已經被關了啊!你能就把這種的下載任務扔掉嗎?顯然是絕不可以的
        c.  不使用operationQueue我們同樣無法手動將operation標記為佇列等待的isReady狀態,怎麼辦?只有將operation設定為paused,然後相應的資料記錄標記為isReady狀態好了(本人使用的是CoreData進行本地持久化儲存)
        d.  ......用operation外的資料模型記錄下載任務的狀態好處還有很多,但同時帶來的同步更新問題也有很多,具體就留給大家自己去體會了!

    • 以上就是本人總結下載模組實現時需要注意到的種種內容。當然各位大神如果有更好的方案提出,比如用本人掌握得還不夠好的stream如何實現上述需求,本人也願虛心聽取以將此處完善得更好。歡迎直言批評與不吝賜教!!

相關推薦

基於下載各種面試絕對

講真的我覺得這篇文章寫得真好,很多人都對下載暈頭轉向 導語 當前市面上的APP,凡有涉及到視訊、期刊、或其它大型檔案傳輸、瀏覽等用途的,新增下載或快取至本地的功能以避免網速的限制及依賴,毫無疑問都將給使用者帶來更好的體驗。而談到下載技術,就又不得不牽扯到了斷點續傳,

深度學習500問面試必備

隨著深度學習在計算機視覺、自然語言處理、語音處理的應用越來越廣泛,由於人才的供不應求,薪資水平也遠 遠高於其他行業,相對於其他行業要求也要高一些。 深度學習500問,是以一問一答的形式,內容也是非常的豐富,涵蓋了數學知識、機器學習、深度學習、計算機 視覺、自然語言處理等,非常適合面試前複習的資料。

Linux面試試題你能打多少分?大神勿進

linux面試試題linux面試寶典(1)一、選擇題1. Linux系統中DNS服務進程名為 ( )A.named B.httpd C.ftpd D.SysLog2.在UINX/Linux中,系統Root用戶口令信息一半保存的文件夾是( )A.autobat B.service.conf C.in

《PHP程式設計師面試筆試》——如果面試問題曾經遇見過是否要告知面試官?

如何巧妙地回答面試官的問題? 本文摘自《PHP程式設計師面試筆試寶典》 面試中,大多數題目都不是憑空想象出來的,而是有章可循,只要求職者肯花時間,耐得住寂寞,複習得當,基本上在面試前都會見過相同的或者類似的問題(當然,很多知名企業每年都會推陳出新,這些題目是很難完全複習到位的)。所以,在面試中,求職者曾經

FFMpeg 3.2移植到android遇到的各種再一次總結

故事很長,慢慢看! 首先我又一個需求,一張jpg加上gif後,變成一個gif動圖。如果你用命令ffmpeg -i  test.jpg -vf 'movie=test.gif[wm];[in][wm]overlay=0:0[out]' out.gif命令的話,我假設你的ff

C#呼叫C++ DLL的完整方法(解決了各種Win7下測試可用)

        由於C#直接訪問USB裝置的能力較弱,而C++在這方面則強大許多。因此,考慮通過C++實現讀寫USB裝置,C#呼叫該DLL介面的方式。這個過程中,上網查了一些資料,但是自己動手,仍然會出現這樣或者那樣的問題,因此,記錄下大體步驟,以便後續他人或者自己可以參考

Mysql-Proxy 讀寫分離的各種特別是複製延遲時

延遲問題讀寫分離不能迴避的問題之一就是延遲,可以考慮Google提供的SemiSyncReplicationDesign補丁。埠問題MySQL-Proxy預設使用的是4040埠,如果你想透明的把3306埠的請求轉發給4040的話,那麼可以:iptables -t nat -I

關於h5 webapp 離線打包定位地圖的各種爬的好辛苦

最近開發一個webapp 由於安全需要,採用離線打包,在使用hbuilder開發的時候,用到定位功能和地圖功能,在使用真機測試的時候 ,一切都是正常的,也去申請了百度地圖的key,當然也可以自己設定自己的簽名:檢視簽名sha1採用下面的方式:由於在h5中我們可以採用很多種方式

《PHP程式設計師面試筆試》——如何巧妙地回答面試官的問題?

如何巧妙地回答面試官的問題? 本文摘自《PHP程式設計師面試筆試寶典》 所謂“來者不善,善者不來”,程式設計師面試中,求職者不可避免地需要回答面試官各種“刁鑽”、犀利的問題,回答面試官的問題千萬不能簡單地回答“是”或者“不是”,而應該具體分析“是”或者“不是”的理由。 回答面試官的問題是一門很深的學問。

《PHP程式設計師面試筆試》——如何回答演算法設計問題?

  如何巧妙地回答面試官的問題?   本文摘自《PHP程式設計師面試筆試寶典》   程式設計師面試中的很多演算法設計問題,都是歷年來各家企業的“炒現飯”,不管求職者以前對演算法知識掌握得是否紮實,理解得是否深入,只要面試前買本《程式設計師面試筆試寶典》,應付此類題目完全

《PHP程式設計師面試筆試》——如何回答快速估算類問題?

如何巧妙地回答面試官的問題? 本文摘自《PHP程式設計師面試筆試寶典》 有些大企業的面試官,總喜歡出一些快速估算類問題,對他們而言,這些問題只是手段,不是目的,能夠得到一個滿意的結果固然是他們所需要的,但更重要的是通過這些題目可以考查求職者的快速反應能力以及邏輯思維能力。由於求職者平時準備的時候可能對此類

《PHP程式設計師面試筆試》——如何回答非技術性問題?

如何巧妙地回答面試官的問題? 本文摘自《PHP程式設計師面試筆試寶典》 評價一個人的能力,除了專業能力,還有一些非專業能力,如智力、溝通能力和反應能力等,所以在IT企業招聘過程的筆試、面試環節中,並非所有的內容都是C/C++/Java、資料結構與演算法及作業系統等專業知識,也包括其他一些非技術類的知識,如

《PHP程式設計師面試筆試》——如何回答技術性的問題?

如何巧妙地回答面試官的問題? 本文摘自《PHP程式設計師面試筆試寶典》 程式設計師面試中,面試官會經常詢問一些技術性的問題,有的問題可能比較簡單,都是歷年的面試、筆試真題,求職者在平時的複習中會經常遇到。但有的題目可能比較難,來源於Google、Microsoft等大企業的題庫或是企業自己為了招聘需要設計

《PHP程序員面試筆試》——如何回答技術性的問題?

規模 語句 相關 變量命名 可擴展 思考 解答 框架 以及 如何巧妙地回答面試官的問題? 本文摘自《PHP程序員面試筆試寶典》 程序員面試中,面試官會經常詢問一些技術性的問題,有的問題可能比較簡單,都是歷年的面試、筆試真題,求職者在平時的復習中會經常遇到。但有的題目可能比較

《PHP程序員面試筆試》——如何回答系統設計題?

node 可能 namo 結構 效率 增加 更多 水平 海量數據 如何巧妙地回答面試官的問題? 本文摘自《PHP程序員面試筆試寶典》 應屆生在面試時,偶爾也會遇到一些系統設計題,而這些題目往往只是測試求職者的知識面,或者測試求職者對系統架構方面的了解,一般不會涉及具體的編碼

《PHP程序員面試筆試》——如何回答快速估算類問題?

重要 tro 求職者 php 例如 求職 交易 合計 過去 如何巧妙地回答面試官的問題? 本文摘自《PHP程序員面試筆試寶典》 有些大企業的面試官,總喜歡出一些快速估算類問題,對他們而言,這些問題只是手段,不是目的,能夠得到一個滿意的結果固然是他們所需要的,但更重要的是通過

《PHP程序員面試筆試》——如何回答算法設計問題?

知識點 進行 bsp 還需要 lsp 數據 深度 生成 遞歸法 如何巧妙地回答面試官的問題? 本文摘自《PHP程序員面試筆試寶典》 程序員面試中的很多算法設計問題,都是歷年來各家企業的“炒現飯”,不管求職者以前對算法知識掌握得是否紮實,理解得是否深入,只要面試前

《PHP程序員面試筆試》——如何巧妙地回答面試官的問題?

參與 strong 如何 討論 最好 程序員 談一談 喜歡 enter 如何巧妙地回答面試官的問題? 本文摘自《PHP程序員面試筆試寶典》 所謂“來者不善,善者不來”,程序員面試中,求職者不可避免地需要回答面試官各種“刁鉆”、犀利的問題,回答面試官的問題千萬不能簡單地回答“

《PHP程式設計師面試筆試》——如何應對面試官的“激將法”語言?

如何巧妙地回答面試官的問題? 本文摘自《PHP程式設計師面試筆試寶典》 “激將法”是面試官用以淘汰求職者的一種慣用方法,它是指面試官採用懷疑、尖銳或咄咄逼人的交流方式來對求職者進行提問的方法。例如,“我覺得你比較缺乏工作經驗”“我們需要活潑開朗的人,你恐怕不合適”“你的教育背景與我們的需求不太適合”“你的

《PHP程式設計師面試筆試》——如何應對自己不會回答的問題?

如何巧妙地回答面試官的問題? 本文摘自《PHP程式設計師面試筆試寶典》 在面試的過程中,對面試官提出的問題求職者並不是都能回答出來,計算機技術博大精深,很少有人能對計算機技術的各個分支學科瞭如指掌。而且拋開技術層面的問題,在面試那種緊張的環境中,回答不上來的情況也容易出現。面試過程中遇到自己不會回答的問題