日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

通读AFN②--AFN的上传和下载功能分析、SessionTask及相应的session代理方法的使用细节...

發(fā)布時間:2024/6/21 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 通读AFN②--AFN的上传和下载功能分析、SessionTask及相应的session代理方法的使用细节... 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

這一部分主要研究AFN的上傳和下載功能,中間涉及到各種NSURLSessionTask的一些創(chuàng)建的解析和HTTPSessionManager對RESTful風格的web應(yīng)用的支持,同時會穿插一點NSURLSession代理方法被調(diào)用的時機和對上傳的數(shù)據(jù)的序列化的步驟。
本文主要講解的是上傳和下載的代碼實現(xiàn)細節(jié),不會考慮上傳過程中的安全性問題。

文件的上傳和下載同時也包括普通的數(shù)據(jù)請求說說到底都是使用了系統(tǒng)的NSURLSession類創(chuàng)建對應(yīng)的Task,然后執(zhí)行,為了更好得理解,我們先理清一下NSURLSessionTask類以及它的子類、NSURLSessionTaskDelegate協(xié)議和它的子協(xié)議之間的關(guān)系,以及各種代理方法調(diào)用的時機。
先看一張圖:

其中的調(diào)用是指,task在resume之后會調(diào)用的Session對應(yīng)的代理方法聲明在的協(xié)議,例如:
當執(zhí)行一個NSURLSessionDataTask類型的任務(wù)resume之后,負責創(chuàng)建它的session將會調(diào)用在NSURLSessionDataDelegate中定義的幾個方法:

- URLSession: dataTask: didReceiveResponse: completionHandler: - URLSession: dataTask: didBecomeDownloadTask: - URLSession: dataTask: didBecomeStreamTask: - URLSession: dataTask: didReceiveData: - URLSession: dataTask: willCacheResponse: completionHandler:

由于NSURLSessionDataDelegate協(xié)議遵守了NSURLSessionTaskDelegate和NSURLSessionDelegate,所以也會調(diào)用這樣幾個方法:

// 在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來創(chuàng)建NSURLSessionTask,只能創(chuàng)建它的子類來使用,iOS并沒有提供可以直接創(chuàng)建它的方法:
1.不可能通過alloc init創(chuàng)建 ,因為創(chuàng)建之后 無法給request屬性(readonly)賦值,網(wǎng)絡(luò)請求無法進行。
2.NSURLSession、NSURLSession(NSURLSessionAsynchronousConvenience) 沒有提供直接創(chuàng)建的方法。
或許apple本來就打算將這個類設(shè)計為抽象類,而只能使用繼承它的類。

而只要是使用了session類進行創(chuàng)建任何一個dataTask、uploadTask或者是downloadTask就會調(diào)用在NSURLSessionDelegate中和NSURLSessionTaskDelegate中聲明的代理方法,這些代理方法大都是進行網(wǎng)絡(luò)請求的配置,少部分涉及到數(shù)據(jù)處理,而子協(xié)議NSURLSessionDataDelegate、NSURLSessionDownloadDelegate和NSURLSessionSteamDelegate都是具體的數(shù)據(jù)處理方法。

經(jīng)過一些簡單的測試:看看一些方法的調(diào)用順序,使用dataTask進行一個普通的網(wǎng)絡(luò)請求:
如果使用的是GET請求,或者使用的是POST請求、但是HTTPBody沒有數(shù)據(jù),主要調(diào)用兩個代理方法:

- URLSession: dataTask: didReceiveData: // 當服務(wù)端有數(shù)據(jù)返回時調(diào)用,沒有數(shù)據(jù)返回則不調(diào)用 - URLSession: task: didCompleteWithError: // 在請求完成之后必調(diào)用

如果是POST請求,并且HTTPBody中帶有數(shù)據(jù),那么主要調(diào)用以下幾個方法(實際上不管創(chuàng)建的任務(wù)是dataTask或是uploadTask都是這樣,畢竟uploadTask是繼承自dataTask的):

- URLSession: task: didSendBodyData: totalBytesSent: totalBytesExpectedToSend: // 當HTTPBody中有數(shù)據(jù)時調(diào)用 - URLSession: dataTask: didReceiveData: // 同上 - URLSession: task: didCompleteWithError: // 同上

