IOS 开发进阶--多线程和网络--NSURLSession详细解

来源:互联网 发布:python网络爬虫源码 编辑:程序博客网 时间:2024/05/22 11:30

URLSession 的基本概念:

  • NSURLSession是在 ios7.0推出的,用于替代 NSURLSession(本身就是与与NSURLConnection是并列
  • NSURLSession支持后台网络操作,除非用户强行关闭应用程序。

1、NSURLSession提供的功能:

  1. 通过URL将数据下载到内存
  2. 通过URL将数据下载到文件系统
  3. 将数据上传到指定URL
  4. 在后台完成上述功能

对于小型数据,例如用户登录、下载小图像、JSON & XML仍然使用NSURLConnection的异步或同步方法即可

NSURLConnection 和 NSURLSession的关系示意图
这里写图片描述

(NSURLSession可以在任何时候进行挂起和执行操作)

2、NSURLSession的代码体现

在使用NSURLSession的时候 URLRequest很大的程度上是可以被 NSURLSessionConfigration 替代。
如果使用 get 请求,URLRequest是可以省略的。

1. NSURLSession的基本的使用练习

这是一个最常规的写法:- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    [self sesssionDemo];}// MARK: - 常规代码- (void)sesssionDemo {    // 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]);    }];    // 4. 启动任务(一定要启动任务)    [task resume];}使用: 1. 创建一个 url 路径 2. 获取 session 的对象 3. 通过 session 对象发起会话 task 4. 启动 task 会话 注意:启动 task 会话后是在后台开启一条线程发起下载的任务,将数据从服务器加载到本地或者从本地上传的服务器。
我们在实际开发中使用的 NSURLSession 的精简模式:- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    [self sesssionDemo];}// MARK: - 精简代码- (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 的基本的封装思路就是这样,给一个 url ,后直接启动就可以了。注意点:为了方便程序员使用,苹果提供了一个全局 session ,[NSURLSession sharedSession]所有的任务都是由 session 发起的,session 的回调是异步的

