In this episode we continue our building of a share extension. This time we focus on performing the actual upload in the background, sharing a background session with our application through the App Group capability.
Episode Links Episode Source Code The first thing we need to do to share a background session between the extension and the containing application it to create an App Group. Add the App Group Capability Add the App Group Capability to your project's application target as well as the extension target. You'll have to create a new group with the format following this pattern: group.<your.bundle.identifier>. In some cases Xcode won't be able to create the appropriate settings and you'll have to do it in the portal, but the steps are fairly straight-forward. Configure the Background Session In the ImgShareKit target, open up ImgurApi.m and add this code to the initializer: self.backgroundSessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.nsscreencast.ImgShare.BackgroundSession"]; self.backgroundSessionConfig.sharedContainerIdentifier = @"group.com.nsscreencast.ImgShare"; self.backgroundSession = [NSURLSession sessionWithConfiguration:self.backgroundSessionConfig delegate:self delegateQueue:[NSOperationQueue mainQueue] ]; Here we create the background session with an identifier. We then set the sharedContainerIdentifier to the App Group we created earlier. Finally we create the background session, providing the delegate and delegateQueue. Background session require this and will not work with the block-based usage. Keep Track of Callbacks and Request Data Since we're using a shared instance to coordinate access to the API, we have to have a safe way of coordinating callbacks and the temporary state of incoming data for each background session. We could store this in a variable, however multiple downloads for different identifiers could be added, which would cause the last caller to overwrite important state and cause the wrong callback to be called. Instead we'll use dictionaries keyed off of the identifier. @property (nonatomic, strong) NSMutableDictionary *callbacks; @property (nonatomic, strong) NSMutableDictionary *requestData; We'll initialize these at the bottom of init: self.callbacks = [NSMutableDictionary dictionary]; self.requestData = [NSMutableDictionary dictionary]; Implementing the Upload Method - (void)uploadImageAtURL:(NSURL *)imageURL withTitle:(NSString *)title completion:(void (^)(BOOL success, NSError *error))completion { NSURL *url = [self URLForPath:@"image"]; NSLog(@"Posting %@ to %@", imageURL, [url path]); NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; request.allHTTPHeaderFields = @{ @"Authorization": [NSString stringWithFormat:@"Client-ID %@", self.clientID] }; NSURLSessionUploadTask *upload = [self.backgroundSession uploadTaskWithRequest:request fromFile:imageURL]; self.callbacks[upload] = [completion copy]; [upload resume]; } Here we save off the completion callback so we can call it later. Storing bytes as they are downloaded Next we'll receive one or more callbacks with data we need to hang onto to assemble the final response: - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { if (!self.requestData[dataTask]) { self.requestData[dataTask] = [NSMutableData data]; } NSMutableData *taskData = self.requestData[dataTask]; [taskData appendData:data]; } Handling the Completion and Cleaning Up - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { void (^callback)(BOOL success, NSError *error) = self.callbacks[task]; if (error) { callback(NO, error); } else { NSLog(@"Upload completed"); NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; NSLog(@"Status code: %d", (int)response.statusCode); NSData *responseData = self.requestData[task]; NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; NSLog(@"Response: %@", responseString); if (response.statusCode == 200) { callback(YES, nil); } else { callback(NO, nil); } } [self.requestData removeObjectForKey:task]; [self.callbacks removeObjectForKey:task]; }