Внутри нсурлсессия и фоновая передача
Я работаю в проекте, где мне нужно загрузить большой JSON-ответ в фоновом режиме (даже если приложение вылетает).
Я также хочу понять внутреннюю часть службы фоновой передачи, то есть, что iOS делает внутри и как. Таким образом, я сохранил стек вызовов в UserDefaults
,
было 2 сценария.
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];
при создании задачи загрузки она создаст 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
)
Редактировать 1
Я также заметил, что, когда мое приложение падает, viewDidLoad
Вызывается метод viewController, из которого происходит сбой приложения, и из которого я инициализирую вышеуказанный класс и начинаю выборку.