我需要在单个流请求中使用 AFNetworking 将大量文件(最多 100 个)上传到服务器,这可以为我提供上传进度
Posted
技术标签:
【中文标题】我需要在单个流请求中使用 AFNetworking 将大量文件(最多 100 个)上传到服务器,这可以为我提供上传进度【英文标题】:I need to upload lot of files (up to 100) to server using AFNetworking in a single streaming request that can provide me upload progress 【发布时间】:2014-04-01 08:02:56 【问题描述】:我在使用 AFNetworking 2.0 进行流式传输请求时面临几个挑战。
我想将大量文件 (~50MB) 上传到服务器,并且请求必须是流式传输的。 (否则应用会因为内存压力而崩溃)
我在 AFNetworking 2.0 中尝试了各种方法,但均未成功。 这就是我目前正在做的事情:
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:baseServiceUrl parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData)
int imageIndex = 0;
for (NSURL *tempFileUrl in tempFilesUrlList)
NSString* fileName = [NSString stringWithFormat:@"image%d", imageIndex];
NSError *appendError = nil;
[formData appendPartWithFileURL:tempFileUrl name:fileName error:&appendError];
imageIndex++;
error:&error];
AFHTTPRequestOperation *requestOperation = nil;
requestOperation = [manager HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject)
DLog(@"Uploading files completed: %@\n %@", operation, responseObject);
failure:^(AFHTTPRequestOperation *operation, NSError *error)
DLog(@"Error: %@", error);
];
[requestOperation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite)
double percentDone = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
DLog(@"progress updated(percentDone) : %f", percentDone);
];
[requestOperation start];
这很好用,但不是流式传输。如果请求太大,它会在内存中准备请求可能会崩溃。我曾尝试使用原生套接字和流,但 Apple 表示他们可能会因此而拒绝该应用程序。
我尝试过的另一种方法如下:
AFHTTPSessionManager* manager = [AFHTTPSessionManager manager];
NSString *appID = [[NSBundle mainBundle] bundleIdentifier];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:appID];
NSURL* serviceUrl = [NSURL URLWithString:baseServiceUrl];
manager = [manager initWithBaseURL:serviceUrl sessionConfiguration:configuration];
NSURLSessionDataTask *uploadFilesTask = [manager POST:baseServiceUrl parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData)
int imageIndex = 0;
for (NSURL *tempFileUrl in tempFilesUrlList)
NSString* fileName = [NSString stringWithFormat:@"image%d", imageIndex];
NSError *appendError = nil;
[formData appendPartWithFileURL:tempFileUrl name:fileName error:&appendError];
imageIndex++;
success:^(NSURLSessionDataTask *task, id responseObject)
DLog(@"Files uploaded successfully: %@\n %@", task, responseObject);
failure:^(NSURLSessionDataTask *task, NSError *error)
DLog(@"Error: %@", error);
];
[progressBar setProgressWithUploadProgressOfTask:uploadFilesTask animated:true];
但这给了我运行时错误提示:
由于未捕获的异常“NSGenericException”而终止应用程序, 原因:'后台会话中的上传任务必须来自文件' *** 第一次抛出调用堆栈:( 0 CoreFoundation 0x02cc75e4 __exceptionPreprocess + 180 1 libobjc.A.dylib 0x02a4a8b6 objc_exception_throw + 44 2 CFNetwork 0x0459eb6c -[__NSCFBackgroundSessionBridge uploadTaskForRequest:uploadFile:bodyData:completion:] + 994 3 CFNetwork 0x045f2f37 -[__NSCFURLSession uploadTaskWithStreamedRequest:] + 73
(必须是流式传输,并且能够在后台继续)
【问题讨论】:
据我所知,ios 不支持通过后台上传进行流式传输。您需要在后台或仅在前台支持此功能吗?如果在前台你理论上应该可以流?另一种选择是查看服务器端对以较小部分上传文件的支持。您的服务器是否支持分段上传(即 Amazon S3 的分段上传 docs.aws.amazon.com/AmazonS3/latest/dev/mpuoverview.html )。使用部分,您将能够以较小的部分上传文件,从而避免内存开销。 【参考方案1】:我最近自己解决了这个问题。
我建议使用网络库,例如 AFNetworking。它将使您免于执行以前已解决的单调任务。
为了真正在后台发送请求,您需要将整个请求写入一个文件,然后使用 NSURLSession 将其流式传输到您的服务器。 (这是公认的答案——下面的链接)因此,您需要将请求写入磁盘而不将整个文件读入内存。
How to upload task in background using afnetworking
您可以流式传输文件(不在后台),例如:
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData)
[formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil];
error:nil];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSProgress *progress = nil;
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error)
if (error)
NSLog(@"Error: %@", error);
else
NSLog(@"%@ %@", response, responseObject);
enter code here
];
[uploadTask resume];
http://cocoadocs.org/docsets/AFNetworking/2.5.0/
注意:一个简单的解决方案是请求时间让您的应用在后台运行。当在您的应用程序委托上调用 applicationDidEnterBackground:
时,您可以调用 [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:]
来请求在后台运行一段时间。 ([[UIApplication sharedApplication] endBackgroundTask:];
说你已经完成了。)你问的缺点是什么?你被给予有限的时间。 “如果你没有在时间到期之前调用endBackgroundTask:对于每个任务,系统会杀死应用程序。如果你在handler参数中提供一个块对象,系统会在时间到期之前调用你的处理程序,让你有机会结束任务。 "
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/#//apple_ref/occ/instm/UIApplication/beginBackgroundTaskWithExpirationHandler:
进展
您可以使用子/父关系跟踪多个 NSProgress 对象的进度。
您可以创建一个NSProgress *overallProgress
对象,然后在创建新请求之前调用[overallProgress becomeCurrentWithPendingUnitCount:<unit size>];
,然后调用[overallProgress resignCurrent];
。您的总体进度对象将反映所有孩子的进度。
【讨论】:
以上是关于我需要在单个流请求中使用 AFNetworking 将大量文件(最多 100 个)上传到服务器,这可以为我提供上传进度的主要内容,如果未能解决你的问题,请参考以下文章
使用 AFNetworking 2.0 从 iOS 应用程序以流的形式上传 100 张图像
如何使用 AFNetworking 为请求设置 HTTP 正文?