NSURLSession backgroundSession дополнительный вызов API по завершении

Мне нужно загрузить контент в AWS S3 через PUT, который можно запустить в фоновом сеансе с помощью NSURLSessionUploadTask.

Пока это работает отлично. Однако мне нужно затем вызвать мой API после завершения загрузки на S3, чтобы изменить его состояние для завершения.

Я использую AWSS3 для создания запроса S3, а затем копирую его в NSURLSessionUploadTask согласно ТАК ответьте. Это работает как на переднем плане, так и на заднем плане, и загружает файл в S3.

Теперь это та часть, с которой мне нужна помощь. Я пытался использовать оба метода делегата URLSession:task:didCompleteWithError и URLSessionDidFinishEventsForBackgroundURLSession для вызова моего дополнительного запроса API, я использовал как стандартные задачи данных, так и задачи загрузки, которые, похоже, не запускают запрос в фоновом режиме, пока Я снова открываю приложение. В идеале я хотел бы, чтобы они стреляли сразу. Они делают огонь загрузки на переднем плане. Я видел, как Wunderlist делает именно то, что я пытаюсь сделать в фоновом режиме, не знаю, как они это делают.

Вот что у меня есть до сих пор... Любая помощь/предложения были бы потрясающими, это сводит меня с ума! :)

- (IBAction)upload:(id)sender {
  AmazonS3Client *s3Client = [[AmazonS3Client alloc] initWithAccessKey:@"ABC" withSecretKey:@"123"];

  NSString *identifier = [NSString stringWithFormat:@"com.journeyhq.backgroundSession.%@-%@", @"S3", @"ATTACHMENT_REMOTE_ID"];
  NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];
  NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];

  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString *documentsDirectory = [paths objectAtIndex:0];
  NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"img.jpg"];
  NSURL *fromFile = [NSURL fileURLWithPath:filePath isDirectory:NO];

  S3PutObjectRequest *s3Request = [[S3PutObjectRequest alloc] initWithKey:[[NSString stringWithFormat:@"%@__%@__%@", @"ATTACHMENT_ID", @"ATTACHMENT_UUID", @"img.jpg"] lowercaseString] inBucket:@"BUCKET_NAME"];
  s3Request.cannedACL = [S3CannedACL publicReadWrite];

  NSMutableURLRequest *request = [s3Client signS3Request:s3Request];

  NSString *urlString = [NSString stringWithFormat:@"https://%@.%@/%@", @"BUCKET_NAME", @"s3-eu-west-1.amazonaws.com", [[NSString stringWithFormat:@"%@__%@__%@", @"ATTACHMENT_ID", @"ATTACHMENT_UUID", @"img.jpg"] lowercaseString]];
  request.URL = [NSURL URLWithString:urlString];

  NSMutableURLRequest *request2 = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
  [request2 setHTTPMethod:@"PUT"];
  [request2 setAllHTTPHeaderFields:[request allHTTPHeaderFields]];
  [request2 setValue:@"BUCKET_NAME.s3-eu-west-1.amazonaws.com" forHTTPHeaderField:@"Host"];

  NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request2 fromFile:fromFile];
  [uploadTask resume];
}

#pragma - NSURLSessionTaskDelegate

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
    dispatch_async(dispatch_get_main_queue(), ^{
      self.progressView.progress = (float)totalBytesSent / (float) totalBytesExpectedToSend;
    });
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
  NSURLSessionConfiguration *sessionConfig = [session configuration];
  NSString *identifier = [sessionConfig identifier];

  NSLog(@"**identifier**: %@", identifier);

  NSArray *identifierComponents = [identifier componentsSeparatedByString:@"."];
  NSString *lastComponent = [identifierComponents lastObject];
  NSArray *components = [lastComponent componentsSeparatedByString:@"-"];
  NSString *sessionType = [components firstObject];
  NSString *attachmentID = [components lastObject];

  NSLog(@"sessionType: %@", sessionType);
  NSLog(@"attachmentID: %@", attachmentID);

  if (error == nil)
  {
    NSLog(@"Task %@ completed successfully", task);
    if ([sessionType isEqualToString:@"S3"]) {
        NSString *downloadIdentifier = [NSString stringWithFormat:@"com.journeyhq.backgroundSession.%@-%@", @"s3Completion", attachmentID];
        NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:downloadIdentifier];
        NSURLSession *downloadSession = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];

        NSString *urlString = @"API_COMPLETION_URL";
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]];

        NSURLSessionDownloadTask *downloadTask = [downloadSession downloadTaskWithRequest:request];
        [downloadTask resume];
    }
  }
  else
  {
    NSLog(@"Task %@ completed with error: %@", task,
          [error localizedDescription]);
  }
  task = nil;

}

