美文网首页iOS 大神之路IOS技术iOS进阶知识
iOS使用NSURLSession进行下载(包括后台下载,断点下

iOS使用NSURLSession进行下载(包括后台下载,断点下

作者: HK_Hank | 来源:发表于2016-08-14 22:30 被阅读13553次

从iOS7以来,苹果推出NSURLSession后,iOS现在可以实现真正的后台下载,这对我们iOSer来说是一个福音。

一个 NSURLSession对象可以协调一个或多个 NSURLSessionTask对象,并根据NSURLSessionTask 创建的 NSURLSessionConfiguration 实现不同的功能。使用相同的配置,你也可以创建多组具有相关任务的 NSURLSession 对象。要利用后台传输服务,你将会使用 [NSURLSessionConfiguration backgroundSessionConfiguration]来创建一个会话配置。添加到后台会话的任务在外部进程运行,即使应用程序被挂起,崩溃,或者被杀死,它依然会运行。

下面我们来看看如何使用NSURLSession

下载用到的委托方法

  1. AppDelegate委托方法
//在应用处于后台,且后台任务下载完成时回调
 - (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier 
 completionHandler:(void (^)())completionHandler;
  1. NSURLSession委托方法
/* 在任务下载完成、下载失败
  * 或者是应用被杀掉后,重新启动应用并创建相关identifier的Session时调用
  */
 - (void)URLSession:(NSURLSession *)session
                 task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error;
/* 应用在后台,而且后台所有下载任务完成后,
 * 在所有其他NSURLSession和NSURLSessionDownloadTask委托方法执行完后回调,
 * 可以在该方法中做下载数据管理和UI刷新
  */
 - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;

注:最好将handleEventsForBackgroundURLSessioncompletionHandler保存,在该方法中待所有载数据管理和UI刷新做完后,再调用completionHandler()

  1. NSURLSessionDownloadTask委托方法
/* 下载过程中调用,用于跟踪下载进度
 * bytesWritten为单次下载大小
 * totalBytesWritten为当当前一共下载大小
 * totalBytesExpectedToWrite为文件大小
 */
 - (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
/* 下载恢复时调用
 * 在使用downloadTaskWithResumeData:方法获取到对应NSURLSessionDownloadTask,
 * 并该task调用resume的时候调用
  */
 - (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes;
//下载完成时调用
 - (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location;

注:在URLSession:downloadTask:didFinishDownloadingToURL方法中,location只是一个磁盘上该文件的临时 URL,只是一个临时文件,需要自己使用NSFileManager将文件写到应用的目录下(一般来说这种可以重复获得的内容应该放到cache目录下),因为当你从这个委托方法返回时,该文件将从临时存储中删除。

创建后台下载的操作步骤

后台传输的的实现也十分简单,简单说分为三个步骤:

  1. 创建后台下载用的NSURLSession对象,设置为后台下载类型;
  2. 向这个对象中加入对应的传输的NSURLSessionTask,并开始下载;
  3. 在AppDelegate里实现handleEventsForBackgroundURLSession,以刷新UI及通知系统传输结束。
  4. 实现NSURLSessionDownloadDelegate中必要的代理

下面用代码来说明描述后台下载的流程

首先,我们看下后台下载的时序图

后台下载时序图

具体代码实现

  1. 创建一个后台下载对象
    用dispatch_once创建一个用于后台下载对象,目的是为了保证identifier的唯一,文档不建议对于相同的标识符 (identifier) 创建多个会话对象。这里创建并配置了NSURLSession,将通过backgroundSessionConfiguration其指定为后台session并设定delegate。
 - (NSURLSession *)backgroundURLSession {
    static NSURLSession *session = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSString *identifier = @"com.yourcompany.appId.BackgroundSession";
        NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
        session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                delegate:self
                                           delegateQueue:[NSOperationQueue mainQueue]];
    });
    
    return session;
}
  1. 向其中加入对应的传输用的NSURLSessionTask,并调用resume启动下载。
 - (void)beginDownloadWithUrl:(NSString *)downloadURLString {
    NSURL *downloadURL = [NSURL URLWithString:downloadURLString];
    NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
    NSURLSession *session = [self backgroundURLSession];
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
    [downloadTask resume];
}
  1. 在appDelegate中实现handleEventsForBackgroundURLSession,要注意的是,需要在handleEventsForBackgroundURLSession中必须重新建立一个后台 session 的参照(可以用之前dispatch_once创建的对象),否则 NSURLSessionDownloadDelegateNSURLSessionDelegate 方法会因为没有 对 session 的 delegate 设置而不会被调用。
    然后保存completionHandler()。
 - (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier 
 completionHandler:(void (^)())completionHandler {
       NSURLSession *backgroundSession = [self backgroundURLSession];
       NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession);
       // 保存 completion handler 以在处理 session 事件后更新 UI
       [self addCompletionHandler:completionHandler forSession:identifier];    
}
 - (void)addCompletionHandler:(CompletionHandlerType)handler 
  forSession:(NSString *)identifier {
        if ([self.completionHandlerDictionary objectForKey:identifier]) {
           NSLog(@"Error: Got multiple handlers for a single session identifier.  This should not happen.\n");
        }
        [self.completionHandlerDictionary setObject:handler forKey:identifier];
}

注:handleEventsForBackgroundURLSession方法是在后台下载的所有任务完成后才会调用。如果当后台传输完成时,如果应用程序已经被杀掉,iOS将会在后台启动该应用程序,下载相关的委托方法会在 application:didFinishLaunchingWithOptions:方法被调用之后被调用。

  1. 实现URLSessionDidFinishEventsForBackgroundURLSession,待所有数据处理完成,UI刷新之后在改方法中在调用之前保存的completionHandler()。
//NSURLSessionDelegate委托方法,会在NSURLSessionDownloadDelegate委托方法后执行
 - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
        NSLog(@"Background URL session %@ finished events.\n", session);
        if (session.configuration.identifier) {
            // 调用在 -application:handleEventsForBackgroundURLSession: 中保存的 handler
            [self callCompletionHandlerForSession:session.configuration.identifier];
        }
}
 - (void)callCompletionHandlerForSession:(NSString *)identifier {
        CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey: identifier];
        if (handler) {
           [self.completionHandlerDictionary removeObjectForKey: identifier];
           NSLog(@"Calling completion handler for session %@", identifier);
            handler();
        }
}
  1. 在此,后台下载的基本功能已经具备了,如果还需要监听下载进度和对下载完成数据进行处理,则需要实现上面提到的委托方法
    URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:URLSession:downloadTask:didFinishDownloadingToURL:

关于断点下载

对于断点下载需要考虑几个问题:

  1. 如何暂停下载,暂停后,如何继续下载?
  • 下载失败后,如何恢复下载?
  • 应用被用户杀掉后,如何恢复之前的下载?

针对这几个问题,我们一个来分析

  • 如何暂停下载,暂停后,如何继续下载?
    有两种方法
  • 第一种,使用cancelByProducingResumeData
    /* 对某一个NSURLSessionDownloadTask取消下载,取消后会回调给我们 resumeData,
    * resumeData包含了下载任务的一些状态,之后可以用户恢复下载
   */
   - (void)cancelByProducingResumeData:(void (^)(NSData * resumeData))completionHandler;

调用该方法会触发以下方法,会附带resumeData,用于恢复。

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

对应恢复方法

//通过之前保存的resumeData,获取断点的NSURLSessionTask,调用resume恢复下载
NSURLSessionDownloadTask *task = [[self backgroundURLSession] downloadTaskWithResumeData:resumeData];
[task resume];
  • 第二种,使用NSURLSessionDownloadTask的suspend方法
//暂停
[self.downloadTask suspend];
//恢复
[self.downloadTask resume];

通过以上的两个方法,就可以实现下载的暂停与恢复下载了

  • 下载失败后,如何恢复下载?
    下载失败后,可以通过以下代码来恢复下载
  /* 该方法下载成功和失败都会回调,只是失败的是error是有值的,
  * 在下载失败时,error的userinfo属性可以通过NSURLSessionDownloadTaskResumeData
  * 这个key来取到resumeData(和上面的resumeData是一样的),再通过resumeData恢复下载
  */
 - (void)URLSession:(NSURLSession *)sessiona
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
    if (error) {
        // check if resume data are available
        if ([error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]) {
            NSData *resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
            NSURLSessionTask *task = [[self backgroundURLSession] downloadTaskWithResumeData:resumeData];
            [task resume];
        }
    }
}
  • 应用被用户杀掉后,如何恢复之前的下载?
    在应用被杀掉前,iOS系统保存应用下载sesson的信息,在重新启动应用,并且创建和之前相同identifier的session时(苹果通过identifier找到对应的session数据),iOS系统会对之前下载中的任务进行依次回调URLSession:task:didCompleteWithError:方法,之后可以使用上面提到的下载失败时的处理方法进行恢复下载

