nsurlsession - 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
我还注意到,当我的应用程序崩溃时,viewDidLoad
viewController 的方法从我崩溃的应用程序以及我在上面的类中初始化并开始获取的地方被调用。
解决方案
推荐阅读
- php - 如何分解两个数组并在每个循环中使用相同的数组
- python - 我有一个 python 代码和一个 json 文件,我无法将字符串更改为浮动
- python - 如何用函数交换全局变量
- angular - 如何在 p-calendar angular 中设置属性值?
- css - 如何在 Blogger 和评论框上实现暗模式
- c# - 时间:2019-03-10 标签:c#custom DictionarygetStackOverflowException
- python - Python3 SQLAlchemy 删除重复项
- javascript - 使用聚合进行合并
- java - 将 Spring Boot 1.5 迁移到 2.1 后出现 Missing jaxb-runtime api 错误
- c# - 如何检索没有任何属性名称的 JSON 数组?