我需要通过 PUT 将内容上传到 AWS S3,这些内容可以使用 NSURLSessionUploadTask 在后台会话中运行。
到目前为止,它工作得很好。但是,一旦上传到 S3 完成,我需要调用我的 API,以更改其完成状态。
我AWSS3创建S3请求,然后根据这个SO答案将其复制到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 {
}
编辑 我也尝试了以下方法。任务像以前一样创建,当我恢复任务时仍然没有触发。
- (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
...
}
正如NSURLSessionDidFinishEventsForBackgroundURLSession
的文档所说:
在 iOS 中,当后台传输完成或需要凭据时,如果您的应用不再运行,您的应用将在后台自动重新启动,并且应用的
UIApplicationDelegate
将发送一条application:handleEventsForBackgroundURLSession:completionHandler:
消息。此调用包含导致应用启动的会话的标识符。然后,应用应先存储该完成处理程序,然后再创建具有相同标识符的后台配置对象,并使用该配置创建会话。新创建的会话会自动与正在进行的后台活动重新关联。当你的应用稍后收到
URLSessionDidFinishEventsForBackgroundURLSession:
消息时,这表示以前为此会话排队的所有消息都已传递,现在可以安全地调用以前存储的完成处理程序或开始可能导致调用完成处理程序的任何内部更新。
因此,handleEventsForBackgroundURLSession
应该保存completionHandler
并重新实例化后台NSURLSession
。然后,URLSessionDidFinishEventsForBackgroundURLSession
应该称之为completionHandler
。
当您的应用程序被重新唤醒并调用handleEventsForBackgroundURLSession
时,我没有看到您在任何地方重新实例化后台会话。我也没有看到您实现一个将调用该completionHandler
的URLSessionDidFinishEventsForBackgroundURLSession
.
如果在应用未运行时完成后台任务,则必须在 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();
}