iOS 後臺上傳的處理邏輯,大概和後臺下載的邏輯相差無幾。基本的邏輯是:首先建立一個NSURLSessionConfiguration
,然後通過這個configuration,建立一個NSURLSession
,接著是建立相關的NSURLSessionTask
,最後就是處理相關的回撥方法。有了後臺上傳給資料同步提供方便。
一、建立NSURLSession
建立一個後臺下載的session
- (NSURLSession *)getDownloadURLSession {
NSString *identifier = [self backgroundSessionIdentifier];
NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
return session;
}
複製程式碼
獲取後臺下載的標識
- (NSString *)backgroundSessionIdentifier {
NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
NSString *identifier = [NSString stringWithFormat:@"%@.BackgroundSession", bundleId];
return identifier;
}
複製程式碼
二、建立上傳的NSURLSessionTask
NSURLSessionUploadTask 的父類是NSURLSessionDataTask,它的父類是NSURLSessionTask。在建立上傳的task的時候,有幾個注意點(坑點)。下面介紹總結的三種不同型別的後臺上傳方式。
- 直接通過檔案上傳(最簡單)
這種上傳方式是,網路上最容易搜尋到的一種上傳方法,直接拿到檔案的本地路徑,然後進行上傳。其中
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
不支援後臺上傳。
- (void)bgUploadFromFile{
NSLog(@"%s", __func__);
NSURL *url = [NSURL URLWithString:kStreamUploadUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSString *path = [[NSBundle mainBundle] pathForResource:@"icon.jpg" ofType:nil];
self.uploadTask = [self.backgroundSession uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:path]];
[self.uploadTask resume];
}
複製程式碼
- 通過form檔案流進行上傳
使用這種方式上傳form型別的資料的時候有個坑點,那麼就是必須要給予兩個請求頭:
Content-Length
和Content-Type
,沒有這兩個請求頭,點選是沒有反應的!
- (void)bgUploadStreamForm
{
NSLog(@"%s", __func__);
NSURL *url = [NSURL URLWithString:kFormUploadUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSString *path = [[NSBundle mainBundle]pathForResource:@"icon" ofType:@"jpg"];
NSData *bodydata = [self buildBodyDataWithPicPath:path];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; charset=utf-8;boundary=%@",boundary];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
[request setValue:[NSString stringWithFormat:@"%zd", bodydata.length] forHTTPHeaderField:@"Content-Length"];
request.HTTPBodyStream = [NSInputStream inputStreamWithData:bodydata];
self.uploadTask = [self.backgroundSession uploadTaskWithStreamedRequest:request];
[self.uploadTask resume];
}
複製程式碼
拼接form型別的資料,上傳中需要的額外引數,也可以在form當中拼接,提交給伺服器
-(NSData*)buildBodyDataWithPicPath:(NSString *)path{
NSMutableData *bodyData = [NSMutableData data];
NSMutableString *bodyStr = [NSMutableString string];
[bodyStr appendFormat:@"--%@\r\n",boundary];//\n:換行 \n:切換到行首
[bodyStr appendFormat:@"Content-Disposition: form-data; name=\"sampleFile\"; filename=\"icon.jpg\""];
[bodyStr appendFormat:@"\r\n\r\n"];
NSData *start = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
[bodyData appendData:start];
NSData *picData = [NSData dataWithContentsOfFile:path];
[bodyData appendData:picData];
bodyStr = [NSMutableString string];
[bodyStr appendFormat:@"\r\n--%@--",boundary];
NSData *endData = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
[bodyData appendData:endData];
return bodyData;
}
複製程式碼
- 檔案流進行上傳 這種上傳方式對於客戶端來講的話,也是比較方便簡單,效能好的一種方法,但是特殊的地方就是伺服器需要特殊處理,簡單來講就是有點反伺服器的常規。還有請求頭和上面一樣必須要做處理。
- (void)bgUploadStreamFile {
NSLog(@"%s", __func__);
NSURL *url = [NSURL URLWithString:kStreamUploadUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSString *path = [[NSBundle mainBundle]pathForResource:@"icon" ofType:@"jpg"];
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSDictionary *fileAttri = [fileMgr attributesOfItemAtPath:path error:nil];
NSNumber *fileSize = [fileAttri valueForKey:@"NSFileSize"];
request.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:path];
[request setValue:[NSString stringWithFormat:@"%zd", fileSize.integerValue] forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
self.uploadTask = [self.backgroundSession uploadTaskWithStreamedRequest:request];
[self.uploadTask resume];
}
複製程式碼
- 這裡簡單聊聊分片上傳
- 首先分片上傳,在上面三種方式中你認為哪一種方式可以了?做過分片開發的同學,應該會裡面反應過來選擇2。是的,在第二種上傳方式中,給予了我們自定義上傳資料的可能。分片上傳與斷點續傳是有區別的。
- 在上傳和下載中,續傳的過程中,每次暫停後上傳的過程中,我們會帶一個
range
請求頭,這個range才是上傳實現續傳的關鍵,檔案伺服器通過range的範圍,來給我們開始的資料流,客戶端根據range來拼接本地的快取檔案。 - 那我們上傳的過程中,如何實現分片上傳呢?斷點續傳的區別又是什麼?假設我們有200MB的檔案,我們單次上傳的時候,檔案塊有點大,那麼我們分片上傳的時候,會將這個檔案進行分塊,假設我們分成10塊,那麼每塊就是20MB;接著對分塊的資料進行上傳,分塊的資料一定要有個分塊的標號,根據切割的開始為標號0,結束為標號9。分別對這10塊資料進行上傳,上傳的時候將標號帶到請求頭或者form引數中。
- 最後檔案伺服器接收到資料之後,根據標號進行儲存到快取目錄,假設檔名為fasadas_0 ~ fasadas_9,客戶端上傳10個分塊結束之後,呼叫一個檔案合併的介面,然後伺服器檢測分塊檔案,合併成一個檔案,回撥成功。
到這裡其實還有坑,這時候2,3後臺上傳還是不起作用,還是有坑,下面繼續走
三、上傳的代理
- 必須實現這個代理,上面的2,3方式的上傳才能夠進行
/* Sent if a task requires a new, unopened body stream. This may be
* necessary when authentication has failed for any request that
* involves a body stream.
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler {
NSInputStream *inputStream = task.originalRequest.HTTPBodyStream ;;
if (completionHandler) {
completionHandler(inputStream);
}
}
複製程式碼
- 上傳的進度回撥方法
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
NSLog(@"progress: %f" ,totalBytesSent/(float)totalBytesExpectedToSend);
}
複製程式碼
- 首個上傳任務在後臺上傳成功的回撥
/* If an application has received an
* -application:handleEventsForBackgroundURLSession:completionHandler:
* message, the session delegate will receive this message to indicate
* that all messages previously enqueued for this session have been
* delivered. At this time it is safe to invoke the previously stored
* completion handler, or to begin any internal updates that will
* result in invoking the completion handler.
*/
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
NSLog(@"%s", __func__);
}
複製程式碼
- AppDelegate中首個後臺上傳任務在後臺完成是的回撥
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
NSLog(@"%s", __func__);
}
複製程式碼
這幾個回撥是比較重要的上傳回撥方法,部分和下面類似,細節處理可檢視上傳的回撥處理
四、有了這些東西沒有上傳測試伺服器請看這裡
上傳測試伺服器:github.com/onezens/fil…