知道这些后,看下前台下载的时序图对整个下载流程就了解了。


前台下载时序图.png

关于Session的生命周期,可以阅读 Apple 的 Life Cycle of a URL Session with Custom Delegates 文档,它讲解了所有类型的会话任务的完整生命周期。

后台下载的配置和限制

NSURLSessionConfiguration 允许你设置默认的HTTP头,配置缓存策略,限制使用蜂窝数据等等。其中一个选项是discretionary标志,这个标志允许系统为分配任务进行性能优化。这意味着只有当设备有足够电量时,设备才通过 Wifi 进行数据传输。如果电量低,或者只仅有一个蜂窝连接,传输任务是不会运行的。后台传输总是在 discretionary模式下运行。timeoutIntervalForResource属性,支持资源超时特性。你可以使用这个特性指定你允许完成一个传输所需的最长时间。内容只在有限的时间可用,或者在用户只有有限Wifi带宽的时间内无法下载或上传资源的情况下,你也可以使用这个特性。

最后,我们来说一说使用后台会话的几个限制。作为一个必须实现的委托,您不能对NSURLSession使用简单的基于 block的回调方法。后台启动应用程序,是相对耗费较多资源的,所以总是采用HTTP重定向。后台传输服务只支持HTTP和HTTPS,你不能使用自定义的协议。系统会根据可用的资源进行优化,在任何时候你都不能强制传输任务在后台进行。

