iOS - NSURLSession第二次无法运行

时间:2016-05-11 18:01:43

标签: ios objective-c

我有一个包含进度条的UIView。我想做的很简单,我有一个按钮,用户点击该按钮,应用程序下载文件并显示进度条中的进度。我可以在用户第一次点击下载按钮时执行此操作。但是,当用户再次单击第二次下载时,不会调用NSURLSession个委托。

我的UIView .m

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self configure];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self configure];
    }
    return self;
}

-(void)configure
{
    [self createSpinner];
    [self createProgressBar];

    NSArray *URLs = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    self.docDirectoryURL = [URLs objectAtIndex:0];

    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.tinkytickles"];
    sessionConfiguration.HTTPMaximumConnectionsPerHost = 1;
    self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                 delegate:self
                                            delegateQueue:nil];
}

-(void)createSpinner
{
    [self setBackgroundColor:[UIColor colorWithWhite:1.0f alpha:0.5f]];

    spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    [self addSubview:spinner];
    [spinner setColor:original_new_dark_grey];
    [spinner setUserInteractionEnabled:NO];
    [spinner setCenter:CGPointMake([[UIScreen mainScreen] bounds].size.width/2, [[UIScreen mainScreen] bounds].size.height/2)];
    [spinner setFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
    [spinner startAnimating];
}

-(void)createProgressBar
{
    self.progressBar = [[TYMProgressBarView alloc] initWithFrame:CGRectMake(0, 0, 280, 15)];
    [self.progressBar setBarBackgroundColor:[UIColor whiteColor]];
    [self.progressBar setBarBorderColor:original_new_dark_grey];
    [self.progressBar setBarFillColor:original_new_dark_grey];
    [self.progressBar setBarBorderWidth:1.0f];
    [self addSubview:self.progressBar];
    [self.progressBar setCenter:CGPointMake([[UIScreen mainScreen] bounds].size.width/2, [[UIScreen mainScreen] bounds].size.height/2)];
    [self.progressBar setHidden:YES];

    self.label = [[UILabel alloc] initWithFrame:CGRectMake(self.progressBar.frame.origin.x, self.progressBar.frame.origin.y - 30, self.progressBar.frame.size.width, 25)];
    [self.label setText:NSLocalizedString(locDownloading, nil)];
    [self.label setTextAlignment:NSTextAlignmentCenter];
    [self.label setTextColor:original_new_dark_grey];
    [self.label setFont:quicksand_14];
    [self addSubview:self.label];
    [self.label setHidden:YES];
}

-(void)showProgressBarWithProgress:(CGFloat)progress withText:(NSString *)text
{
    [spinner setHidden:YES];

    [self.label setText:[NSString stringWithFormat:NSLocalizedString(locDownloadingAt, nil), text]];
    [self.label setHidden:NO];
    [self.progressBar setHidden:NO];
    [self.progressBar setProgress:progress];
}


-(void)stopAnimating
{
    [spinner stopAnimating];
}

-(void)startDownloadingURL:(PromoterDownloadInfo *)downloadInfo
{
    info = downloadInfo;

    if (!info.isDownloading)
    {
        if (info.taskIdentifier == -1)
        {
            info.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:info.downloadSource]];
            info.taskIdentifier = info.downloadTask.taskIdentifier;
            [info.downloadTask resume];
        }
        else
        {
            info.downloadTask = [self.session downloadTaskWithResumeData:info.taskResumeData];
            [info.downloadTask resume];
            info.taskIdentifier = info.downloadTask.taskIdentifier;
        }
    }
    else
    {
        [info.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
            if (resumeData != nil) {
                info.taskResumeData = [[NSData alloc] initWithData:resumeData];
            }
        }];
    }

    info.isDownloading = !info.isDownloading;
}

-(void)stopDownload:(PromoterDownloadInfo *)downloadInfo
{
    if (!info.isDownloading)
    {
        if (info.taskIdentifier == -1)
        {
            info.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:info.downloadSource]];
        }
        else
        {
            info.downloadTask = [self.session downloadTaskWithResumeData:info.taskResumeData];
        }

        info.taskIdentifier = info.downloadTask.taskIdentifier;
        [info.downloadTask resume];
        info.isDownloading = YES;
    }

    [self stopAnimating];
    [self removeFromSuperview];
}

