這一部分主要研究AFN的上傳和下載功能,中間涉及到各類NSURLSessionTask的一些建立的解析和HTTPSessionManager對RESTful風格的web應用的支持,同時會穿插一點NSURLSession代理方法被調用的時機和對上傳的數據的序列化的步驟。
本文主要講解的是上傳和下載的代碼實現細節,不會考慮上傳過程當中的安全性問題。php
文件的上傳和下載同時也包括普通的數據請求說說到底都是使用了系統的NSURLSession類建立對應的Task,而後執行,爲了更好得理解,咱們先理清一下NSURLSessionTask類以及它的子類、NSURLSessionTaskDelegate協議和它的子協議之間的關係,以及各類代理方法調用的時機。
先看一張圖:html
其中的調用是指,task在resume以後會調用的Session對應的代理方法聲明在的協議,例如:
當執行一個NSURLSessionDataTask類型的任務resume以後,負責建立它的session將會調用在NSURLSessionDataDelegate中定義的幾個方法:web
- URLSession: dataTask: didReceiveResponse: completionHandler: - URLSession: dataTask: didBecomeDownloadTask: - URLSession: dataTask: didBecomeStreamTask: - URLSession: dataTask: didReceiveData: - URLSession: dataTask: willCacheResponse: completionHandler:
因爲NSURLSessionDataDelegate協議遵照了NSURLSessionTaskDelegate和NSURLSessionDelegate,因此也會調用這樣幾個方法:數組
// 在NSURLSessionDelegate中聲明的 - URLSession: didBecomeInvalidWithError: - URLSession: didReceiveChallenge: completionHandler: - URLSessionDidFinishEventsForBackgroundURLSession: // 在NSURLSessionTaskDelegate中聲明的 - URLSession: task: willPerformHTTPRedirection: newRequest: completionHandler: - URLSession: task: didReceiveChallenge: completionHandler: - URLSession: task: needNewBodyStream: - URLSession: task: didSendBodyData: totalBytesSent: totalBytesExpectedToSend: - URLSession: task: didCompleteWithError:
實際上你沒法經過session來建立NSURLSessionTask,只能建立它的子類來使用,iOS並無提供能夠直接建立它的方法:
1.不可能經過alloc init建立 ,由於建立以後 沒法給request屬性(readonly)賦值,網絡請求沒法進行。
2.NSURLSession、NSURLSession(NSURLSessionAsynchronousConvenience) 沒有提供直接建立的方法。
或許apple原本就打算將這個類設計爲抽象類,而只能使用繼承它的類。
而只要是使用了session類進行建立任何一個dataTask、uploadTask或者是downloadTask就會調用在NSURLSessionDelegate中和NSURLSessionTaskDelegate中聲明的代理方法,這些代理方法大都是進行網絡請求的配置,少部分設計到數據處理,而子協議NSURLSessionDataDelegate、NSURLSessionDownloadDelegate
和NSURLSessionSteamDelegate都是具體的數據處理方法。瀏覽器
通過一些簡單的測試:看看一些方法的調用順序,使用dataTask進行一個普通的網絡請求:
若是使用的是GET請求,或者使用的是POST請求、可是HTTPBody沒有數據,主要調用兩個代理方法:安全
- URLSession: dataTask: didReceiveData: // 當服務端有數據返回時調用,沒有數據返回則不調用 - URLSession: task: didCompleteWithError: // 在請求完成以後必調用
若是是POST請求,而且HTTPBody中帶有數據,那麼主要調用如下幾個方法(實際上無論建立的任務是dataTask或是uploadTask都是這樣,畢竟uploadTask是繼承自dataTask的):bash
- URLSession: task: didSendBodyData: totalBytesSent: totalBytesExpectedToSend: // 當HTTPBody中有數據時調用 - URLSession: dataTask: didReceiveData: // 同上 - URLSession: task: didCompleteWithError: // 同上
當進行一些下載操做,使用downloadTask的時候:服務器
- URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite: // 持續調用 - URLSession: downloadTask: didFinishDownloadingToURL: // 下載完成時調用 - URLSession: task: didCompleteWithError: // 本次網絡訪問完成時調用 在上面的方法調用以後調用
能夠發現,但凡是session進行的網絡請求都會最終調用- URLSession: task: didCompleteWithError:
,而在在以前調用的代理方法,會因request是否攜帶數據,訪問完成的時候服務端是否有response的數據,還有使用的task的類型會有一些差異。下面會針對uploadTask的使用和downloadTask的使用細說這些差異,以及介紹一些實現上傳和下載的具體方案。restful
使用上傳歸根結底都會使用apple的uploadTask,翻看AFN的源碼(僅僅是session部分)也都是使用了蘋果的三個建立uploadTask的方法完成的。
apple的三個方法都是一個思路:將要上傳的文件的二進制寫入到HTTPBody中,
按照有沒有使用Form能夠分爲兩類:markdown
1.沒有使用form
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL; - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
2.使用了form
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;
下面分別介紹一下:
AFN沒有使用html表單直接上傳的方式比較簡單,實現上是直接調用了apple的- uploadTaskWithRequest: fromFile:
方法或者- uploadTaskWithRequest: fromData:
,關於蘋果的這兩個方法,蘋果給出這樣的文檔
建立一個任務,這個任務能對指定的URLRquest對象執行HTTP請求和上傳提供的數據。
對於request的參數有一點須要注意的是:它的body stream和body data會被忽略,只使用fromData參數提供的數據。對於request對象還有一個要求,必須是包含了request body,所以HTTP方法能夠是POST或者PUT,另外可使用HTTP的RequestHeader提供一些上傳的元數據,如文件名字等。其實這兩個方法內部的實現中,是將要上傳的數據覆蓋寫入到了HTTPBody中。
AFNURLSessionManager對上面兩個蘋果的方法進行了再一次的封裝,這個封裝就是將代理方法的處理交給了AFURLSessionManagerTaskDelegate類,同時將傳入的進度NSProgress對象的指針指向了AFURLSessionManagerTaskDelegate對象的屬性progress,將task處理完成的回調賦給它的屬性AFURLSessionTaskCompletionHandler。
若是按照這種方式進行文件上傳,能夠按照以下方式使用AFN:
// 文件上傳,不使用表單(只能上傳單個文件),須要服務端的配合: // 1.服務端從HTTPBody獲得文件內容的二進制 // 2.將二進制存入文件中,並命名 // 這個方法內部直接使用apple的uploadTaskWithRequest建立任務, uploadTask具體的實現是: // 將文件的二進制寫入到HTTPBody中 - (void)uploadFileNoFormWithURLString:(NSString *)urlString fromFile:(NSURL *)fileURL orFromData:(NSData *)bodyData progress:(NSProgress * __autoreleasing *)progress success:(void(^)(id responseObject))success failure:(void(^)(NSError *error))failure { AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; NSURL *url = [NSURL URLWithString:urlString]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; // 必需要使用POST 不然會使用默認的GET, 這樣服務器獲得的input和HTTPBody內容不一樣,這是由於使用這種方式上傳文件其實是將文件的二進制寫入到HTTPBody中。 void (^completionBlock)(id responseObject, NSError *error) = ^(id responseObject, NSError *error) { if (error) { if (failure) { failure(error); } } else { if (success) { success(responseObject); } } }; // 這裏實際調用的URLSessionManager的方法,而不是HTTPSessionManager的方法 if (fileURL) { [[manager uploadTaskWithRequest:request fromFile:fileURL progress:progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { completionBlock(responseObject, error); }] resume]; return; } if (bodyData) { [[manager uploadTaskWithRequest:request fromData:bodyData progress:progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { completionBlock(responseObject, error); }] resume]; } return; }
使用表單實際上是對HTTPBody數據格式進行改造,相似於html中使用表單控件上傳,一樣的在底層上也是模擬html表單上傳數據的格式。通過這樣的模擬以後,服務端接收到的每一個文件對應到一個表單域(field)的值,這樣方便了服務端的處理和前臺的html頁面的統一。
AFN使用這種方式上傳的實現依靠的是apple的- uploadTaskWithStreamedRequest:
這個方法,對於這方法,文檔中有這樣的說明:
用一個指定的request建立upload task。以前的request的body stream數據會被忽略,如何須要上傳數據調用URLSession:task:needNewBodyStream:方法。也就是說在這個方法中設置的request的HTTPBody和HTTPBodyStream會被忽略,而真正上傳的數據是從代理方法URLSession:task:needNewBodyStream:
中取得的。
AFN的作法是:在AFHTTPRequestSerializer對象的multipartFormRequestWithMethod: URLString: parameters: constructingBodyWithBlock: error:
方法中將要上傳的數據組裝爲NSInputStream對象(實際上是NSInputStream的子類AFMultipartBodyStream)並設置爲request的HTTPBodyStream屬性,而後返回這個request,當真正執行到URLSession:task:needNewBodyStream:
方法時,會從request中將這個InputStream取出,而後複製,最終傳遞給用來接收它的回調completionHandler。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler { NSInputStream *inputStream = nil; if (self.taskNeedNewBodyStream) { inputStream = self.taskNeedNewBodyStream(session, task); } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) { inputStream = [task.originalRequest.HTTPBodyStream copy]; } if (completionHandler) { completionHandler(inputStream); } }
這裏AFN拼接表單域的方式和html在瀏覽器中的行爲一直,AFN的拼接方式徹底按照瀏覽器的方式模擬了這個過程,主要經過兩個類來實現,用於拼接的AFStreamingMultipartFormData類和用於Strea轉換的AFMultipartBodyStream類。
1.首先是將parameter參數轉爲AFQueryStringPair數組,並將每一個AFQueryStringPair對象的元素轉爲filed和value的二進制形式,而後使用AFStreamingMultipartFormData對象的- appendPartWithFormData: name:
方法將它們拼接爲下面格式(boundary生成以後的boundary)
--boundary
Content-Disposition: form-data; name="xx"; 二進制data
2.將parameter的傳遞的參數拼接完成後拼接文件:
利用request中傳遞過來的block繼續給上面的AFStreamingMultipartFormData兌現追加內容:使用-appendPartWithFileURL: name: error:
方法拼接文件,拼接爲以下格式:
--boundary
Content-Disposition: form-data; name="xxx"; filename="xxx" Content-Type: xxx/xxx 二進制data
最後還得加上頭部
Content-Type:multipart/form-data; boundary=生成後的boundary
還有尾部
--生成後的boundary--
這是最終拼接的結果,實際上AFN的拼接過程比這個要複雜,它並無將最終形式的'串'直接拼接出來,而是將每個部分轉爲一個AFHTTPBodyPart對象,存儲到AFStreamingMultipartFormData對象的屬性bodyStream中,bodyStream是一個AFMultipartBodyStream對象,使用的是的- appendHTTPBodyPart:
方法將AFHTTPBodyPart存儲到了它本身的可變數組屬性HTTPBodyParts中,最後在AFStreamingMultipartFormData對象的如下方法完成拼接:
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData { if ([self.bodyStream isEmpty]) { return self.request; } // Reset the initial and final boundaries to ensure correct Content-Length [self.bodyStream setInitialAndFinalBoundaries]; [self.request setHTTPBodyStream:self.bodyStream]; [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"]; [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"]; return self.request; }
能夠看到bodyStream在加了頭部和尾部以後賦值給了request,這裏最關鍵的就是AFMultipartBodyStream(bodyStream的類型)已經重寫了InputStream的read:maxLength:
和getBuffer:length:
連個方法,這樣當bodyStream被讀取的時候會按照這兩個方法的實現,按照剛纔介紹的那種形式將數據拼接起來。
介紹完了這些,咱們看一下使用這種方案進行上傳文件的經常使用代碼:
// 多文件上傳,使用POST方法,使用的是表單的方式,須要服務端的腳本支持 // 使用表單上傳,將文件做爲表單的中的一個field - (void)uploadFileUseFormWithURLString:(NSString *)urlString parameter:(id)parameter constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block progress:(NSProgress * __autoreleasing *)progress success:(void(^)(id responseObject))success failure:(void(^)(NSError *error))failure { AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager]; [mgr POST:urlString parameters:parameter constructingBodyWithBlock:block success:^(NSURLSessionDataTask *task, id responseObject) { if (success) { success(responseObject); } } failure:^(NSURLSessionDataTask *task, NSError *error) { if (failure) { failure(error); } }]; } // 也可使用提早組裝好request的方法 - (void)uploadFileUseFormWithStreamedRequest:(NSURLRequest *)urlRequest progress:(NSProgress * __autoreleasing *)progress success:(void(^)(id responseObject))success failure:(void(^)(NSError *error))failure { AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager]; [[mgr uploadTaskWithStreamedRequest:urlRequest progress:progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { if (error) { if (failure) { failure(error); } } else { if (success) { success(responseObject); } } }] resume]; }
這裏提供了兩種方案,底層代碼徹底同樣,第一種是在使用時拼接表單,第二種是將表單和parameter組裝到request以後直接調用,調用方法以下:
NSString *uploadURLString = @"http://127.0.0.1/post/upload-multipart.php"; NSDictionary *parameter = @{@"username": @"Mike"}; NSProgress *progress1 = nil; [self uploadFileUseFormWithURLString:uploadURLString parameter:parameter constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { NSString *documentFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSURL *fileURL = [NSURL fileURLWithPath:[documentFolder stringByAppendingPathComponent:@"1.txt"]]; [formData appendPartWithFileURL:fileURL name:@"userfile[]" error:NULL]; NSURL *fileURL1 = [NSURL fileURLWithPath:[documentFolder stringByAppendingPathComponent:@"2.jpg"]]; [formData appendPartWithFileURL:fileURL1 name:@"userfile[]" fileName:@"aaa.jpg" mimeType:@"image/jpeg" error:NULL]; } progress:&progress1 success:^(id responseObject) { NSLog(@"%@", responseObject); } failure:nil]; // 預先組裝request NSString *documentFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSURL *fileURL1 = [NSURL fileURLWithPath:[documentFolder stringByAppendingPathComponent:@"1.txt"]]; NSURL *fileURL2 = [NSURL fileURLWithPath:[documentFolder stringByAppendingPathComponent:@"2.jpg"]]; NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:uploadURLString parameters:parameter constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { [formData appendPartWithFileURL:fileURL1 name:@"userfile[]" fileName:@"1.txt" mimeType:@"text/plain" error:nil]; [formData appendPartWithFileURL:fileURL2 name:@"userfile[]" fileName:@"aaa.jpg" mimeType:@"image/jpeg" error:nil]; } error:nil]; NSProgress *progress2 = nil; [self uploadFileUseFormWithStreamedRequest:request progress:&progress2 success:^(id responseObject) { NSLog(@"%@", responseObject); } failure:nil];
兩種方法的底層實現和結果是徹底一致的。
使用RESTful風格的應用,默認對網絡資源增、刪、改、查對應於HTTP的PUT、DELETE、POST、GET方法。RESTful更多的是強調服務端的技術,而對於iOS客戶端而言,網絡訪問的代碼變更不是特別大,例如針對文件上傳,服務器要具備處理文件上傳的能力,並且不用像html表單那樣針對特定的頁面定製具備專注功能的處理腳本,對於這種需求,也許webDav是一個很好的工具,固然web服務端也可能會針對各自的語言和平臺使用各自的文件處理中間件,但服務端的處理不是本文的重點,再也不多說。另外,針對文件上傳服務端可能要作一些用戶驗證,iOS客戶端就要作一些配合了。
以'我要在http://127.0.0.1/uploads這個url映射的目錄下放置一個文件名爲12345.png的圖片文件'爲例,當我要進行的操做已經能用天然語言表達時,即可以以REST的思路來寫代碼了,那麼:
我要使用的HTTP方法是PUT
我要操做的url爲http://127.0.0.1/uploads
我會寫一個方法將文件名傳入,將文件的URL或者二進制傳入,以下:
- (void)uploadFileUseRESTWithURLString:(NSString *)urlString rename:(NSString *)rename fromFile:(NSURL *)fileURL orFromData:(NSData *)bodyData progress:(NSProgress * __autoreleasing *)progress success:(void(^)(id responseObject))success failure:(void(^)(NSError *error))failure { AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; NSString *urlStringPath = [urlString stringByAppendingPathComponent:rename]; NSURL *url = [NSURL URLWithString:urlStringPath]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"PUT"; // 身份驗證 BASIC 方式 NSString *usernameAndPassword = @"admin:123456"; NSData *data = [usernameAndPassword dataUsingEncoding:NSUTF8StringEncoding]; NSString *authString = [@"BASIC " stringByAppendingString:[data base64EncodedStringWithOptions:0]]; [request setValue:authString forHTTPHeaderField:@"Authorization"]; void (^completionBlock)(id responseObject, NSError *error) = ^(id responseObject, NSError *error) { if (error) { if (failure) { failure(error); } } else { if (success) { success(responseObject); } } }; if (fileURL) { [manager uploadTaskWithRequest:request fromFile:fileURL progress:progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { completionBlock(responseObject, error); }]; return; } if (bodyData) { [manager uploadTaskWithRequest:request fromData:bodyData progress:progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { completionBlock(responseObject, error); }]; } return; }
調用也是很是的簡單:
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"1.png"]; NSURL *fileURL = [NSURL fileURLWithPath:filePath]; NSProgress *progress = nil; [self uploadFileUseRESTWithURLString:@"http://127.0.0.1/uploads" rename:@"12345.png" fromFile:fileURL orFromData:nil progress:&progress success:^(id responseObject) { NSLog(@"%@", responseObject); } failure:nil];
AFN使用的下載一樣是調用了apple提供的方法:
- downloadTaskWithRequest: - downloadTaskWithURL: - downloadTaskWithResumeData:
毫無疑問的是使用url最簡單了,畢竟大部分的下載是無需構建request的。AFN使用名字類似的方法對系統方法進行的一層包裝,經過sessionManager統一管理task。
例如完成一個簡單的下載任務:
- (void)downloadWithURLString:(NSString *)urlString progress:(NSProgress * __autoreleasing *)progress completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { AFURLSessionManager *manager = [[AFURLSessionManager alloc] init]; NSMutableCharacterSet *mutableCharacterSet = [[NSMutableCharacterSet alloc] init]; [mutableCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLHostAllowedCharacterSet]]; [mutableCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLPathAllowedCharacterSet]]; NSString *escapedURLString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:mutableCharacterSet]; NSURL *url = [NSURL URLWithString:escapedURLString]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [[manager downloadTaskWithRequest:request progress:progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; NSString *fileName = httpResponse.suggestedFilename ?: urlString; NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:fileName]; NSURL *destinationURL = [NSURL fileURLWithPath:filePath]; return destinationURL; } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) { completionHandler(response, filePath, error); }] resume]; }
好吧,那段進行urlEncode的代碼確實礙眼。看一下下載的功能很是簡單,只要使用幾行代碼就完成了,這裏有一個調用並獲取進度的示例:
NSString *urlString = @"http://127.0.0.1/static/功夫熊貓.mp4"; NSProgress *progress = nil; [self downloadWithURLString:urlString progress:&progress completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) { NSLog(@"%@", error); }]; [progress addObserver:self forKeyPath:@"completedUnitCount" options:NSKeyValueObservingOptionNew context:nil];} - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([object isKindOfClass:[NSProgress class]]) { NSProgress *p = object; NSLog(@"已完成大小:%lld 總大小:%lld", p.completedUnitCount, p.totalUnitCount); NSLog(@"進度:%0.2f%%", p.fractionCompleted * 100); } }
能夠利用這種方式來觀察進度,它的原理是將外部傳來的NSProgress指針指向AFURLSessionManagerTaskDelegate對象的progress屬性,AFURLSessionManagerTaskDelegate對象會在- URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite:方法中改變自身的progress屬性值,這是外部的Progress的值也就改變了。
也可使用URLSessionManager的downloadProgressForTask:方法獲取指定task的完成進度,不過這要進行對task的統一管理。
在開發中常常會遇到下載管理,UI要獲取到下載的進度,一般有兩種方法:
1.下載器管理進度,進度改變發送通知,監聽進度改變的UI控件更新(很是消耗性能)
2.UI控件主動獲取任務管理器中的任務,間隔性地獲取進度。
說到NSURLSessionDownloadDelegate中聲明的三個方法:
- URLSession:session downloadTask:downloadTask didFinishDownloadingToURL: // 在下載完成的時候調用 - URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite: // 在下載的過程當中間歇性調用 - URLSession: downloadTask: didResumeAtOffset: expectedTotalBytes:
對於第三個方法到底何時調用,就不得不說一下使用NSURLSession的- downloadTaskWithResumeData:
方法建立downloadTask了。
這個方法用resumeData建立一個downloadTask,若是downloadTask不能被成功地恢復, URLSession:task:didCompleteWithError: 會被調用。
說白了它被用來恢復已經暫停的downloadTask,那麼downloadTask如何暫停呢,網絡問題還有主動調用downloadTask的一個方法:
- (void)cancelByProducingResumeData:(void (^)(NSData * __nullable resumeData))completionHandler;
這個方法接收一個回調,在任務暫停以後調用,通常在這個回調內部記錄一下恢復點的數據resumeData,resumeData參數是未來用來繼續的參數。resumeData只是一個chunk,而不是已經下載的所有數據,所以沒法經過它實現斷點續傳,只能實現簡單的暫停和下載,而且要保證resume建立dataTask的session和建立被取消的dataTask的session是同一個,也就是所謂的session沒有離線 。
這是一些實現暫停和繼續的示例代碼:
// 暫停下載 - (IBAction)pauseButtonDidClicked:(UIButton *)sender { [self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) { self.resumeData = resumeData; self.downloadTask = nil; }]; // 這是一個異步的方法 } // 繼續 - (IBAction)resumeButtonDidClicked:(UIButton *)sender { if (self.resumeData == nil) { return; } self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData]; self.resumeData = nil; [self.downloadTask resume]; }
那麼以AFN的方式,就得保存manager,由於只要manager沒有改變session就沒有改變,同時應該將下載的任務添加到一個任務管理器中,對任務進行統一的調配,這樣就可使用使用下面的步驟進行任務的暫停和繼續:1.先取得downloadTask調用它的cancelByProducingResumeData:將resumeData保存起來2.須要繼續的時候使用建立了上面的dataTask的manager和保存的resumeData進行恢復,manager調用downloadTaskWithResumeData:就能夠。