iOS多线程和网络

提供的功能
  • 通过URL将数据下载到内存
  • 通过URL将数据下载到文件系统
  • 将数据上传到指定的URL
  • 在后台完成上述操作(支持后台网络操作,除非用户强行关闭应用程序)

NSURLConnection和NSURLSession的关系示意图

在这里插入图片描述
NSURLSession中的请求都看做一个请求任务(task)。

NSURLSession的Task继承关系如下:
在这里插入图片描述

  • NSURLSessionTask 是一个抽象类,提供了一些基本的方法
  • NSURLSessionDataTask 是一个具体的 task 类,可以获取数据
  • NSURLSessionDownloadTask 是一个具体的 task 类,可以下载数据
  • NSURLSessionUploadTask 是一个具体的 task 类,可以上传数据
  • NSURLSessionStreamTasj 是一个具体的 task 类,以流的方式请求数据,使用较少

每一个类都有对应的协议。

代码体现

基本使用
- (void)sesssion {    
    // 1. url    
    NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/demo.json"];    
    // 2. 如果使用 GET 请求,request 可以省略    
    // session - 苹果为了方便程序员的开发,提供了一个全局的 session 单例 (我们是直接获取的,不需要创建)    
    NSURLSession *session = [NSURLSession sharedSession];    
    // 3. 数据任务 - 所有的任务,都是由session 发起的    
    NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {       
        // 4.将得到数据进行返序列化        
        NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]);    	}];    
    // 5. 启动任务(一定要启动任务)    
    [task resume];
}

注意以上发起的DataTask任务中:

- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

  • 该方法中会自动将 url 转换为一个请求对象(NSURLRequest),并且该请求对象是 GET 请求方式
  • 回调方法是在子线程中运行的,所以如果在回调方法中刷新 UI 必须回到主线程中
  • 回调方法中有两个参数 response / error,这两个参数和 该消息的接受者(即 NSURLSessionDataTask 对象)中的 response / error 是指同一个内容,即地址相同
  • 使用该方法的缺点是不能监听获取数据的进度,因为只有当全部请求完数据后,才会调用这个方法,也就是说,data 中的数据是请求的全部数据.

// @param request 请求对象

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

withRequest方法可以手动设置请求对象,这样就可以指定GET/POST中的任何一种方法了。而withURL方法只能使用GET方法。

在第三方框架对于 NSURLSession 的基本的封装思路就是这样,给一个 url ,后直接启动就可以了。

- (void)sesssionDemo {    
    // 1. url    
    NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/demo.json"];    
    // 2. 启动会话    
    [self taskWithURL:url];
}

- (void)taskWithURL:(NSURL *)url {    
    // 1. 全局会话发起数据任务 - 所有的任务,都是由session 发起的    
    [[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {        
        NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]);    }] resume];
}

但是使用单例类的缺点就是不能实时监控获取数据的情况。因为以上两种方法中只有当请求完全部的数据后才会调用回调方法,如果想要实时获取并监控请求进度,就要设置代理方法。

NSURLSession下载进度跟进

下载进度的跟进是通过代理实现的。

需要跟踪进度,就需要使用代理,代理是一对一的关系,我就要使用一个自定义的会话。

@property(strong, nonatomic)NSURLSession *session;
  • 注意delegate Queue如果是传代理方法在子线程中运行时,则UI的刷新要注意在主线程进行。

  • 而主队列是[NSOperationQueue mainQueue]

  • 如何选择队列: 网络访问结束后,如果不需要做复杂的操作,可以指定主队列,这样不用考虑线程间通讯。

  • 由于下载本身是异步的,不会阻塞主线程工作。

  • 在主线程中代理的话,所有的代理回调,都是在主线程异步的,不会阻塞主线程执行!(主线程异步并不会开辟新的线程, 只是把队列任务放到异步执行, 等待主队列中的当前任务都完成, 主线程空闲的时候, 再来执行需要执行的任务)。