2. NSURLSession的下载的使用练习

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    [self download];}- (void)download {    // 1. url (网络访问的路径)    NSURL *url = [NSURL URLWithString:@"http://192.168.28.3/itcast/images.zip"];    // 2. 下载    /**     参数:     location   下载文件保存的位置,在临时文件夹中     注意:     1. 如果不做任何操作,下载完成后,会自动删除文件!     2. 全局会话默认的回调是在异步线程执行的     考虑一个问题:     1. 通常下载的文件格式是什么格式?zip          压缩包格式有很多种         这样做的好处:            *** 节约流量        * 解压缩(后台线程)        * 结果保存在 cache 目录中        * 删除压缩包(session的下载任务会自动完成)     - 程序员只需要负责解压缩就够了!     */     // 我们这里要使用一个个下载的会话 downloadTask    [[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {        NSLog(@"%@ %@", location.path, [NSThread currentThread]);        // 解压缩的路径        NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];        // 细节:源文件是一个文件,目标的是一个目录,因为压缩包中可能会有很多文件!        [SSZipArchive unzipFileAtPath:location.path toDestination:cacheDir];    }] resume];}NSURLSession的下载的过程中,是先将文件下载到一个临时文件夹中,我们要将下载到临时文件夹中的文件解压缩到我们要保存文件的文件夹中(一般是保存在缓存目录里面)。当downloadTask 执行完毕后,会自动的将临时文件里面下载的文件删除。在解压缩的过程中使用到了一个 SSZipArchive 的框架:SSZipArchive 这个方法的使用需要添加一个 libz.dylib 的动态库才能正常的使用,动态库是我们程序员自己手动添加的。#ifndef _SSZIPARCHIVE_H#define _SSZIPARCHIVE_H#import <Foundation/Foundation.h>#include "unzip.h"@protocol SSZipArchiveDelegate;/// 是对 C 语言 zip 的一个 OC 的封装,只要会用就可以,不需要看懂源代码!@interface SSZipArchive : NSObject// Unzip - 解压缩/// 将文件解压缩到指定的目录+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination;/// 将文件解压缩到指定的目录,同时指定密码+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error;/// 将文件解压缩到指定的目录,并且设置代理(解压缩进度的回调!)+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination delegate:(id<SSZipArchiveDelegate>)delegate;/// 将文件解压缩到指定的目录,同时指定密码,并且设置代理(解压缩进度的回调!)+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error delegate:(id<SSZipArchiveDelegate>)delegate;

关于动态库的添加:
这里写图片描述

这里写图片描述

2. NSURLSession的下载进度跟进的问题

下载进度的跟进是使用代理来实现的。

这个是头文件属性的设置:@interface ViewController ()<NSURLSessionDownloadDelegate> //遵守一个下载的协议/** *  网络会话,通常应用程序内部会有一个独立的网络会话,提供所提供的网络服务。 *  需要跟踪进度,就需要使用代理,代理是一对一的关系,我就要使用一个自定义的会话 */@property(strong, nonatomic)NSURLSession *session;@end
/* 下载进度的跟踪:  这个主要是使用代理来实现进度跟踪。  存在的问题:主要是有一个下载的峰值存在 */- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{    [self sessionDownloadDemo];}- (void)sessionDownloadDemo {    // 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];}// 这个是   必须实现的协议方法 // 这个是下载完成的- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{}// 这个是断点续传 可以什么都不写// 这个是用来实现下载跟进的(在 ios 7.0 开发的时候,这个方法是必须实现的)- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{}// 这个方法是用来进行下载跟进的  下载进度的跟进存在的问题,存在一个峰值的问题。(xcode6 退出的,不知道是服务器设置合适 xcode 的联动的问题)/* 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 *)session{    if (_session == nil) {        // 网络会话设置        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];        // 我们使用的都是异步的 (最好不要放在主队列执行)        _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc] init]];    }    return _session;}注意点:1、使用 NSURLSession 方法下载,并进行下载的跟进,主要是使用代理来实现的,NSURLSession 的进度跟进有一个不好的地方是存在下载进度的跟进的问题。(有些服务器的下载链接好使有些不好使用)2、下载进度的跟进是使用代理来实现的,代理是一对一的关系,我们不能直接的使用一个全局的NSURLSession对象来跟进下载的进度(NSURLSession是全局的,控制着一堆 app 的下载进度合适嘛?)
代理的工作对列:nil - 异步回调 和 实例化一个 操作队列的效果是一样的![[NSOperationQueue alloc] init] - 异步回调注意:下载本身的线程"只有一条"代理回调可以在"多个线程"回调![NSOperationQueue mainQueue] - 主队列回调下载本身是异步执行的,这一点和 NSURLConnection 是一样的指定代理执行的队列,不会影响到下载本身的执行NSURLSession 即使在主线程回调也不会造成阻塞如何选择队列网络访问结束后,如果不需要做复杂的操作,可以指定主队列,这样不用考虑线程间通讯

3. NSURLSession的断点续传的问题

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

#import "ViewController.h"@interface ViewController () <NSURLSessionDownloadDelegate>// 这个是用来设置进度条@property (weak, nonatomic) IBOutlet UIProgressView *progressView;/// 网络会话,通常应用程序内部会有一个独立的网络会话,提供所有的网络服务!@property (nonatomic, strong) NSURLSession *session;/// 下载任务@property (nonatomic, strong) NSURLSessionDownloadTask *task;/// 续传数据@property (nonatomic, strong) NSData *resumeData;@end
@implementation ViewController/** NSURLSession 提供的断点续传存在的问题,不能"停",应用程序不能终止,一旦终止,续传数据就丢失了,下次还需要重头再来! *//// 开始- (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(@"暂停");    // 使用需要续传的数据,暂停下载任务    // 暂停方法,只需要执行一次,被暂停就不用再次暂停    // 在运行的时候,可以个 nil 发送消息,但是不会执行    /**     1. 所有的任务都是由会话发起的     2. 当任务启动后,会话会对任务进行强引用     3. 一旦任务被取消,意味着会话不再引用任务,一个 weak 对象没有其他对象强引用,会被立即释放     4. 如果属性 是 strong,除了会话之外,vc同样对任务强引用(session 为什么要使用强引用)     提问:自己开发,用 strong!     */     // 这个是取消下载任务 (达到暂停的目的)    [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];}// MARK: 下载代理方法/// 提示:如果是 iOS 7.0 开发,三个代理方法都是必须实现的/// 下载完成 - 必须要实现的- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {    NSLog(@"%@", location);}/// 断点续传的代理方法,可以什么都不写- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {    NSLog(@"%s", __FUNCTION__);}/** 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(@"%f %@", progress, [NSThread currentThread]);    dispatch_async(dispatch_get_main_queue(), ^{        self.progressView.progress = progress;    });}// MARK: - 懒加载- (NSURLSession *)session {    if (_session == nil) {        // 网络会话的配置        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];        /**         下载本身是异步的,并不会阻塞主线程的工作!         "代理"的队列 - 只是指定了代理执行所在的队列!         - [[NSOperationQueue alloc] init]            * 所有的代理回调,都是异步的         - nil(效果等同于自定义队列 - 斯坦福大学白胡子老头说的使用 nil)            * 所有的代理回调,都是异步的         - 主队列            * 所有的代理回调,都是在主线程异步的,不会阻塞主线程执行!            * 提示:下载回调会非常频繁,如果有很多文件同时下载,同时回调,还是会影响到主线程的!         * 队列的选择            - 如果完成之后,直接更新 UI,选择主队列            - 如果完成之后,要做后续的耗时(解压缩)操作,使用nil         */        _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];    }    return _session;}@end
0 0
原创粉丝点击