Мне нужно загрузить много файлов (до 100) на сервер с помощью AFNetworking в одном запросе потоковой передачи, который может предоставить мне прогресс загрузки

Есть несколько проблем, с которыми я сталкиваюсь с потоковым запросом с использованием AFNetworking 2.0.

Я хочу загрузить много файлов (~50 МБ) на сервер, и запрос должен быть потоковым. (иначе приложение вылетит из-за нехватки памяти)

Я пробовал различные способы сделать это в 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: завершение:] + 994 3
CFNetwork 0x045f2f37 - [__NSCFURLSession uploadTaskWithStreamedRequest:] + 73

(Должен быть потоковым и иметь возможность продолжить в фоновом режиме)

1 ответ

Я решил это сам недавно.

Я рекомендую использовать сетевую библиотеку, такую ​​как 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: для каждой задачи до истечения времени система убивает приложение. Если вы предоставите объект блока в параметре обработчика, система вызывает ваш обработчик до истечения времени, чтобы дать вам возможность завершить задачу. "

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/:

Прогресс

Вы можете отслеживать ход выполнения нескольких объектов NSProgress, используя отношения ребенок / родитель. Вы можете создать NSProgress *overallProgress объект, а затем вызвать [overallProgress becomeCurrentWithPendingUnitCount:<unit size>]; перед созданием нового запроса и [overallProgress resignCurrent]; после этого. Ваш общий объект Progress будет отражать прогресс всех детей.

Другие вопросы по тегам