8

使用 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

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

4

1 回答 1

6

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

我建议使用网络库,例如 AFNetworking。它将使您免于执行以前已解决的单调任务。

为了真正在后台发送请求,您需要将整个请求写入文件,然后使用 NSURLSession 将其流式传输到您的服务器。(这是公认的答案——下面的链接)因此,您需要将请求写入磁盘而不将整个文件读入内存。

如何使用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];。您的整体进度对象将反映所有孩子的进度。

于 2014-12-05T15:27:06.407 回答