另外,要注意的是在后台会话中,NSURLSessionDataTasks 是完全不支持的,你应该只出于短期的,小请求为目的使用这些任务,而不是用来下载或上传。其中发现一些需要注意的点,记录下来。

NSURLSession在iOS10上的Bug

在iOS10上,resumeData不能直接使用,会提示以下错误

*** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
*** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
Invalid resume data for background download. Background downloads must use http or https and must download to an accessible file.

解决方法有点小麻烦,具体解决方法可以参照以下Demo

关于在后台启动新的下载任务,苹果对这方面有限制

大致说明如下:

1.苹果的NSURLSession这个类会维护一个Delay值(即延时执行时间),用于后台启动任务延时执行时使用;
2.当在后台启动一个新任务时,苹果会对这个任务进行延时执行,延时时间苹果那边是有一个默认的延时时间,当后台启动的任务数越多,这个值就会成2的N-1幂倍增长;
3.比如:假设苹果设定的延时时间为Delay。当在后台启动了第一个任务时,这个任务的延时时间为Delay,这个任务会在Delay时间后开始执行;当启动在后台启动第二个任务时,这个任务的延时时间为2*Delay,当启动第三个任务是,该任务的延时执行时间即为2*2*Delay以此类推,在后台启动第N个任务是,该任务的延时执行时间为2(N-1)次方*Delay
4.但是在应用从后台切到前台或者重新启动时,这个延时时间会重置。

所以苹果对在后台启动新的下载或者上传任务时,是有限制的,苹果也是不建议这么处理的

