Ответ 1
Несколько мыслей:
-
Вы должны убедиться, что вы сделали необходимую кодировку, описанную в разделе "Обработка фоновой активности iOS" в разделе "Загрузка системы загрузки URL" Руководство говорит:
Если вы используете
NSURLSession
в iOS, ваше приложение автоматически перезапускается при завершении загрузки. Ваш метод приложений appapplication:handleEventsForBackgroundURLSession:completionHandler:
отвечает за воссоздание соответствующего сеанса, хранение обработчика завершения и вызов этого обработчика, когда сеанс вызывает ваш делегат сеансаURLSessionDidFinishEventsForBackgroundURLSession:
.В этом руководстве приведены некоторые примеры того, что вы можете сделать. Честно говоря, я думаю, что примеры кода, рассмотренные в последней части видео WWDC 2013 Что нового в Foundation Networking, еще более понятны.
-
Основная реализация
AFURLSessionManager
будет работать совместно с фоновыми сеансами, если приложение просто приостановлено (вы увидите, что ваши блоки вызываются при выполнении сетевых задач, если вы сделали это). Но, как вы уже догадались, любые параметры блока, специфичные для задачи, которые передаются методуAFURLSessionManager
, где вы создаетеNSURLSessionTask
для загрузки и загрузки, теряются ", если приложение завершено или сбой."Для загрузки в фоновом режиме это раздражает (поскольку ваши информационные уровни прогресса и блокировки на уровне задач, которые вы указали при создании задачи, не будут вызваны). Но если вы используете оценки уровня сеанса (например,
setTaskDidCompleteBlock
иsetTaskDidSendBodyDataBlock
), это будет вызвано правильно (если вы всегда установите эти блоки при повторной инициализации диспетчера сеансов).Как выясняется, эта проблема потери блоков на самом деле более проблематична для загрузки фонограмм, но решение там очень похоже (не используйте параметры блока на основе задач, а скорее используйте блоки на основе сеанса, такие как
setDownloadTaskDidFinishDownloadingBlock
). -
Альтернативой вы можете придерживаться стандартного (нефонического)
NSURLSession
, но убедитесь, что ваше приложение требует немного времени для завершения загрузки, если пользователь покидает приложение во время выполнения задачи. Например, перед созданиемNSURLSessionTask
вы можете создатьUIBackgroundTaskIdentifier
:UIBackgroundTaskIdentifier __block taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) { // handle timeout gracefully if you can [[UIApplication sharedApplication] endBackgroundTask:taskId]; taskId = UIBackgroundTaskInvalid; }];
Но убедитесь, что блок завершения сетевой задачи правильно информирует iOS о том, что он завершен:
if (taskId != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:taskId]; taskId = UIBackgroundTaskInvalid; }
Это не так сильно, как фон
NSURLSession
(например, у вас есть ограниченное количество времени), но в некоторых случаях это может быть полезно.
Update:
Я думал, что добавлю практический пример того, как делать фоновые загрузки, используя AFNetworking.
-
Сначала определите свой фоновый менеджер.
// // BackgroundSessionManager.h // // Created by Robert Ryan on 10/11/14. // Copyright (c) 2014 Robert Ryan. All rights reserved. // #import "AFHTTPSessionManager.h" @interface BackgroundSessionManager : AFHTTPSessionManager + (instancetype)sharedManager; @property (nonatomic, copy) void (^savedCompletionHandler)(void); @end
и
// // BackgroundSessionManager.m // // Created by Robert Ryan on 10/11/14. // Copyright (c) 2014 Robert Ryan. All rights reserved. // #import "BackgroundSessionManager.h" static NSString * const kBackgroundSessionIdentifier = @"com.domain.backgroundsession"; @implementation BackgroundSessionManager + (instancetype)sharedManager { static id sharedMyManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedMyManager = [[self alloc] init]; }); return sharedMyManager; } - (instancetype)init { NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kBackgroundSessionIdentifier]; self = [super initWithSessionConfiguration:configuration]; if (self) { [self configureDownloadFinished]; // when download done, save file [self configureBackgroundSessionFinished]; // when entire background session done, call completion handler [self configureAuthentication]; // my server uses authentication, so let handle that; if you don't use authentication challenges, you can remove this } return self; } - (void)configureDownloadFinished { // just save the downloaded file to documents folder using filename from URL [self setDownloadTaskDidFinishDownloadingBlock:^NSURL *(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location) { if ([downloadTask.response isKindOfClass:[NSHTTPURLResponse class]]) { NSInteger statusCode = [(NSHTTPURLResponse *)downloadTask.response statusCode]; if (statusCode != 200) { // handle error here, e.g. NSLog(@"%@ failed (statusCode = %ld)", [downloadTask.originalRequest.URL lastPathComponent], statusCode); return nil; } } NSString *filename = [downloadTask.originalRequest.URL lastPathComponent]; NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; NSString *path = [documentsPath stringByAppendingPathComponent:filename]; return [NSURL fileURLWithPath:path]; }]; [self setTaskDidCompleteBlock:^(NSURLSession *session, NSURLSessionTask *task, NSError *error) { if (error) { // handle error here, e.g., NSLog(@"%@: %@", [task.originalRequest.URL lastPathComponent], error); } }]; } - (void)configureBackgroundSessionFinished { typeof(self) __weak weakSelf = self; [self setDidFinishEventsForBackgroundURLSessionBlock:^(NSURLSession *session) { if (weakSelf.savedCompletionHandler) { weakSelf.savedCompletionHandler(); weakSelf.savedCompletionHandler = nil; } }]; } - (void)configureAuthentication { NSURLCredential *myCredential = [NSURLCredential credentialWithUser:@"userid" password:@"password" persistence:NSURLCredentialPersistenceForSession]; [self setTaskDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *credential) { if (challenge.previousFailureCount == 0) { *credential = myCredential; return NSURLSessionAuthChallengeUseCredential; } else { return NSURLSessionAuthChallengePerformDefaultHandling; } }]; } @end
-
Убедитесь, что делегат приложения сохраняет обработчик завершения (при необходимости создает экземпляр фонового сеанса):
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler { NSAssert([[BackgroundSessionManager sharedManager].session.configuration.identifier isEqualToString:identifier], @"Identifiers didn't match"); [BackgroundSessionManager sharedManager].savedCompletionHandler = completionHandler; }
-
Затем запустите загрузку:
for (NSString *filename in filenames) { NSURL *url = [baseURL URLByAppendingPathComponent:filename]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [[[BackgroundSessionManager sharedManager] downloadTaskWithRequest:request progress:nil destination:nil completionHandler:nil] resume]; }
Заметьте, я не поставляю ни один из этих блоков, связанных с задачей, потому что они не являются надежными для фоновых сеансов. (Исходные загрузки продолжаются даже после того, как приложение завершено, и эти блоки уже давно исчезли.) Нужно полагаться на уровень сеанса, легко воссозданный
setDownloadTaskDidFinishDownloadingBlock
.
Очевидно, что это простой пример (только один фоновый объект сеанса, просто сохранение файлов в папку документов с использованием последнего компонента URL как имя файла и т.д.), но, надеюсь, он иллюстрирует шаблон.