AppDelegate.h

- (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier
completionHandler:(void (^)())completionHandler {
}

EDIT Я также пробовал следующее. Задача создается, как и раньше, по-прежнему не запускается, когда я возобновляю задачу.

- (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier
completionHandler:(void (^)())completionHandler {

    NSDictionary *userInfo = @{
        @"completionHandler" : completionHandler,
        @"sessionIdentifier" : identifier
    };

    [[NSNotificationCenter defaultCenter]
        postNotificationName:@"BackgroundTransferNotification"
        object:nil
        userInfo:userInfo];
}

Затем в контроллере представления

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(handleBackgroundTransfer:)
     name:@"BackgroundTransferNotification"
     object:nil];
}

- (void)handleBackgroundTransfer:(NSNotification *)notification {
    // handle the API call as shown in original example
    ...
}

person Steve P. Sharpe    schedule 07.04.2014    source источник


Ответы (2)


Как говорится в документации для NSURLSessionDidFinishEventsForBackgroundURLSession:

В iOS, когда фоновая передача завершается или требуются учетные данные, если ваше приложение больше не работает, ваше приложение автоматически перезапускается в фоновом режиме, и UIApplicationDelegate приложения отправляется сообщение application:handleEventsForBackgroundURLSession:completionHandler:. Этот вызов содержит идентификатор сеанса, вызвавшего запуск вашего приложения. Затем ваше приложение должно сохранить этот обработчик завершения перед созданием объекта фоновой конфигурации с тем же идентификатором и созданием сеанса с этой конфигурацией. Вновь созданный сеанс автоматически повторно связывается с текущей фоновой активностью.

Когда ваше приложение позже получает сообщение URLSessionDidFinishEventsForBackgroundURLSession:, это означает, что все сообщения, ранее поставленные в очередь для этого сеанса, были доставлены и что теперь можно безопасно вызывать ранее сохраненный обработчик завершения или начинать любые внутренние обновления, которые могут привести к вызову обработчика завершения. .

Итак, handleEventsForBackgroundURLSession следует сохранить completionHandler и воссоздать фон NSURLSession. Затем URLSessionDidFinishEventsForBackgroundURLSession должен позвонить этому completionHandler.

Я не вижу, чтобы вы повторно создавали фоновый сеанс где-либо, когда ваше приложение пробуждается и вызывается handleEventsForBackgroundURLSession. Я также не вижу, чтобы вы реализовали URLSessionDidFinishEventsForBackgroundURLSession, который будет вызывать этот completionHandler.

person Rob    schedule 30.10.2014

Если фоновая задача завершается, когда ваше приложение не запущено, вам придется обрабатывать ее в своем AppDelegate, чего вы, похоже, не делаете.

Что-то вроде этого должно работать:

- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {

    // the identifier you used to create the background session
    NSString *identifier = [NSString stringWithFormat:@"com.journeyhq.backgroundSession.%@-%@", @"S3", @"ATTACHMENT_REMOTE_ID"];

    if ([identifier isEqualToString:identifier]) {
        // call your API here
    }

    // call the handler block so the app can exit again
    completionHandler();    
}
person Hugo Sousa    schedule 07.04.2014
comment
Спасибо за ответ Хьюго. Я пробовал это раньше, хотя и не непосредственно внутри делегата приложения, а через уведомление, запускаемое внутри handleEventsForBackgroundURLSession, которое затем обрабатывается в контроллере представления. К сожалению, результат был тот же. Он создает задачу, но просто не запускается, хотя я вызываю возобновление задачи, если я не открою приложение, тогда задача запускается. Я попробовал это прямо внутри handleEventsForBackgroundURLSession, и он все еще не срабатывает. - person Steve P. Sharpe; 07.04.2014
comment
Не могли бы вы опубликовать код, который вы пробовали, в методе делегата приложения? - person Hugo Sousa; 10.04.2014
comment
Вы когда-нибудь решали это? у меня точно такая же проблема - person d0n13; 03.03.2016