本文摘自:http://www.ithao123.cn/content-8680777.html
[摘要:當(dāng)前市道上的APP讯泣,凡是有觸及到視頻阵漏、期刊预侯、或別的大型文件傳輸、掃瞄等用處的甲雅,增加下載或緩存至當(dāng)?shù)氐墓πб悦饩W(wǎng)速的限定及依附解孙,毫無(wú)疑問(wèn)皆將給用戶(hù)帶去更好的體驗(yàn)。而道到下]
當(dāng)前市面上的APP务荆,凡有涉及到視頻、期刊穷遂、或其它大型文件傳輸函匕、瀏覽等用途的,添加下載或緩存至本地的功能以避免網(wǎng)速的限制及依賴(lài)蚪黑,毫無(wú)疑問(wèn)都將給用戶(hù)帶來(lái)更好的體驗(yàn)盅惜。而談到下載技術(shù),就又不得不牽扯到了斷點(diǎn)續(xù)傳忌穿,隊(duì)列任務(wù)等老生常談的問(wèn)題抒寂。這不,本人當(dāng)前的項(xiàng)目掠剑,就恰好遇到了這樣的需求屈芜。然而在經(jīng)過(guò)大量調(diào)研之后,本人竟無(wú)法找到一篇總結(jié)得很好的文檔,對(duì)此進(jìn)行全面的介紹井佑;能夠?qū)さ降囊恍┗钴S度并不高的開(kāi)源項(xiàng)目属铁,卻又不能恰如其分并抱之以信賴(lài)滿(mǎn)足項(xiàng)目的需求。所以仔細(xì)斟酌后躬翁,不得不選擇自己動(dòng)手焦蘑,豐衣足食。鉆研的過(guò)程中遇到了不少坑盒发、不少困難例嘱,有些個(gè)別的地方真是不用不知道,一用才知道是如此得蹩腳宁舰,難怪很少有人對(duì)此進(jìn)行系統(tǒng)完整的介紹∑绰眩現(xiàn)將本人在實(shí)現(xiàn)下載模塊時(shí)所用到的技術(shù)總結(jié)如下,相信本文中所蘊(yùn)涵的干貨一定不會(huì)令費(fèi)心閱讀的你感到失望明吩!話(huà)休絮煩间学。首先,說(shuō)下載就離不開(kāi)網(wǎng)絡(luò)請(qǐng)求印荔。而當(dāng)今iOS開(kāi)發(fā)技術(shù)當(dāng)中低葫,最廣泛使用的網(wǎng)絡(luò)請(qǐng)求框架無(wú)疑要屬AFNetworking。經(jīng)過(guò)對(duì)其進(jìn)行簡(jiǎn)單研究后仍律,你就會(huì)尋到最適合用來(lái)完成下載這件“小事”的組件嘿悬,叫做AFHTTPRequestOperation
現(xiàn)假定我們的需求是最常見(jiàn),也是最能體現(xiàn)技術(shù)問(wèn)題的一個(gè)水泉,叫做:下載隊(duì)列在某一時(shí)刻善涨,最多僅能有一個(gè)下載任務(wù)處于正在下載的狀態(tài)中!-- 敘述的節(jié)奏似乎稍稍快了些
那就先來(lái)看下實(shí)現(xiàn)隊(duì)列下載草则、斷點(diǎn)續(xù)傳等需求的關(guān)鍵示例代碼吧!
NSError * error = nil;
// 創(chuàng)建下載隊(duì)列
NSOperationQueue * downloadOperationQueue = [[NSOperationQueue alloc]init];
// 規(guī)定operationQueue中钢拧,最大可以同時(shí)執(zhí)行的operation數(shù)量為1
downloadOperationQueue.maxConcurrentOperationCount = 1;
// 創(chuàng)建單個(gè)下載任務(wù)(訪問(wèn)已下載部分的文件,實(shí)現(xiàn)斷點(diǎn)續(xù)傳)
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");
}];
// 將單個(gè)下載任務(wù)加入到下載隊(duì)列當(dāng)中
[downloadOperationQueue addOperation:downloadOperation];
// 暫停某下載任務(wù)
[downloadOperation pause];
// 繼續(xù)某下載任務(wù)
[downloadOperation resume];
// 取消某下載任務(wù)(同時(shí)應(yīng)將其已下載部分的文件刪除)
[downloadOperation cancel];
[[NSFileManager defaultManager] removeItemAtPath:DOWNLOADED_PART_FILE_PATH error:&error];
// 取消全部下載任務(wù)
[downloadOperationQueue cancelAllOperations];
// 此外還有若干方法用以判斷相應(yīng)一見(jiàn)其名便知其義的狀態(tài)...
downloadOperation.isReady
downloadOperation.isExecuting
downloadOperation.isPaused
downloadOperation.isCancelled
downloadOperation.isFinished
// 判斷downloadOperation是否存在在downloadOperationQueue當(dāng)中
[downloadOperationQueue.operations containsObject:downloadOperation]
以上代碼創(chuàng)建了一個(gè)AFHTTPRequestOperation對(duì)象作為單個(gè)下載任務(wù)炕横,并將其加入到NSOperationQueue中源内。仿照上例,我們可以創(chuàng)建多個(gè)AFHTTPRequestOperation對(duì)象并加入到NSOperationQueue中份殿,即形成了下載隊(duì)列
做到這里膜钓,你是不是認(rèn)為已經(jīng)沒(méi)有神馬技術(shù)問(wèn)題啦?只要把operation一個(gè)個(gè)地添加到queue里卿嘲,下載任務(wù)就可以一個(gè)接一個(gè)地自動(dòng)執(zhí)行了颂斜!而如果我們將上一個(gè)operation暫停、取消拾枣,或是它自然地下載完成了沃疮,又或是它下載中途失敗了盒让,下一operation就會(huì)聰明地自動(dòng)啟動(dòng),繼續(xù)其下載任務(wù)了7薨酢E幢颉?錯(cuò)4兴A冒恰!吨些!
接下來(lái)筆者將要告訴你的搓谆,就是本文最最核心的干貨,絕對(duì)顛覆你的想象:朗泉手!只要你親手動(dòng)手試一試,就會(huì)發(fā)現(xiàn)如下大跌眼球的驚恐現(xiàn)象E计鳌斩萌!
驚人事實(shí) 1:對(duì)queue中前一個(gè)下載operation執(zhí)行pause方法,下一個(gè)operation并不能自動(dòng)啟動(dòng)進(jìn)入正在執(zhí)行的狀態(tài)F梁洹颊郎!
驚人事實(shí) 2:如果queue中前一個(gè)下載operation執(zhí)行失敗了(可用下載中途斷網(wǎng)進(jìn)行模擬),它將從queue中自動(dòng)地被移除掉v纭姆吭!
驚人事實(shí) 3:注意到代碼里那個(gè)failure回調(diào)的block了沒(méi)?它不僅僅將在operation執(zhí)行失敗的時(shí)候被調(diào)用唁盏,還會(huì)在operation被cancel的時(shí)候被調(diào)用D诶辍!所以對(duì)于神馬叫做“operation的失敗”厘擂,你要重新建立起你的世界觀了@サ!
驚人事實(shí) 4:如果對(duì)一個(gè)正處于pause狀態(tài)的operation執(zhí)行cancel會(huì)怎么樣刽严?答案是這個(gè)operation還保留在queue中0毫椤!并且仍然保持著pause狀態(tài)8圩倔既!僅有的一點(diǎn)變化恕曲,是它的isCancelled屬性鹏氧,變成了YES!佩谣!
......未完待續(xù)把还,本文要令你感到驚詫的,還有很多
有木有感到AFHTTPRequestOperation和NSOperationQueue是個(gè)多么坑爹的東東?為何就不能像我們想象中一樣用得舒爽吊履?
原因就在于AFHTTPRequestOperation的父類(lèi)NSOperation安皱,在設(shè)計(jì)之處就不是為了下載的操作而生的!人家開(kāi)始就僅僅是用來(lái)處理多線程的巴а住W靡痢!所以造成了AFNetworking在擴(kuò)展這個(gè)類(lèi)的時(shí)候缀踪,可用的資源居砖、接口等等就非常少。對(duì)于什么下載任務(wù)暫停/繼續(xù)驴娃,下載中途失敗等等情況奏候,很多問(wèn)題幾乎就是沒(méi)有辦法理想地解決的,只好用NSOperation中僅有的幾種狀態(tài)予以并不貼切的表示唇敞。于是乎就出現(xiàn)了上表中種種詭異的情況
補(bǔ)充幾點(diǎn)干貨蔗草。然后告訴你一個(gè)本文之前偷偷誤導(dǎo)了你的大坑!疆柔!
驚人事實(shí) 5:如果一個(gè)queue中有一個(gè)下載operation正在執(zhí)行咒精,此時(shí)對(duì)另一處在isReady狀態(tài)的operation執(zhí)行start方法會(huì)怎么樣?你很可能會(huì)說(shuō):“沒(méi)用的婆硬,因?yàn)橹霸O(shè)了queue.maxConcurrentOperationCount = 1嘛狠轻!” 可事實(shí)恰好相反,這個(gè)operation也會(huì)立刻被啟動(dòng)執(zhí)行1蚍浮向楼!于是乎你不忍心看到的事情就出現(xiàn)了,這時(shí)queue將會(huì)有兩個(gè)任務(wù)被同時(shí)執(zhí)行P城湖蜕!maxConcurrentOperationCount完全失效了!宋列!
驚人事實(shí) 6:承接上一點(diǎn)昭抒,如果此時(shí)另一條的狀態(tài)不是isReady,而是isPaused暫停狀態(tài)炼杖,你對(duì)其執(zhí)行resume方法灭返,此時(shí)會(huì)怎么樣呢?哈哈坤邪,沒(méi)錯(cuò)熙含,你吸取了上一條的經(jīng)驗(yàn),終于猜對(duì)了艇纺!這個(gè)operation也會(huì)立刻啟動(dòng)被執(zhí)行怎静,不管當(dāng)前的queue有沒(méi)有另一個(gè)operation正在被執(zhí)行S实!從中我們就可以意識(shí)到蚓聘,maxConcurrentOperationCount這個(gè)屬性腌乡,只能管得自動(dòng)啟動(dòng)每一operation時(shí),先檢查下是否正在執(zhí)行的operation的數(shù)量已經(jīng)超過(guò)那個(gè)數(shù)字了夜牡;可是如果你要手動(dòng)start某一operation与纽,對(duì)不起,這條限制半點(diǎn)都沒(méi)有用處了......
驚人事實(shí) 7:從上表中我們可以看到塘装,無(wú)論是一個(gè)operation自然地執(zhí)行完畢渣锦,還是中途失敗,還是被執(zhí)行了cancel方法氢哮,都會(huì)被標(biāo)記為isFinished袋毙,從operation中被移除掉,operation所認(rèn)為的“完成”可完全不像我們想象中的那么狹義冗尤!問(wèn)題來(lái)了听盖,此時(shí)如果再對(duì)這個(gè)operation執(zhí)行start方法會(huì)怎么樣?對(duì)不起裂七!沒(méi)有任何用處皆看!:sob:所以你如果想要讓一個(gè)已失敗的operation從斷點(diǎn)處繼續(xù)再開(kāi)始執(zhí)行下載該怎么辦?不好意思背零,只好新建operation重新再來(lái)了......頭痛腰吟、抓狂得很啊a闫俊毛雇!本人剛開(kāi)始實(shí)現(xiàn)下載模塊相關(guān)需求的時(shí)候,就被這些問(wèn)題坑了個(gè)體無(wú)完膚侦镇。最后得出了本文最大的關(guān)鍵結(jié)論灵疮,也就是前面所說(shuō)的“大坑”:
不能夠使用NSOperationQueue來(lái)進(jìn)行多下載任務(wù)的管理!?欠薄震捣!
理由如下:
你無(wú)法妥善地實(shí)現(xiàn)“隊(duì)列中最多僅能有一個(gè)下載任務(wù)正在進(jìn)行”這條產(chǎn)品經(jīng)理臆測(cè)會(huì)讓開(kāi)發(fā)變簡(jiǎn)單的需求!闹炉!比方說(shuō)蒿赢,你讓NSOperationQueue中一個(gè)operation暫停后,下一個(gè)任務(wù)并不會(huì)自動(dòng)啟動(dòng)霸ァ羡棵!有人說(shuō)可以手動(dòng)去start下一個(gè)operation,如果這個(gè)姑且算做可以接受昵观,可是問(wèn)題又來(lái)了:我們沒(méi)有辦法手動(dòng)將一個(gè)operation置為isReady狀態(tài)傲狼弧!啊犬!處于isReady狀態(tài)的operation灼擂,要么是還未加入queue,要么是加入了還未輪到執(zhí)行觉至,但是它只要一執(zhí)行剔应,就再也回不到isReady的狀態(tài)了!那我們要讓暫停的operation恢復(fù)到等待下載狀態(tài)該怎么搞语御?此時(shí)可能還有另一operation正在執(zhí)行熬!应闯!反之筆者搞了半天纤控,是無(wú)能為力了
下載是需要一定時(shí)間的過(guò)程,需要不停地向服務(wù)器進(jìn)行請(qǐng)求碉纺,那么就永遠(yuǎn)避免不了因?yàn)榫W(wǎng)絡(luò)等原因中途會(huì)失敗的問(wèn)題船万。可要命的是骨田,一旦下載失敗耿导,operation就會(huì)毫不妥協(xié)地從queue中被移除掉啊L汀舱呻!你能在這時(shí)候讓你的下載任務(wù)從UI界面上消失掉嗎?顯然大BOSS是不會(huì)允許你這么干的悠汽。有人說(shuō)可以重建operation再加入到queue中箱吕,可那樣你只能將operation插到隊(duì)尾,列表順序就被打亂了笆脸濉V呈稀!你去瞧瞧看姻采,operationQueue.operations雅采,那可只是一個(gè)只讀屬性啊?住婚瓜!
......自己去體會(huì)吧,反正坑多的已經(jīng)無(wú)力吐槽刑棵,再堅(jiān)持下去也是枉費(fèi)心思了巴刻。
不幸的事情來(lái)了。筆者最后只得放棄NSOperationQueue蛉签,使用古老原始的工具--NSMutableArray來(lái)進(jìn)行多下載任務(wù)的管理胡陪。這樣
的話(huà)所有operation的啟動(dòng)沥寥、移除等操作都必須依靠手動(dòng)來(lái)執(zhí)行。這個(gè)辦法雖然辦法土了些柠座,可是起碼對(duì)于每個(gè)operation的控制權(quán)又重新回到了
我們手里邑雅。有得必有失嘛!當(dāng)能恰當(dāng)?shù)貙?shí)現(xiàn)了項(xiàng)目需求的時(shí)候妈经,這點(diǎn)犧牲也就算不上神馬了
在使用AFHTTPRequestOperation時(shí)我們還需要注意以下幾點(diǎn):
對(duì)isReady狀態(tài)的operation執(zhí)行resume淮野、pause、cancel等方法是沒(méi)有任何用處的吹泡,所以為了確保執(zhí)行正確骤星,在對(duì)operation執(zhí)行resume、pause爆哑、cancel前洞难,都要首先執(zhí)行[operation start]。(對(duì)已經(jīng)start過(guò)的operation執(zhí)行start不會(huì)造成任何影響)
對(duì)處于isPaused的operation執(zhí)行cancel方法是無(wú)法得到正確結(jié)果的揭朝,所以每次執(zhí)行cancel方法前廊营,都要先執(zhí)行一下[operation resume]。 (同樣對(duì)于正處于isExecuting狀態(tài)的operation來(lái)說(shuō)萝勤,執(zhí)行resume方法也是不會(huì)造成任何影響的)
對(duì)于下載模塊這個(gè)糾結(jié)之處來(lái)說(shuō)露筒,本地持久化下載記錄的相關(guān)數(shù)據(jù)也是必不可少的,理由如下:
AFHTTPRequestOperation敌卓、NSMutableArray這些都是運(yùn)行時(shí)的東西慎式,一關(guān)掉app,這些東西自然也都消失得無(wú)影無(wú)蹤了趟径。我們能讓下載記錄就此消失得無(wú)影無(wú)蹤么瘪吏?NO!顯然是不能接受的
我們下載得到的那個(gè)文件蜗巧,可能是已下載完成的掌眠,可能是只下載了部分的;而只下載了部分這種的幕屹,又可能是下載中途暫停了的蓝丙,失敗的,被取消的等等情況望拖。請(qǐng)問(wèn)單憑這個(gè)文件如何判斷它是屬于哪種情況渺尘?而且這還不夠,有些下載任務(wù)根本可能就還未生成相應(yīng)的下載文件说敏,app就已經(jīng)被關(guān)了芭父!你能就把這種的下載任務(wù)扔掉嗎?顯然是絕不可以的
不使用operationQueue我們同樣無(wú)法手動(dòng)將operation標(biāo)記為隊(duì)列等待的isReady狀態(tài)医咨,怎么辦枫匾?只有將operation設(shè)定為paused,然后相應(yīng)的數(shù)據(jù)記錄標(biāo)記為isReady狀態(tài)好了(本人使用的是CoreData進(jìn)行本地持久化存儲(chǔ))
......用operation外的數(shù)據(jù)模型記錄下載任務(wù)的狀態(tài)好處還有很多拟淮,但同時(shí)帶來(lái)的同步更新問(wèn)題也有很多干茉,具體就留給大家自己去體會(huì)了!