當進行一些下載操作,使用downloadTask的時候:

- URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite: // 間歇性調(diào)用 - URLSession: downloadTask: didFinishDownloadingToURL: // 下載完成時調(diào)用 - URLSession: task: didCompleteWithError: // 本次網(wǎng)絡(luò)訪問完成時調(diào)用 在上面的方法調(diào)用之后調(diào)用

可以發(fā)現(xiàn),但凡是session進行的網(wǎng)絡(luò)請求都會最終調(diào)用- URLSession: task: didCompleteWithError:,而在在之前調(diào)用的代理方法,會因request是否攜帶數(shù)據(jù),訪問完成的時候服務(wù)端是否有response的數(shù)據(jù),還有使用的task的類型會有一些差別。下面會針對uploadTask的使用和downloadTask的使用細說這些差別,以及介紹一些實現(xiàn)上傳和下載的具體方案。

第二部分 上傳

使用上傳歸根結(jié)底都會使用apple的uploadTask,翻看AFN的源碼(僅僅是session部分)也都是使用了蘋果的三個創(chuàng)建uploadTask的方法完成的。
apple的三個方法都是一個思路:將要上傳的文件的二進制寫入到HTTPBody中,
按照有沒有使用Form可以分為兩類:

1.沒有使用form

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL; - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;

2.使用了form

- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;

下面分別介紹一下:

不使用表單的情況

AFN沒有使用html表單直接上傳的方式比較簡單,實現(xiàn)上是直接調(diào)用了apple的- uploadTaskWithRequest: fromFile:方法或者- uploadTaskWithRequest: fromData:,關(guān)于蘋果的這兩個方法,蘋果給出這樣的文檔
創(chuàng)建一個任務(wù),這個任務(wù)能對指定的URLRquest對象執(zhí)行HTTP請求和上傳提供的數(shù)據(jù)。

對于request的參數(shù)有一點需要注意的是:它的body stream和body data會被忽略,只使用fromData參數(shù)提供的數(shù)據(jù)。對于request對象還有一個要求,必須是包含了request body,因此HTTP方法可以是POST或者PUT,另外可以使用HTTP的RequestHeader提供一些上傳的元數(shù)據(jù),如文件名字等。其實這兩個方法內(nèi)部的實現(xiàn)中,是將要上傳的數(shù)據(jù)覆蓋寫入到了HTTPBody中。

AFNURLSessionManager對上面兩個蘋果的方法進行了再一次的封裝,這個封裝就是將代理方法的處理交給了AFURLSessionManagerTaskDelegate類,同時將傳入的進度NSProgress對象的指針指向了AFURLSessionManagerTaskDelegate對象的屬性progress,將task處理完成的回調(diào)賦給它的屬性AFURLSessionTaskCompletionHandler。

如果按照這種方式進行文件上傳,可以按照如下方式使用AFN:

// 文件上傳,不使用表單(只能上傳單個文件),需要服務(wù)端的配合: // 1.服務(wù)端從HTTPBody得到文件內(nèi)容的二進制 // 2.將二進制存入文件中,并命名// 這個方法內(nèi)部直接使用apple的uploadTaskWithRequest創(chuàng)建任務(wù), uploadTask具體的實現(xiàn)是: // 將文件的二進制寫入到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, 這樣服務(wù)器得到的input和HTTPBody內(nèi)容不同,這是因為使用這種方式上傳文件實際上是將文件的二進制寫入到HTTPBody中。void (^completionBlock)(id responseObject, NSError *error) = ^(id responseObject, NSError *error) {if (error) {if (failure) {failure(error);}} else {if (success) {success(responseObject);}}};// 這里實際調(diào)用的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數(shù)據(jù)格式進行改造,類似于html中使用表單控件上傳,同樣的在底層上也是模擬html表單上傳數(shù)據(jù)的格式。經(jīng)過這樣的模擬之后,服務(wù)端接收到的每個文件對應(yīng)到一個表單域(field)的值,這樣方便了服務(wù)端的處理和前臺的html頁面的統(tǒng)一。

