首页 > 解决方案 > nsurlsession 内部和后台传输

问题描述

我在项目中工作,我需要在后台下载大型 JSON 响应(即使应用程序崩溃)。

我还想了解后台传输服务的内部结构,即 iOS 在内部做什么以及如何做。所以我将调用堆栈保存在UserDefaults.

有两种情况。

1)没有应用程序崩溃。

(
Download task created with taskidentifier : 1,
didfinishdownloadingtourl,
File downloaded for taskidentifier : 1,
didCompleteWithError->success for taskIdentifier 1,
)

2)当我在下载开始后使用空指针取消引用手动崩溃应用程序时,结果很奇怪。

(
Download task created with taskidentifier : 1,
Download task created with taskidentifier : 2,
handleEventsForBackgroundURLSession,
didfinishdownloadingtourl,
File downloaded for taskidentifier : 1,
didCompleteWithError->success for taskIdentifier 1,
URLSessionDidFinishEventsForBackgroundURLSession,
didfinishdownloadingtourl,
File downloaded for taskidentifier : 2,
didCompleteWithError->success for taskIdentifier 2
)

我只创建了 1 个任务。iOS 会自动创建第二个任务吗?这里发生了什么。

这是我的代码

#import "KiviSyncLogin.h"
#import "AppDelegate.h"

@implementation KiviSyncLogin

-(instancetype)init{
    self = [super init];
    return self;
}

-(void)startSync{
    if(self.session != nil){
        NSLog(@"session is not nil");
        return;
    }
    self.session = [self setUpSession]; // init session with once token
    NSLog(@"This is session : %@",self.session);
    [self pullDataFromServerWithSession:self.session];
}

-(NSURLSession *)setUpSession{
    static dispatch_once_t onceToken;
    static NSURLSession *session = nil;

    dispatch_once(&onceToken, ^{
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.kivi.login.sync"];
        config.HTTPMaximumConnectionsPerHost = 1;
        session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
    });
    return session;
}

-(void)pullDataFromServerWithSession:(NSURLSession *)session{

    NSString *serverUrlString = @"apiurl";
    NSMutableDictionary *mydic = [[NSMutableDictionary alloc] init];
    [mydic setObject:@"email" forKey:@"email"];
    [mydic setObject:@"password" forKey:@"password"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serverUrlString]];
    request.HTTPMethod = @"POST";
    request.HTTPBody = [[self encodedString:mydic] dataUsingEncoding:NSUTF8StringEncoding];

    self.downloadTask = [self.session downloadTaskWithRequest:request];
    NSLog(@"Download task %@ created with identifier : %ld", self.downloadTask,self.downloadTask.taskIdentifier);

    [self.downloadTask resume];
}


/*  common delegate Start  */

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{

    NSLog(@"file downloaded");
    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    NSURL *documentsDirectory = [URLs objectAtIndex:0];

    NSURL *originalURL = [[downloadTask originalRequest] URL];
    NSURL *destinationURL = [documentsDirectory URLByAppendingPathComponent:[originalURL lastPathComponent]];
    NSError *errorCopy;

    [fileManager removeItemAtURL:destinationURL error:NULL];
    BOOL success = [fileManager copyItemAtURL:location toURL:destinationURL error:&errorCopy];

    if (success){

        /* Store data in database */

    }
    else{
        NSLog(@"Error during the copy: %@", [errorCopy localizedDescription]);
    }


}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {

    if(task.taskIdentifier == self.downloadTask.taskIdentifier){
        if(error){
            NSLog(@"Download task completed with error : %@",error);

            /* Show error that data is not downloaded */

            [self windUpSession];

        }else{

            NSLog(@"Download task completed successfully");

        }

    }
}

-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{

    NSLog(@"Session %@ URL Session Did Finish Events For Background URL Session\n", session);
    dispatch_async(dispatch_get_main_queue(), ^{
        AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
        [session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
            if ([downloadTasks count] == 0 && [uploadTasks count] == 0) {
                if (appDelegate.backgroundTransferCompletionHandler != nil) {
                    NSLog(@"I have completion handler");
                    void(^completionHandler)(void) = appDelegate.backgroundTransferCompletionHandler;
                    appDelegate.backgroundTransferCompletionHandler = nil;
                    [self windUpSession];
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        completionHandler();
                    }];
                }
            }
        }];
    });
}

- (void)windUpSession{
    //[self.session invalidateAndCancel];
    self.session = nil;
    self.downloadTask = nil;
}

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error{

    if(error == nil){
        NSLog(@"Session is invalidated successfully");
    }else{
        NSLog(@"Error while invalidating session");
    }

}

/* Common Delegates End */


/* Helper methods */

-(NSString *)encodedString:(NSDictionary *)mydic{
    NSString *params = @"";//[NSString stringWithFormat:@"email=%@&password=%@",email,token];
    int i = 0;
    for (NSString *key in [mydic allKeys]) {
        NSString *stringToBeAppended;
        if(i++ == 0)
            stringToBeAppended = [NSString stringWithFormat:@"%@=%@",key,mydic[key]];
        else
            stringToBeAppended = [NSString stringWithFormat:@"&%@=%@",key,mydic[key]];
        params = [params stringByAppendingString:stringToBeAppended];
    }
    return params;
}

@end

如果我[self.session finishTasksAndInvalidate];在创建下载任务时添加,它将创建第二个任务但不执行它,因为会话无效。

在这种情况下调用堆栈是(如果我手动 carsh 应用程序。)

(
Download task created with taskidentifier : 1,
Download task created with taskidentifier : 2,
handleEventsForBackgroundURLSession,
didfinishdownloadingtourl,
File downloaded for taskidentifier : 1,
didCompleteWithError->success for taskIdentifier 1,
URLSessionDidFinishEventsForBackgroundURLSession
)

编辑 1

我还注意到,当我的应用程序崩溃时,viewDidLoadviewController 的方法从我崩溃的应用程序以及我在上面的类中初始化并开始获取的地方被调用。

标签: nsurlsessionnsurlsessiondownloadtask

解决方案


推荐阅读