问题
What's the best approach to make sure that an upload works?
I need to upload images, to a server and make sure, that the upload has worked. If, for whatever reason, the upload did not work, I'll have to retry later.
Currently, I'm using NSUrlSession to upload the image:
- (void)didCreateSignature:(UIImage *)image {
BLog();
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *imageFile = [NSString stringWithFormat:@"%@/%@", documentsDirectory,@"test.png"];
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:imageFile atomically:YES];
while (![[NSFileManager defaultManager] fileExistsAtPath:imageFile]) {
[NSThread sleepForTimeInterval:.5];
}
NSURLSession *session = [self backgroundSession];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:self.uploadUrl]];
[request setHTTPMethod:@"POST"];
[request addValue:@"image/png" forHTTPHeaderField:@"Content-Type"];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:signatureFile]];
[uploadTask resume];
}
Now let's assume that the user does not have internet connection, then the delegate will be fired:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
BLog();
if (error == nil)
{
NSLog(@"Task: %@ completed successfully", task);
}
else
{
NSLog(@"Task: %@ completed with error: %@", [task originalRequest], [error localizedDescription]);
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
self.internetReachability = [Reachability reachabilityForInternetConnection];
[self.internetReachability startNotifier];
});
}
}
According to the Apple Documentation you should retry later if the Reachability status has changed. So i was thinking of adding these tasks to an Array and start each task again once the Internet connection has become available.
Is this the right approach?
And what happens, if the user closes the app (via TaskManager). How can I make sure that these tasks will be resumed ?
回答1:
In the meantime, I solved it by using a custom UploadManager Class and store downloads in Core Data until the upload was successful: (Below Code is not complete, and only shows the basic concept. If anybody is interested in the full implementation, please let me know)
@implementation UploadManager
- (void) uploadImage:(UIImage *)image toUrl:(NSString *)uploadUrl insertIntoDB:(BOOL)insertIntoDB
{
if(insertIntoDB) {
[self insertUploadTaskIntoDBWithImage:image andUploadUrl:uploadUrl];
}
// Background upload only works with files
NSString *fileName = [NSString stringWithFormat:@"%@%@", [uploadUrl sha256], @".png"];
NSString *signatureFile = [NSString stringWithFormat:@"%@/%@", NSTemporaryDirectory(), fileName];
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:signatureFile atomically:YES];
while (![[NSFileManager defaultManager] fileExistsAtPath:signatureFile]) {
[NSThread sleepForTimeInterval:.5];
}
NSURLSession *session = [self backgroundSession];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:uploadUrl]];
[request setHTTPMethod:@"POST"];
[request addValue:@"image/png" forHTTPHeaderField:@"Content-Type"];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:signatureFile]];
[self showNetworkActivity:YES];
self.taskCount++;
[uploadTask resume];
}
-(void) uploadTasksInDatabase
{
NSArray* uploadTasks = [self getAllUploadTasksFromDatabase];
for(UploadTask *task in uploadTasks) {
UIImage *image = [UIImage imageWithData:task.signature];
[self uploadImage:image toUrl:task.uploadUrl insertIntoDB:NO];
}
}
/*
If an application has received an
application:handleEventsForBackgroundURLSession:completionHandler: message, the session
delegate will receive this message to indicate that all messages previously enqueued for this
session have been delivered. At this time it is safe to invoke the previously stored completion
handler, or to begin any internal updates that will result in invoking the completion handler.
*/
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
if (appDelegate.backgroundSessionCompletionHandler) {
void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler;
appDelegate.backgroundSessionCompletionHandler = nil;
completionHandler();
}
self.taskCount = 0;
NSDebug(@"All tasks are finished");
[self clearDatabase];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
BLog();
self.taskCount--;
[[NSNotificationCenter defaultCenter] postNotificationName:[task.originalRequest.URL description] object:self];
if(self.taskCount == 0) {
[self showNetworkActivity:NO];
}
if (error == nil)
{
NSLog(@"Task: %@ completed successfully", [task.originalRequest.URL description]);
[self deleteUploadTaskWithUploadUrl:[task.originalRequest.URL description]];
}
else
{
NSLog(@"Task: %@ completed with error: %@", [task.originalRequest.URL description], [error localizedDescription]);
}
}
In the App delegate insert the following:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Try to reupload all tasks in DB
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
[self.uploadManager uploadTasksInDatabase];
});
return YES;
}
This way it will try to reupload all unfinished tasks whenever the application is started again.
来源:https://stackoverflow.com/questions/23549635/nsurlsession-make-sure-upload-worked