AFN使用這種方式上傳的實現(xiàn)依靠的是apple的- uploadTaskWithStreamedRequest:這個方法,對于這方法,文檔中有這樣的說明:
用一個指定的request創(chuàng)建upload task。之前的request的body stream數(shù)據(jù)會被忽略,如何需要上傳數(shù)據(jù)調(diào)用URLSession:task:needNewBodyStream:方法。也就是說在這個方法中設(shè)置的request的HTTPBody和HTTPBodyStream會被忽略,而真正上傳的數(shù)據(jù)是從代理方法URLSession:task:needNewBodyStream:中取得的。

AFN的做法是:在AFHTTPRequestSerializer對象的multipartFormRequestWithMethod: URLString: parameters: constructingBodyWithBlock: error:方法中將要上傳的數(shù)據(jù)組裝為NSInputStream對象(其實是NSInputStream的子類AFMultipartBodyStream)并設(shè)置為request的HTTPBodyStream屬性,然后返回這個request,當真正執(zhí)行到URLSession:task:needNewBodyStream:方法時,會從request中將這個InputStream取出,然后復制,最終傳遞給用來接收它的回調(diào)completionHandler。

- (void)URLSession:(NSURLSession *)sessiontask:(NSURLSessionTask *)taskneedNewBodyStream:(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的拼接方式完全按照瀏覽器的方式模擬了這個過程,主要通過兩個類來實現(xiàn),用于拼接的AFStreamingMultipartFormData類和用于Strea轉(zhuǎn)換的AFMultipartBodyStream類。

1.首先是將parameter參數(shù)轉(zhuǎn)為AFQueryStringPair數(shù)組,并將每個AFQueryStringPair對象的元素轉(zhuǎn)為filed和value的二進制形式,然后使用AFStreamingMultipartFormData對象的- appendPartWithFormData: name:方法將它們拼接為下面格式(boundary生成之后的boundary)

--boundary Content-Disposition: form-data; name="xx";二進制data

2.將parameter的傳遞的參數(shù)拼接完成后拼接文件:
利用request中傳遞過來的block繼續(xù)給上面的AFStreamingMultipartFormData兌現(xiàn)追加內(nèi)容:使用-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--

這是最終拼接的結(jié)果,實際上AFN的拼接過程比這個要復雜,它并沒有將最終形式的'串'直接拼接出來,而是將每一個部分轉(zhuǎn)為一個AFHTTPBodyPart對象,存儲到AFStreamingMultipartFormData對象的屬性bodyStream中,bodyStream是一個AFMultipartBodyStream對象,使用的是的- appendHTTPBodyPart:方法將AFHTTPBodyPart存儲到了它自己的可變數(shù)組屬性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,這里最關(guān)鍵的就是AFMultipartBodyStream(bodyStream的類型)已經(jīng)重寫了InputStream的read:maxLength:和getBuffer:length:連個方法,這樣當bodyStream被讀取的時候會按照這兩個方法的實現(xiàn),按照剛才介紹的那種形式將數(shù)據(jù)拼接起來。

介紹完了這些,我們看一下使用這種方案進行上傳文件的常用代碼:

// 多文件上傳,使用POST方法,使用的是表單的方式,需要服務(wù)端的腳本支持 // 使用表單上傳,將文件作為表單的中的一個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之后直接調(diào)用,調(diào)用方法如下:

// 對第一種方式的調(diào)用 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)的調(diào)用 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];

兩種方法的底層實現(xiàn)和結(jié)果是完全一致的。

針對RESTful Web應(yīng)用的文件上傳

使用RESTful風格的應(yīng)用,默認對網(wǎng)絡(luò)資源增、刪、改、查對應(yīng)于HTTP的PUT、DELETE、POST、GET方法。RESTful更多的是強調(diào)服務(wù)端的技術(shù),而對于iOS客戶端而言,網(wǎng)絡(luò)訪問的代碼變動不是特別大,例如針對文件上傳,服務(wù)器要具有處理文件上傳的能力,而且不用像html表單那樣針對特定的頁面定制具有專一功能的處理腳本,對于這種需求,也許webDav是一個很好的工具,當然web服務(wù)端也可能會針對各自的語言和平臺使用各自的文件處理中間件,但服務(wù)端的處理不是本文的重點,不再多說。另外,針對文件上傳服務(wù)端可能要做一些用戶驗證,iOS客戶端就要做一些配合了。

以'我要在http://127.0.0.1/uploads這個url映射的目錄下放置一個文件名為12345.png的圖片文件'為例,當我要進行的操作已經(jīng)能用自然語言表達時,便可以以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; }