#pragma mark - NSURLSession Delegate method implementation

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSError *error;
    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSString *destinationFilename = downloadTask.originalRequest.URL.lastPathComponent;
    NSURL *destinationURL = [self.docDirectoryURL URLByAppendingPathComponent:destinationFilename];

    if ([fileManager fileExistsAtPath:[destinationURL path]]) {
        [fileManager removeItemAtURL:destinationURL error:nil];
    }

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

    if (success) {
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [self stopAnimating];
            [self removeFromSuperview];
        }];
    }
    else
    {
        NSLog(@"Unable to copy temp file. Error: %@", [error localizedDescription]);
    }
}


-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    if (error != nil) {
        NSLog(@"Download completed with error: %@", [error localizedDescription]);
    }
    else{
        NSLog(@"Download finished successfully.");
    }
}


-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    if (totalBytesExpectedToWrite == NSURLSessionTransferSizeUnknown) {
        NSLog(@"Unknown transfer size");
    }
    else
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            info.downloadProgress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
            [self showProgressBarWithProgress:info.downloadProgress withText:info.fileTitle];
        });
    }
}


-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

    // Check if all download tasks have been finished.
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {

        if ([downloadTasks count] == 0) {
            if (appDelegate.backgroundTransferCompletionHandler != nil) {
                // Copy locally the completion handler.
                void(^completionHandler)() = appDelegate.backgroundTransferCompletionHandler;

                // Make nil the backgroundTransferCompletionHandler.
                appDelegate.backgroundTransferCompletionHandler = nil;

                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    // Call the completion handler to tell the system that there are no other background transfers.
                    completionHandler();

                    // Show a local notification when all downloads are over.
                    UILocalNotification *localNotification = [[UILocalNotification alloc] init];
                    localNotification.alertBody = NSLocalizedString(locDownloadComplete, nil);
                    [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
                }];
            }
        }
    }];
}

我像这样使用这个UIView:

PromoterDownloadInfo *info = [[PromoterDownloadInfo alloc] initWithFileTitle:self.title andDownloadSource:@"https://www.mywebsite.com/file.zip"];
PromotersDownloadView *downloadView = [[PromotersDownloadView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.navigationController.view addSubview:downloadView];
[downloadView startDownloadingURL:info];

我第一次点击下载按钮时效果很好。第二次调用NSURLSession didCompleteWithError方法。这是我第二次从日志中获得的内容:

2016-05-12 00:50:47.440 APP[32990:1230071] A background URLSession with identifier com.app already exists!
2016-05-12 00:50:50.614 APP[32990:1230386] Download finished successfully.

我做错了什么?我尝试只创建一次NSURLSessionConfiguration,但这样就不会调用任何委托方法。我该怎么办?

1 个答案:

答案 0 :(得分:1)

你说:

  

我第一次点击下载按钮时效果很好。 ......这是我第二次从日志中获得的信息:

2016-05-12 00:50:47.440 APP[32990:1230071] A background URLSession with  identifier com.app already exists!<br />

该错误指出您只想为给定标识符实例化一个背景NSURLSession(并且您通常只需要/想要一个后台会话)。如果您要实例化多个,那么您将为它们提供唯一标识符,但处理后台会话非常复杂,而不必多次会话。我建议你只想要一个后台会话。

你说:

  

我尝试只创建一次NSURLSessionConfiguration,但这样就不会调用任何委托方法。

是的,您应该有一个会话配置。而且,同样重要的是,只有一个后台会话对象。

我怀疑您的委托对象无法跟踪应更新哪个视图存在问题。或者您可能丢失了对会话对象的引用,并且您的引用为nil。这可能是几件不同的事情,如果不了解你是如何做到这一点的话,很难知道。

我建议将此会话配置代码移出视图,并提供一些可以在任何地方引用的共享实例(例如,单例工作正常,因此您可以从首次需要的任何地方实例化它,无论是从视图还是来自app delegate的handleEventsForBackgroundURLSession方法。)

唯一的挑战是如何跟踪哪些视图跟踪哪些网络请求。您是否希望拥有一个能够跟踪所有未完成请求的视图,无论该视图何时被实例化?如果是这样,您可以使用NSNotificationCenter通知(这样,任何想要通知进度更新的视图都可以观察您的自定义通知)。或者给定视图是否只关心您从该视图发起的请求?在这种情况下,您可能会维护字典,以映射taskIdentifier值,以便视图或对象需要了解状态更新(您可以使用哪种方式)您的会话对象会跟踪哪些视图关注哪些任务)。这取决于您的应用程序的要求。