// @param configuration 配置信息对象
// @param delegate 代理对象
// @param queue 代理方法在哪个线程中运行,如果传 nil 则会在子线程中运行代理方法
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;

- (void)sessionDownload {    
// 1. url    
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/123.mp4"];    
// 2. 下载    
// 进度跟进:代理,苹果的头文件中,代理都是定义在后面的    
// 注意:如果要跟进下载进度,不能使用全局会话    
// 这个方法是和下面那个方法是矛盾的,使用了这个方法下面那个必须实现的代理方法是不能使用的    
//    
[[self.session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {    
    //        
    // 这个是一个完成的输出    
    //        
    NSLog(@"%@ %@", location.path, [NSThread currentThread]);    
    //    
}] resume];    

要想使用下面的代理方法的话:直接使用如下方法下载数据。

// 直接启动任务,可以通过代理跟踪进度    
[[self.session downloadTaskWithURL:url] resume];}

以下是必须实现的协议方法,表示下载完成了

  • location表示文件存储的路径
 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
 };

以下是断点续传,可以什么都不写,这个是用来实现下载跟进的

在恢复下载的时候可以调用该方法:

  • fileOffset表示从什么地方开始下载
  • expectedTotalBytes表示文件的总大小。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
};

以下方法是用来进行下载跟进的 ,下载进度的跟进存在的问题,存在一个峰值的问题。

  • downloadTask 表示下载任务

  • bytesWritten 表示本次写入的数据大小

  • totalBytesWritten 下载的数据总大小

  • totalBytesExpectedToWrite 文件的总大小

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{    // 下载的进度    
    float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;    
    // 打印下载的进度   
    NSLog(@"progress = %f",progress);
}
NSURLSession的断点续传问题

断点续传主要考虑的是将会话暂停继续还是将任务进行暂停继续。

NSURLSession 提供的断点续传存在的问题,不能"停",应用程序不能终止,一旦终止,续传数据就丢失了,下次还需要重头再来!

@property (nonatomic, strong) NSURLSession *session;/// 下载任务

@property (nonatomic, strong) NSURLSessionDownloadTask *task;/// 续传数据
  • 启动下载任务
- (IBAction)start {
	// 1. url    
    NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.2.dmg"];
    // 发起下载会话   
	self.task = [self.session downloadTaskWithURL:url]; 
	 // 启动任务
	[self.task resume]; 
}
  • 暂停下载任务
/// 暂停 - 下载任务
- (IBAction)pause {    
    NSLog(@"暂停");    
    // 使用需要续传的数据,暂停下载任务    
    // 暂停方法,只需要执行一次,被暂停就不用再次暂停    
    /**     1. 所有的任务都是由会话发起的     
    		2. 当任务启动后,会话会对任务进行强引用     
    		3. 一旦任务被取消,意味着会话不再引用任务,一个 weak 对象没有其他对象强引用,会被立即释放     
    		4. 如果属性 是 strong,除了会话之外,vc同样对任务强引用(session 为什么要使用强引用)     
    */     
    // 这个是取消下载任务 (达到暂停的目的)    
    [self.task cancelByProducingResumeData:^(NSData *resumeData) {        
        NSLog(@"---> %tu", resumeData);        
        // 记录续传数据        
        self.resumeData = resumeData; 
        // 将暂停时候的数据保存起来(如果不保存下次启动的时候是没有数据的)        
        // 释放了下载任务        
        self.task = nil; 
        // 将下载任务释放避免多次暂停的问题    
    }];
}
  • 继续下载任务
- (IBAction)resume {    
    // 需要避免下载任务重复创建    
    if (self.resumeData == nil) {        
        NSLog(@"没有续传数据");        
        return;    
    }    
    // 使用续传数据,重新实例化下载任务    
    // 一旦续传的下载任务被建立之后,续传数据就没有用了!    
    self.task = [self.session downloadTaskWithResumeData:self.resumeData];    				self.resumeData = nil;    
    [self.task resume];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值