Episode #145

Share Extensions Part 2

12 minutes
Published on November 13, 2014

This video is only available to subscribers. Get access to this video and 572 others.

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

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];
}