調(diào)用也是非常的簡單:

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使用的下載同樣是調(diào)用了apple提供的方法:

- downloadTaskWithRequest: - downloadTaskWithURL: - downloadTaskWithResumeData:

毫無疑問的是使用url最簡單了,畢竟大部分的下載是無需構(gòu)建request的。AFN使用名字相似的方法對系統(tǒng)方法進行的一層包裝,通過sessionManager統(tǒng)一管理task。
例如完成一個簡單的下載任務(wù):

- (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的代碼確實礙眼。看一下下載的功能非常簡單,只要使用幾行代碼就完成了,這里有一個調(diào)用并獲取進度的示例:

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的統(tǒng)一管理。
在開發(fā)中經(jīng)常會遇到下載管理,UI要獲取到下載的進度,通常有兩種方法:
1.下載器管理進度,進度改變發(fā)送通知,監(jiān)聽進度改變的UI控件更新(非常消耗性能)
2.UI控件主動獲取任務(wù)管理器中的任務(wù),間隔性地獲取進度。

說到NSURLSessionDownloadDelegate中聲明的三個方法:

- URLSession:session downloadTask:downloadTask didFinishDownloadingToURL: // 在下載完成的時候調(diào)用 - URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite: // 在下載的過程中間歇性調(diào)用 - URLSession: downloadTask: didResumeAtOffset: expectedTotalBytes:

對于第三個方法到底什么時候調(diào)用,就不得不說一下使用NSURLSession的- downloadTaskWithResumeData:方法創(chuàng)建downloadTask了。
這個方法用resumeData創(chuàng)建一個downloadTask,如果downloadTask不能被成功地恢復, URLSession:task:didCompleteWithError: 會被調(diào)用。
說白了它被用來恢復已經(jīng)暫停的downloadTask,那么downloadTask如何暫停呢,畢竟都沒有被暫停何來恢復。可以主動調(diào)用downloadTask的一個對象方法:

- (void)cancelByProducingResumeData:(void (^)(NSData * __nullable resumeData))completionHandler;

這個方法會讓downloadTask暫停,它接收一個回調(diào),在任務(wù)暫停之后調(diào)用,一般在這個回調(diào)內(nèi)部記錄一下恢復點的數(shù)據(jù)resumeData,resumeData參數(shù)是將來用來繼續(xù)的參數(shù)。resumeData只是一個chunk,而不是已經(jīng)下載的全部數(shù)據(jù),因此無法通過它實現(xiàn)斷點續(xù)傳,只能實現(xiàn)簡單的暫停和繼續(xù),并且要保證通過resume創(chuàng)建downloadTask時使用的session和創(chuàng)建被取消的downloadTask時使用的session是同一個,也就是所謂的session沒有離線 。
這是一些實現(xiàn)暫停和繼續(xù)的示例代碼:

// 暫停下載 - (IBAction)pauseButtonDidClicked:(UIButton *)sender {[self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {self.resumeData = resumeData; self.downloadTask = nil;}]; // 這是一個異步的方法 }// 繼續(xù) - (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就沒有改變,同時應(yīng)該將下載的任務(wù)添加到一個任務(wù)管理器中,對任務(wù)進行統(tǒng)一的調(diào)配,這樣就可以使用使用下面的步驟進行任務(wù)的暫停和繼續(xù):
1.先取得downloadTask調(diào)用它的cancelByProducingResumeData:將resumeData保存起來
2.需要繼續(xù)的時候使用創(chuàng)建了上面的dataTask的manager和保存的resumeData進行恢復,manager調(diào)用downloadTaskWithResumeData:就可以。

轉(zhuǎn)載于:https://www.cnblogs.com/Mike-zh/p/5172389.html

總結(jié)

以上是生活随笔為你收集整理的通读AFN②--AFN的上传和下载功能分析、SessionTask及相应的session代理方法的使用细节...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。