ios – 我需要在一个流媒体请求中使用AFNetworking将大量文件(最多100个)上传到服务器,这可以为我提供上传进度

使用AFNetworking 2.0时,我遇到了一些关于流媒体请求的挑战.

我想将大量文件(~50MB)上传到服务器,并且请求必须要流式传输. (否则app会因内存压力而崩溃)

我已尝试过各种方法在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,error);
}];

[progressBar setProgressWithUploadProgressOfTask:uploadFilesTask animated:true];

但这给了我运行时错误说:

Terminating app due to uncaught exception ‘NSGenericException’,
reason: ‘Upload tasks in background sessions must be from a file’
*** First throw call stack: ( 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

(必须是流媒体,并且能够在后台继续)

解决方法

我最近自己解决了这个问题

我建议使用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/

注意:一个简单的解决方案是请求您的应用在后台运行的时间.当你的app delegate上调用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]之前;然后.您的overallProgress对象将反映所有孩子的进度.

相关文章

UITabBarController 是 iOS 中用于管理和显示选项卡界面的一...
UITableView的重用机制避免了频繁创建和销毁单元格的开销,使...
Objective-C中,类的实例变量(instance variables)和属性(...
从内存管理的角度来看,block可以作为方法的传入参数是因为b...
WKWebView 是 iOS 开发中用于显示网页内容的组件,它是在 iO...
OC中常用的多线程编程技术: 1. NSThread NSThread是Objecti...