4

我需要通过 PUT 将内容上传到 AWS S3,可以使用 NSURLSessionUploadTask 在后台会话中运行。

到目前为止效果很好。但是,一旦上传到 S3 完成,我需要调用我的 API 以将其状态更改为完成。

我使用 AWSS3 创建 S3 请求,然后根据此 SO answer将其复制到 NSURLSessionUploadTask 。这在前台和后台运行,并将文件 ok 上传到 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 {
}

编辑我也尝试了以下方法。任务像以前一样创建,当我恢复任务时仍然不会触发。

- (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
    ...
}
4

2 回答 2

1

正如文档NSURLSessionDidFinishEventsForBackgroundURLSession所说:

在 iOS 中,当后台传输完成或需要凭据时,如果您的应用程序不再运行,您的应用程序会在后台自动重新启动,并且会向应用程序UIApplicationDelegate发送一条application:handleEventsForBackgroundURLSession:completionHandler:消息。此调用包含导致您的应用程序启动的会话的标识符。然后,您的应用应在创建具有相同标识符的后台配置对象并使用该配置创建会话之前存储该完成处理程序。新创建的会话会自动与正在进行的后台活动重新关联。

当您的应用稍后收到一条URLSessionDidFinishEventsForBackgroundURLSession:消息时,这表明之前为此会话排队的所有消息都已交付,并且现在可以安全地调用先前存储的完成处理程序或开始任何可能导致调用完成处理程序的内部更新。

所以,handleEventsForBackgroundURLSession应该保存completionHandler并重新实例化背景NSURLSession。那么,URLSessionDidFinishEventsForBackgroundURLSession应该调用那个completionHandler.

handleEventsForBackgroundURLSession当您的应用程序被重新唤醒并被调用时,我看不到您在任何地方重新实例化后台会话。我也没有看到你实现了一个URLSessionDidFinishEventsForBackgroundURLSession会调用它的completionHandler.

于 2014-10-30T01:39:05.130 回答
0

如果在您的应用程序未运行时完成了后台任务,您将不得不在您的 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();    
}
于 2014-04-07T18:35:22.527 回答