以下为苹果官方说明的链接地址:
https://forums.developer.apple.com/thread/14854

简单的下载Demo

Demo

相关文章

网友评论

  • bd68039da95a:楼主,现在有解决后台连续下载多个任务的方法吗,急
  • 爱可乐爱可乐:楼主的教程还不能支持后台下载,需要再设置backgroundconfig的一个属性:

    config.sessionSendsLaunchEvents = true

    这样才能实现后台任务。
  • NFatalist:需要在后台上传1000多个文件,有办法处理吗? 尝试每个任务完成后开启新的下载任务,但是下载速度非常的慢,有没有方案可以解决新任务开启的延时呢?我看腾讯微云好像没有这方面的限制。而且我如果直接用xcode跑代码下载很快,直接开启软件发现延时很严重
  • LD_左岸:在应用被杀掉前,iOS系统保存应用下载sesson的信息,在重新启动应用,并且创建和之前相同identifier的session时(苹果通过identifier找到对应的session数据),iOS系统会对之前下载中的任务进行依次回调URLSession:task:didCompleteWithError:方法,之后可以使用上面提到的下载失败时的处理方法进行恢复下载

    既然如此 是不是可以说 这个resulmData 在用户杀死app之前iOS已经给我保存了 我只需到时候拿来用即可???
    看了很多博客 他们都是费劲巴列的在周期回调函数- (void)URLSession:(NSURLSession *)session
    downloadTask:(NSURLSessionDownloadTask *)downloadTask
    didWriteData:(int64_t)bytesWritten
    totalBytesWritten:(int64_t)totalBytesWritten
    totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite这里面自己存储的resulmData感觉好麻烦啊!!!
  • LD_左岸:- (void)URLSession:(NSURLSession *)session
    downloadTask:(NSURLSessionDownloadTask *)downloadTask
    didWriteData:(int64_t)bytesWritten
    totalBytesWritten:(int64_t)totalBytesWritten
    totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
    这个方法里能否拿到本次写入的二进制数据 还是说只能拿到本次二进制数据写入的大小 不能拿到二进制数据本身
  • LD_左岸:(lldb) po error.userInfo
    {
    NSErrorFailingURLKey = "http://120.25.226.186:32812/resources/videos/minion_08.mp4";;
    NSErrorFailingURLStringKey = "http://120.25.226.186:32812/resources/videos/minion_08.mp4";;
    NSLocalizedDescription = cancelled;
    }

    该方法下载成功和失败都会回调,只是失败的是error是有值的,
    * 在下载失败时,error的userinfo属性可以通过NSURLSessionDownloadTaskResumeData
    * 这个key来取到resumeData(和上面的resumeData是一样的),再通过resumeData恢复下载

    没这个resulmData值呢
  • qwemm:后台上传视频用这个逻辑可已吗
  • 紫色雨伞:[self.downloadTask suspend] 这个方法好像并不能实现暂停的功能,不知道是不是我使用方法有问题?
  • lloveyouyou:请问楼主,能在下载前设定location的path吗,我想要实现边下边看,谢谢
    HK_Hank:@wangzhewansheng 你这种需求要走
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data;了,这个需要你自己拼接数据了。
  • 楼上那只猫:4.实现URLSessionDidFinishEventsForBackgroundURLSession,待所有数据处理完成,UI刷新之后在改方法中在调用之前保存的completionHandler()

    UI 刷新是在这个方法执行?
    为什么要调用completionHandler(),有什么作用.(文档上也是写的要调用,但也没说明原因)
  • 三人禾飞:BackgroundDownloadDemo 在ios11有问题,在网络切换的时候(正在下载的时候,暂停-下载-切换wifi),下载的文件会出现错误,文件的md5错误
  • boundlessocean:你好,作者,谢谢你的分享,我想问一个问题,NSURLSession在官方文档中介绍说可以在程序不运行的情况下下载任务,我下载了你的demo,没能够在app kill后下载任务,请问你有思路吗?
  • 太空蛙:你好 请教个问题 多个不同的下载任务,前面几个任务完成后就不走didFinishDownloadingToURL 这些回调了 是什么问题? 跟延迟有关系吗?
  • Lonely__M:很好奇,为什么后台下载的时候 在代理方法中的日志为何在控制台不打印?
  • Dosun:不用配置后台下载plist吗??
  • cba66dca63b3:断点下载为啥不用NSURLSessionDownloadTask自带的suspend,和resume?
  • 7e33bcf02f7f:楼主 我又回来了 那个后台传输还是要做 根据你的思路 然后结合那个多个传输兄弟的思路 感觉还是把多任务全部放在appdelegate里面好了 不过现在项目要大概了。群主有时间可以搞个多文件demo呗。:kissing_heart:
  • 就叫K:博主,希望您能看到我的问题,thanks
    我的现在是这个情况,因为我们做的是sdk所以,并没有AppDelegate.m,所以一直没有实现application: handleEventsForBackgroundURLSession: completionHandler:,但是之前的后台下载是一直好使的,不过不知道从什么时候开始,后台下载不行了,手机连着xcode的时候是可以正常后台的,但是手机直接启动,就会过段时间被系统自动杀死,求教,这个怎么解决下?
    就叫K:@HK_Hank 老铁,如果可以的话,加一下我企鹅,626749549,十分感谢
    就叫K:@HK_Hank 我已经按照你的demo在sdk里面实现的方法,但是还是不连xcode程序在后台会被杀死,但是连这xcode debug启动的时候,后台下载是好使的,拔掉线就不行,
    HK_Hank:sdk的话,你可以开放一个方法,让上层实现application方法,在里面调用你的方法就行了。
  • 琥珀之剑:是不是只能真机测试后台下载
    HK_Hank:是的,要用真机测试。
  • 喔牛慢慢爬:楼主,有几个问题:1、在从后台超过1分钟再回到前台,无法恢复下载,暂停后再恢复可以下载;2、关闭程序再次进入下载,有时下载的临时文件丢失,文件从新下载;
  • nzbypl:楼主 仔细研究了下你的代码 很厉害 现在准备做多个任务后台下载了
    bd68039da95a:你实现了吗
  • 卢叁:能加个QQ吗?我QQ1015857193
  • 卢叁:多个 后台下载的任务
  • 卢叁:楼主 多个后台任务怎么做呢 能把这个功能的demo也写出来吗 谢谢了
  • WeiHing:我用了一下demo,手动杀掉程序,下载好像就停住了(因为我再启动程序点击继续下载,是从杀死程序时的进度继续的)。不知道是我操作方式不对呢?
  • Eddie_吉:程序杀死之后重新进入App并没有显示已下载多少,点击继续才会显示,在哪个方法里面写可以让UI在刚进入App的时候就显示已下载多少了
  • edison0428:楼主,我用NSURLSessionDownloadTask的代理方式下载一个Xcode文件,很大,退到后台去,然后也能下载完成大概下载过程有半个小时,这是不是说不需要做特别的后台处理也行后台下载呢
    HK_Hank:这些文章里都说明了的。
    edison0428:@HK_Hank “但是考虑网络问题导致下载中断需要断点下载”你说的是不是这种情况,如果后台下载中断,那么可以在代理中重新开启下载动作? 你所说的后台下载和正常的下载,除了在网络中断的情况下可以做动作或者下载完成后进行ui处理,还有其他区别
    HK_Hank:正常的情况是会后台下完的,但是考虑网络问题导致下载中断需要断点下载,或者下完完成后的UI处理,所以还是要做些处理
  • 破弓:您好,我的项目里要求多个url到了后台后依次下载,所以我改了你的代码
    -(void)downloadSomeUrl
    {
    AppDelegate * delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    NSArray * arr = @[@"http://dl1sw.baidu.com/soft/0a/14228/zhuanma_1.2.0.7.exe?version=2718134150",
    @"http://dlsw.baidu.com/sw-search-sp/soft/20/36560/AshampooMP3CoverFinder_1.0.15.0.1430294531.exe",
    @"http://dlsw.baidu.com/sw-search-sp/soft/90/16405/dvd2mp4_4.2.0.1.exe?version=425256212"];

    delegate.urlArr = [NSMutableArray arrayWithArray:arr];

    [delegate beginDownloadWithUrl:[delegate.urlArr firstObject]];
    }

    - (void)sendLocalNotification {
    [self sendLocalNotificationWithTitle:@"单段下载完成"];
    [_urlArr removeObjectAtIndex:0];
    if (_urlArr.count > 0) {
    [self beginDownloadWithUrl:[_urlArr firstObject]];
    }else{
    [self sendLocalNotificationWithTitle:@"全部下载完成"];
    }
    }
    三个url的资源大小共计66M,但后台下载会比前台下载慢五分钟,我想问问您试过多个url在后台依次下载吗?
    我也试过优酷下两集电视剧共500M,后台下载会比前台下载慢两分钟
    nzbypl:老铁 能看下你后台多个下载吗 我这边又稍微有点不一样 是分段下载的 头都晕了 qq2054461
    破弓:@HK_Hank
    不好意思,顺序下载这边是我没有仔细看文章,直接去看代码了!

    1.
    我还有一点不明白:
    // 你必须重新建立一个后台 seesion 的参照
    // 否则 NSURLSessionDownloadDelegate 和 NSURLSessionDelegate 方法会因为
    // 没有 对 session 的 delegate 设定而不会被调用。参见上面的 backgroundURLSession
    NSURLSession *backgroundSession = [self backgroundURLSession];
    NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession);
    你必须重新建立一个后台seesion的参照什么意思?

    2.
    NSURLSession * backgroundSession = [self backgroundURLSession];
    这个backgroundSession和self.backgroundSession地址是一样的,不是说重新建立吗?我没明白这里的意图

    3.
    我把这两句
    NSURLSession * backgroundSession = [self backgroundURLSession];
    NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession);
    注释了,一样下载,一点影响也没有
    HK_Hank:你是说等第一个任务在后台下载完成后,在后台启动另一个任务?这种情况。苹果会对第二个任务延时执行,这个我文章“关于在后台启动新的下载任务”那部分有说明,我这边还没有尝试顺序下载
  • 9a957efaf40a:大神,- application: handleEventsForBackgroundURLSession: completionHandler:这个方法什么时候被调用?completionHandler这个block是干什么的?
    是不是可以这么理解,当进入后台下载的时候,程序并没有被真正杀死。直到所有下载完成,然后调用这个block,这个时候相当于告诉系统已经完事了,可以真正杀死了?
    如果这样理解的话,URLSessionDidFinishEventsForBackgroundURLSession这个方法就是监控这个session里面所有的task的进度。但是又有个问题,如果我有两个session呢?是不是要在这个方法里面判断哪个session完成了,都完成才调用block?还有文章中<b>需要在handleEventsForBackgroundURLSession中必须重新建立一个后台 session 的参照(可以用之前dispatch_once创建的对象),否则 NSURLSessionDownloadDelegate 和 NSURLSessionDelegate 方法会因为没有 对 session 的 delegate 设置而不会被调用。</b>这个是怎么理解,没看明白。
    PGOne爱吃饺子:真希望作者给你解答一下,我也存在这些疑问,好纠结啊。你现在知道了么
  • 5e26ed09492b:添加到后台会话的任务在外部进程运行,即使应用程序被挂起,崩溃,或者被杀死,它依然会运行,也就是说程序退出后台下载任务还是会执行么?
    HK_Hank:@zhangguowen https://developer.apple.com/reference/foundation/nsurlsessionconfiguration/1407496-backgroundsessionconfigurationwi?language=objc
    以上链接是苹果的官方文档对backgroundSessionConfigurationWithIdentifier:的说明,上面说到
    “Use this method to initialize a configuration object suitable for transferring data files while the app runs in the background. A session configured with this object hands control of the transfers over to the system, which handles the transfers in a separate process. In iOS, this configuration makes it possible for transfers to continue even when the app itself is suspended or terminated.

    If an iOS app is terminated by the system and relaunched, the app can use the same identifier to create a new configuration object and session and retrieve the status of transfers that were in progress at the time of termination. ...”
    5e26ed09492b:应用被用户杀掉后,如何恢复之前的下载,这句话的意思就是程序退出后台下载暂停。这个不就和上面的冲突了么,程序被杀死以后,后台下载到底会不会执行啊,我有点搞不懂
    HK_Hank:是的
  • 低吟浅唱1990:如果需要保持后台一直运行, 每个10分钟上传一次数据,这种方法可行吗?
    就是程序不退出就不会被挂起
    低吟浅唱1990:@HK_Hank 有什么办法可以解决这个问题吗? 假如我蓝牙申请后台权限,这是一只读取蓝牙设备的数据,在上传。还要申请 URL的后台权限吗
    HK_Hank:不行,不会后台一直运行,前台添加的任务完成后,应用就被挂起了。在后台添加任务的话,不会立马执行,苹果会让这个任务延时一段时间后才执行。。
  • 高鹏程的替身:- (void)application:(UIApplication *)application
    handleEventsForBackgroundURLSession:(NSString *)identifier
    completionHandler:(void (^)())completionHandler
    有没有人能告诉我 这个completionHandler里面到底是啥?为啥不马上执行,而要在URLSessionDidFinishEventsForBackgroundURLSession 这个回调里去执行呢?
    HK_Hank:@高鹏程的替身 我的理解,执行completion后,系统会把应用再挂起。之后处理下载数据和刷新UI就无效了。
  • 街灯下de橱窗:虽然不再报Invalid resume data for background download. Background downloads must use http or https and must download to an accessible file. 这个错误
    但是依然显示
    *** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
  • 街灯下de橱窗:感谢,我也遇到iOS10这个坑了
  • 尘埃_落定:感谢博主,遇到iOS10的坑了 :smile: 哪天苹果修复了,代码是不是还要改 :sweat:
    HK_Hank:@尘埃_落定 应该可以兼容,你有时间可以测试一下。即使修复了,发布的版本bug还是在的。
  • 请叫我攻城狮:我在后台做下载暂停 恢复的时候老是报这个错 博主知道是什么原因吗 我找了好久都不知道为啥
    Invalid resume data for background download. Background downloads must use http or https and must download to an accessible file
    63f973f1dbb5:@HK_Hank 我在iOS8下也遇到了Invalid resume data for background download. Background downloads must use http or https and must download to an accessible file 这个问题不知道该怎么解决啊
    请叫我攻城狮:@HK_Hank 看了 我一直在xcode8 上做测试 测了一周 查了一周 都没有结果 原来是ios10的问题,谢谢啦
    HK_Hank:@请叫我攻城狮 这是在iOS10上出现的吧,iOS10上要对resumedata做下处理,你看下我的demo
  • 张云龙:2016-09-26 09:46:17.815 BackgroundDownloadDemo[1076:31501] downloadTask:1 percent:4.87%
    2016-09-26 09:46:17.928 BackgroundDownloadDemo[1076:31501] downloadTask:1 percent:5.09%
    断网之后的error:
    2016-09-26 09:46:18.095 BackgroundDownloadDemo[1076:31501] downloadTask:1 percent:5.26%
    (lldb) po error
    Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={NSLocalizedDescription=unsupported URL}

    并没有收到resumeData,当再次将网络连接,没有继续下载。然后退出程序也没有走到error那个代理方法中,所以没有继续下载。
    此时点击下载,是重新下载,而不是继续下载:
    2016-09-26 09:49:18.977 BackgroundDownloadDemo[1138:34071] downloadTask:1 percent:0.20%
    2016-09-26 09:49:19.083 BackgroundDownloadDemo[1138:34071] downloadTask:1 percent:0.41%
    2016-09-26 09:49:19.186 BackgroundDownloadDemo[1138:34071] downloadTask:1 percent:0.63%
    2016-09-26 09:49:19.341 BackgroundDownloadDemo[1138:34071] downloadTask:1 percent:0.78%
    2016-09-26 09:49:19.495 BackgroundDownloadDemo[1138:34071] downloadTask:1 percent:1.00%
    2016-09-26 09:49:19.599 BackgroundDownloadDemo[1138:34071] downloadTask:1 percent:1.22%
    HK_Hank:@iyxo90 iOS10上不能恢复下载的问题解决了,你可以下我的demo看看
    张云龙:@iyxo90 根据系统的resumeData自己创建resumeData,不过比较麻烦的。
    0add96eb9a27:@张云龙 请问这个问题你发什么解决办法吗?
  • 张云龙:正在下载的时候网络中断,网络恢复后如何继续下载?或者在网络中断后,退出程序,然后再次进入程序如何继续下载?遇到这个棘手的问题,还没有解决,博主有方案吗?
    张云龙:@HK_Hank 我用你的demo测试的,在下载中断网后无法再次下载,再次启动程序不会进入失败的方法。等周一我上班了把数据给你看下。另外这个方法在iOS10和Xcode全部失效,我已经解决了,周一聊吧
    HK_Hank:@张云龙 我测试了一下,
    1.断网后,下载会停止,网络恢复时候,下载任务会自动恢复。你是在什么场景下进入下载失败回调,error里取不到resumedata?
    2.退出应用,再次进入应用,重新初始化backgroundURLSession时,没有进入URLSession:task:didCompleteWithError:回调吗?这个时候应该可以从error中取到resumedata
    张云龙:补充下,由于突然断网,导致的下载失败在error里是获取不到resumedata的

  • Tate_code:请问下几个问题,希望你能回复,thank。
    1、你测试的时候电量一般多低就没法后台下载了?
    2、有一些app都有一个选项,‘是否允许移动网络下载的’,这是不是意味着它并没有使用NSURLSession,而是用了其它的方法实现?
    Tate_code:@HK_Hank 音乐这种方式我已经试过了,已被拒,并不好。。。
    Tate_code:@HK_Hank 我也这么想的,应该是前台才能移动网络下载,刚才是因为评论没法编辑,所以只能删除重新评论了~ 感谢你的回复!
    HK_Hank:@Tate_zwt
    之前我回复你了。怎么被删了,那我在回复一次吧
    1.低电量这个我也没测试过
    2.我认为‘是否允许移动网络下载的’这个选择,只是针对应用在前台的时候才有效,在移动网络下,后台下载是被停止的。如果在移动网络下还是可以后台下载,那应该是通过其他途径让应用在后台不被苹果干掉,我知道是在后台循环播放无声的音乐这种方式,但是这个也是有风险的,可能会被苹果拒掉。
  • zero000:demo中- application: handleEventsForBackgroundURLSession: completionHandler: 和-URLSessionDidFinishEventsForBackgroundURLSession:方法没有执行您知道是什么原因吗?
    HK_Hank:@zero000 我真机测过是OK的,你是怎么看出来下载停止了?后台下载的时候是不会打日志的。那两个方法是在后台下载完成后才会执行。
    zero000:@HK_Hank 您那边真机测试是OK的吗,我这里真机测试 回到后台下载暂停了,切换到前台才可以
    HK_Hank:@zero000 你是用真机还是模拟器测的?模拟器好像不会执行。
  • 950ef1ec41d0:有没有swift的
    HK_Hank:@烈焰狂舞 暂时没有额
  • 张云龙:写得不错
    张云龙:@不会唱歌的小逗比 thank u
    iOS小吴:写的很不错的
    HK_Hank:@张云龙 谢谢!
  • 9a6940db9030:有没有demo :relieved:
    HK_Hank:@坏人一起撸 demo之后会补充。

本文标题:iOS使用NSURLSession进行下载(包括后台下载,断点下

本文链接:https://www.haomeiwen.com/subject/zbhfsttx.html