Мне нужно загрузить много файлов (до 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 будет отражать прогресс всех детей.