NSURLSession

NSURLSession基本使用

简介

  • 使用步骤
    • 使用NSURLSession会话对象创建Task,然后执行Task
    • Task类型
      • NSURLSessionTask
        • NSURLSessionDataTask(GET/POST)
          • NSURLSessionUploadTask(文件上传)
        • NSURLSessionDownloadTask(文件下载)
  • 获得NSURLSession
    • 方法一:获得共享的Session
      • sharedSession
    • 方法二:自定义Session
      • sessionWithConfiguration:delegate:delegateQueue:
  • 创建NSURLSessionTask

如何发送GET请求

方法一
  • 01.确定请求路径NSURL
  • 02.创建请求对象NSURLRequest
  • 03.创建|获取会话对象
    • sharedSession
    • 自定义session
  • 04.根据会话对象来创建请求任务
    • dataTaskWithRequest:
      • 参数一:请求对象
      • 参数二:completionHandler完成之后调用的代码块
        • data:响应体
        • response:响应头
        • error:错误信息
  • 05.执行任务(发送请求)
    • [dataTask resume];
      • resume:NSURLSession创建好之后默认是挂起状态需要调用resume恢复
  • 06.解析数据
    • block块默认情况是在子线程中执行的!!!
  • 修改配置文件:ATS
方法二
  • 确定请求路径
  • 创建会话对象
  • 创建task
    • dataTaskWithURL:completionHandler: 该方法内部会根据传入的URL自动创建一个请求对象(默认是GET请求)
  • 发送请求,执行任务resume
  • 解析数据
方法三
[[NSURLSession sharedSession]dataTaskWithURL:[NSURL URLWithString:]completionHandler:{解析数据}]resume]

如何发送POST请求

  • 01.确定请求路径NSURL
  • 02.创建可变的请求对象NSMutableURLRequest
  • 03.修改请求方法为POST
    • request.HTTPMethod = @"POST"
  • 04.设置参数(设置请求体)
    • 把参数转化成二进制数据dataUsingEncoding
    • .HTTPBody
  • 05.创建会话对象
    • Configuration:配置信息
    • sessionWithConfiguration:
  • 06.创建task
    • dataTaskWithRequest:
  • 07.发送请求resume
  • 08.解析数据

代理方法的使用

  • 确定请求路径
  • 创建请求对象
  • 创建|获取会话对象
    • 自定义session
    • sessionWithConfiguration:delegate:delegateQueue:
      • 参数一:配置信息defaultSessionConfiguration默认
      • 参数二:谁成为session的代理
      • 参数三:队列->决定代码块(所有的代理方法)在哪个线程中调用 [NSOperationQueue mainQueue]代理方法就在主线程中,如果队列为nil,那么代理方法在子线程中调用
  • 创建task
    • dataTaskWithRequest:
  • 发送请求resume
  • 遵守协议<NSURLSessionDataDelegate>
  • 代理方法
    • 当接收到服务器响应的时候调用didReceiveResponse:completionHander:
      • 需要告诉系统应该如何处理服务器返回的数据block块是需要我们回调的
      • completionHander(NSURLSessionResponseAllow)
        • NSURLSessionResponseAllow需要手动告诉系统允许
        • NSURLSessionResponseCancel默认
    • 当接收到服务器返回的数据(响应体)的时候调用,该方法可能会调用多次didReceiveData:
      • 需要搞一个可变的二进制数据进行拼接
      • self.data appendData:
    • 当请求完成的时候或者出错的时候调用didCompleteWithError:
      • 解析数据

登录小案例

  • 用户名textField
  • 密码textField
  • 登录按钮
    • 发送POST请求(用户名和密码,POST请求安全性高)
    • 得到用户的输入
      • usernameTextF.text
      • pwdTextF.text
    • 确定请求路径
    • 创建可变的请求对象NSMutableURLRequest:
    • 修改请求方法为POST
      • .HTTPMethod
    • 设置参数
      • stringWithFormat:拼接参数
      • 把字符串转为二进制数据
      • .HTTPBody
    • 创建会话对象
    • 弹出信息,提示用户正在加载
      • SVProgressHUD showStatus:
    • 创建task
    • 发送请求resume
    • 解析数据initWithData:encoding:
      • HUD延迟执行dismiss
      • HUD反馈信息给用户showSuccessWithStatus:
      • 用户名不存在|密码不正确|登录成功
        • 字符串里是否有error|success
        • containsString:
        • showError|showSuccess
        • 使用字符串之前,会对字符串进行解析,怎么把字符串转成字典?
          • JSON解析
    • shift+command+k退出键盘
  • 代码完善
    • 用户没有任何输入,点击按钮发送了网络请求
      • 正则表达式
      • 校验用户是否有输入,如果用户名密码为空-不能登录
    • 弹出提示框
      • 第三方框架SVProgressHUD
      • showErrorWithStatus:占位文字
    • 取消弹框
      • dispatch_after延迟执行dismiss
    • 发送网络请求的时候弹出提示框,提示用户正在加载
    • 显示遮盖蒙板,不能操作界面
      • SVProgressHUD setDefaultMaskType:
    • 数据解析JSON->OC
      • 最外层{}
      • NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:options:error:]
      • 显示HUD提示信息的时候就可以直接根据key进行取值,不用截取字符串了!

数据解析

JSON

  • 一种轻量级的数据格式,一般用于数据交互
  • 服务器返回给客户端的数据,一般都是JSON或者XML格式(文件下载除外)
  • 格式很像OC中的字典和数组
    • {"name":"jack","age":10}
    • {"name":["jack","rose","jim"]}
  • 标准的JSON格式的注意点:key必须用双引号
  • JSON转为OC数据
    • {}-->NSDictionary
    • []-->NSArray
    • ""-->NSString
    • 数字-->NSNumber
    • 从外向内转换
  • JSON解析方案
    • 第三方框架:JSONKit/SBJson/TouchJSON(性能从左到右,越来越差)
    • 苹果原生:
      • NSJSONSerialization(性能最好)JSON的序列化处理
      • JSON-->OC
        • JSONObjectWithData:
      • OC-->JSON
        • dataWithJSONObject:
JSON反序列化处理(JSON->OC)
  • 确定请求路径NSURL
  • 创建请求对象NSURLRequest
  • 创建会话对象NSURLSession
  • 创建task
    • dataTaskWithRequest:completionHander:
  • 执行任务resume
  • 解析数据
    • 解析JSON数据(data本质上是一个JSON数据)
    • NSJSONSerialization JSONObjectWithData:options:error:
      • 参数一:要解析的JSON数据
      • 参数二:选项,枚举[3]
        • MutableContainers生成的字典或者数组是可变的
        • MutableLeaves 生成的字典中的(key)字符串也是可变的
        • AllowFragments 最外层既不是字典也不是数组
        • 默认做法是传0,kNilOptions
      • 参数三:错误信息,传地址
      • 返回值:是一个对象
    • 把一个JSON字符串转成OC的数据类型(字典|数组|其他)
JSON序列化处理(OC->JSON)
  • @{@"name":@"xiaomage"}字典

  • @[@"name",@"age"]数组

  • 字典和数组都可以转成JSON,但是字符串不能转为JSON;

  • NSJSONNSerialization dataWithJSONObject:options:error:

    • 参数一:要转化的OC对象
    • 参数二:选项 NSJSONWritingPrettyPrinted 排版
    • 参数三:错误
    • 返回值:JSON二进制数据
  • 哪些对象可以转为JSON呢?

    • 通过[NSJSONSerialization isValidJSONObject:]判断
    • 需要满足的条件
      • 最外层必须是字典或者是数组
      • 所有的对象必须是字符串、NSNumber、NSArray/NSDictionary、NSNull中的一种
      • 字典的key都必须是NSString
      • NSNumber是标准的,并且不是无穷大
OC和JSON转换的注意点:
  • "false"-->NSNumber 0

  • "true" -->NSNumber 1

  • "null" -->NSNull 空

  • 字典不能传nil,但是@{@"name":[NSNull null]}这样就不会报错

  • 把.plist里面的数据,以JSON的形式进行存储

    • arrayWithContentsOfFile:
    • NSJSONSerialization dataWithJSONObject:options:NSJSONWritingPrettyPrinted error:
    • writeToFile:atomically:
  • 怎么取JSON数据?

    • dataWithContentsOfFile:
    • 解析JSON,转换成OC对象
    • NSJSONSerialization JSONObjectWithData:options:error:
复杂JSON解析-数据展示
  • tableViewController
  • sytle:subtitle
  • indentifier:
  • 发送网络请求获取数据
[[ NSURLSession sharedSession]dataTaskWithURL:[NSURL URLWithString:@""] completionHandler:^{
解析服务器返回的数据
NSDictionary * dict = [NSJSONSeriazlization JSONObjectWithData:options:error:] 
方法一:写成plist文件
[dict writeToFile:atomically:]
得到字典数组
NSArray *array = [dict objectForKey:]
self.videos = array ;
//回到主线程显示数据
dispatch_async(dispatch_get_main_queue(),^{
刷新tableView
[self.tableView reloadData]
});
}] resume]; 
  • 复杂的JSON数据如何处理?

    • 01.写plist文件
    • 02.在线格式化JSON数据
      • 百度JSON在线格式化tool.oschina
  • 展示数据

    • 获得cell
    • 设置cell的数据
      • 得到该行cell对应的数据
      • 设置标题、子标题
      • 图片url路径不完整,需要拼接主机地址
        • 多图下载SDWebImage
        • UIImageView+WebCache
        • 拼接主机地址
        • sd_setImageWithURL:placeholderImage:
        • 图片尺寸问题:设置frame
    • 返回cell
复杂JSON解析- 播放视频
  • 点击cell,创建一个播放器播放视频
  • didSelectRowAtIndexPath:
    • 获得该cell对应的数据dict= self.videos[indexPath.row]
    • 拼接路径
    • 创建一个视频播放控制器
      • 导入一个库:import<MediaPlayer/MediaPlayer.h>
      • MPMoviePlayerViewController alloc initWithContentURL:
    • 弹出控制器
      • modal
      • [self presentViewController:animated:completion:]
复杂JSON解析 - 面向模型开发
  • 创建模型
  • 字典转模型
  • 字典数组转为模型数组
  • 问题:模型里面的属性id的颜色是关键字颜色,把id替换为ID?
    • 可以使用框架MJExtension
    • 在字典转模型之前,告诉框架应该用什么属性来替换掉字典中的某个key
 [模型名称 mj_setupReplaceKeyFromPropertyName:^NSDictionary*{
    return @{@"ID":@"id"};
        }] 
  • 字典转模型框架
    • Mantle:需要继承自MTModel
    • JSONModel:需要继承自JSONModel
    • MJExtension :不需要继承、无代码侵入性

XML(不需要掌握)

  • 学习网站:W3school.com
  • XML文档组成:
    • 文档声明:<?xml version = "1.0"?>
    • 元素Element
      • 开始标签
      • 结束标签
      • 拥有内容的元素:<video>小黄人</video>
      • 没有内容的元素:<video></video>
      • 没有内容的元素简写:<video/>
      • 一个元素可以嵌套若干个子元素(不能出现交叉嵌套)
      • 注意:所有的空格和换行,都会当做具体内容处理
    • 属性Attribute
      • 一个元素里面可以拥有多个属性
      • <video name = "小黄人 " length = "30" />
      • video元素拥有name和length两个属性
      • 属性值必须用“”或者‘’括住
      • 实际上,属性表示的信息也可以用子元素来表示
使用NSXMLParser解析XML
  • 提取video元素中的name和length属性的值

  • XML的解析方法有两种

    • DOM:一次性的将整个XML文档加载进内存,比较适合解析小文件
    • SAX:从根元素开始,按顺序一个元素一个元素往下解析,比较适合解析大文件
  • ios开发中解析XML的方式

    • 苹果原生NSXMLParser:SAX方式解析,使用简单
    • 第三方框架
      • libxml2:纯C语言,默认包含在ios sdk中,同时支持DOM和SAX方式解析
      • GDataXML:DOM方式解析,由Google开发,基于libxml2
  • XML解析方式的选择建议:

    • 大文件:NSXMLParser/libxml2
    • 小文件:GDataXML/NSXMLParser/libxml2
  • NSXMLParser

    • 使用步骤
      • 传入XML数据,创建解析器 NSXMLParser * parser = [[NSXMLParser alloc]initWithData:data];
      • 设置代理,监听解析过程parser.delegate = self;
      • 开始解析 [parser parse]
        • 该方法本身是阻塞式的,同步
      • 遵守协议<NSXMLParserDelegate>
      • 实现代理方法
        • 开始解析XML文档的时候调用parserDidStartDocument:
        • 开始解析某个元素的时候调用,该方法会调用多次didStartElement:namespaceURI:qualifiedName:attributes:
          • 第二个参数:元素的名称
          • 最后一个参数:属性字典
          • 字典转模型,把模型放到数组里面-->模型数组,作为tableView的数据源
            • 需要做过滤处理:如果是根元素,就直接返回,不进行这一个操作
        • 某个元素解析完毕的时候调用,该方法会调用多次parser:didEndElement:namespaceURI:qualifiedName:
        • 整个XML文档解析完毕的时候调用 parserDidEndDocument:
    • JSON和XML本质上就是字符串(有特殊格式的字符串)
使用GDataXML解析XML
  • GDataXML框架
  • 解析数据之前要检查数据获取有没有问题
  • 配置
    • 导入libxml2(现在默认包含了这个库)
    • 设置libxml2的头文件搜索路径(为了找到libxml2库的所有头文件)
      • 在Head Search Path中加入/usr/include/libxml2
    • 设置链接参数(自动链接libxml2库)
      • 在Other Linker Flags中加入 -lxml2
  • 非ARC
    • 告诉编译器在编译的时候以非ARC进行编译
      • -fno-objc-arc
  • 解析数据
    • 加载整个XML文档
      • GDataDocument alloc initWithData:options:error:
    • 得到根元素,根据根元素得到内部所有名称为video的子元素
      • [.rootElement elementsForName:]返回值是数组
    • 遍历所有的子元素,得到子元素中的属性
      • for in
      • 元素的类型:GDataXMLElement
      • 创建一个空的模型
      • 给模型里的属性赋值
      • video.ID = [element attributeForName:@"id"].stringValue;
      • addObject:把模型添加到可变数组中

JSON和XML比较

  • 同一份数据,既可以用JSON来表示,也可以用XML来表示
  • JSON更加轻量级
  • JSON的体积小于XML,所以服务器返回给移动端的数据以JSON居多

文件下载

小文件下载

  • 文件下载的第一种方式:NSData dataWithContentsOfURL:
  • 局限性:
    • 只能处理小文件,如果文件过大会造成内存飙升
    • 不能监听下载进度
    • 性能不好
    • 一般开发中不用这种方法下载文件,以后尽量不要使用了

NSURLSessionDataTask下载文件

NSURLSession发请求下载文件(DataTask-代理)
  • 确定请求路径
  • 创建请求对象
  • 创建会话对象NSURLSession
    • sessionWithConfiguration:delegate:delegateQueue:
  • 遵守代理协议<NSURLSessionDataDelegate>
  • 实现代理方法
    • 接收到服务器响应的时候调用didReceiveResponse:completionHandler:
      • 告诉服务器怎么处理服务器返回的数据completionHandler(NSURLSessionResponseAllow)
    • 接收到服务器返回的数据的时候调用 didReceiveData:
      • 搞一个可变的data,初始化处理(或者懒加载),不断拼接服务器返回的数据 appendData:
    • 整个请求完成或者失败的时候调用didCompleteWithError:
      • 处理拼接得到的数据(保存到沙盒里面)
        • 获得caches路径
          • NSSearchPathForDirectoriesInDomains()
        • 设置文件的名称
          • NSString *fileName = task.response.suggestedFileName
          • suggestedFileName:推荐的文件名称
        • 拼接获得全路径
          • stringByAppendingPathComponent:
        • 写数据到磁盘
          • writeToFile:atomically:
  • 创建请求任务
    • session dataTaskWithRequest:
  • 执行task
    • [dataTask resume]
监听文件下载进度
  • 下载进度 = 当前已经下载的数据大小/文件的总大小
  • 如何获得当前已经下载的数据大小?
    • 第一种方法:self.fileData.length
    • 第二种方法:不断累加data的大小
  • 如何获得文件的总大小?
    • 通过响应头来得到
    • 在didReceiveResponse:方法里面拿响应头
      • 得到本次请求的文件数据大小response.expectedContentLength;
    • 在didReceiveData:方法里面计算进度
      • 1.0*self.fileData.length/self.tatalSize
  • 问题:内存飙升的问题!!
    • 原因:fileData属性变量占用内存空间
    • 解决:把拿到的每一块数据直接写入到沙盒(边接受数据边把数据写入到沙盒)
解决内存飙升的问题- 文件句柄指针
  • 01.在沙盒里面创建一个空的文件
    • 在didReceiveResponse:方法里
      • 获得caches路径
      • 设置文件的名称
      • 拼接获得全路径
      • 创建空的文件[NSFileManager defaultManager]createFileAtPath:contents:attributes:
        • 参数一:要创建的文件保存的位置
        • 参数二:文件内容
        • 参数三:文件属性
  • 02.创建文件句柄指针,指向哪里就从哪里写,让文件句柄指针指向空的文件
    • [NSFileHandle fileHandleForWritingAtPath:]
  • 03.当接收到数据之后,使用文件句柄指针写数据,写一点句柄指针移动一点(边写数据边移动指针)
    • 定义一个NSFileHandle属性
    • [self.handle writeData:]
    • 累加当前已经下载的文件数据大小
      • self.currentSize +=data.length;
    • 计算文件的下载进度currentSize/totalSize
  • 04.所有文件写完之后,关闭文件句柄指针
    • 在didCompleteWithError:中关闭
    • [self.handle close]
解决内存飙升的问题- 输出流的基本使用
  • 解决内存飙升问题:
    • NSFileHandle文件句柄指针
    • 输出流NSOutputStream
      • 创建输出流(路径)如果该路径对应的文件不存在那么会自动创建一个空的文件
      • 开始任务(打开输出流)
      • 当接收到数据之后使用输出流来写数据
      • 关闭输出流
  • 输出流的基本使用(水管)
    • 用输出流实现离线断点下载
    • 当接收到服务器响应的时候创建输出流
    • didReceiveResponse:
      • 创建输出流NSOutputStream alloc initToFileAtPath:append:
        • 参数一:文件的路径(全路径)
        • 参数二:是否追加拼接YES
      • 打开输出流,开始任务
        • [stream open]把服务器的数据引入到客户端
        • 定义一个输出流属性
      • 写数据 self.stream write:maxLength:
        • 参数一:传字节,data.bytes
        • 参数二:大小,data.length
      • 关闭输出流
        • [self.stream close]
  • 好处:不需要创建空的文件,也不需要判断
常用操作
  • 定义一个请求任务属性,写一个懒加载
    • NSURLSessionDataTask 属性用strong修饰
      • 在cancel方法之后手动清空
  • 开始start
    • [self.dataTask resume]
  • 暂停suspend
    • [self.dataTask suspend]
    • 暂停是可以恢复的
  • 恢复resume
    • [self.dataTask resume]
  • 取消cancel
    • [self.dataTask cancel]
    • 手动给指针(NSURLSessionDataTask)清空
    • 取消不可以恢复
      • cancel代表整个请求已经结束了
    • 怎么实现点击取消可以恢复下载?
      • self.dataTask = nil
文件断点下载
  • 应用场景
    • 当用户点击了取消之后想要继续下载文件(默认:从头开始把整个文件下载--缺点:浪费流量)
    • 实现条件
      • 知道应该下载哪一部分数据 currentSize
      • 如何实现只下载某个文件的一部分数据
  • 代码实现
    • 设置请求头信息,只请求某一部分数据

    • [NSString stringWithFormat:@"bytes=%zd-",sefl.currentSize]

    • [request setValue:forHTTPHeaderField:@"Range"]

    • bug:进度问题(进度不准确,进度信息变大了)

      • currentSize一直都没有改变
      • 重新调用didReceiveResponse代理方法,totalSize被重新赋值
      • 真正文件的总大小 = 当前已经下载的+本次请求的
      • totalSize = response.expectedContentLength+self.currentSize
    • bug:文件下载不完整,文件损坏

      • 重新发送网络请求,重新走了didRecevieResponse,又创建了一个空的文件,但是第一次已经创建了一个空的文件,怎么判断之前有文件了?
        • 判断currentSize是否为0,如果为0再创建空的文件,否则不再创建
    • bug:点击了取消之后,调用didCompleteError:handler被释放掉了,下载后面的数据的时候,重新创建了句柄指针,指向文件的开头,边写边移动,直接覆盖,怎么让句柄指针,让它指向文件的末尾,再让它写数据!

      • 让文件句柄指向文件的末尾[self.handle seekToEndOfFile]
离线断点下载优化(进度条)
  • 当程序重新启动后显示实时的进度信息!!

  • 怎么做到实时显示数据?

  • 当计算文件总大小之后,把文件数据的总大小就保存到沙盒里去

    • 把文件的总大小包装成NSData
      • stringWithFormat:先转换成字符串
      • dataUsingEncoding:把字符串转为NSData
  • 计算进度信息 = 已经下载的数据大小 /文件的总大小

    • 加载文件总大小
      • dataWithContentsOfFile:
      • alloc initWithData:encoding:
      • .integerValue
      • 第一次程序启动的时候,文件总大小为0,需要做判断,不等于0的时候计算下载进度
  • 退出程序:shift+command+hh

  • 性能问题:

    • 定义一个NSURLSession属性,给session写一个懒加载
    • 开了很多的线程,NSURLSession内部会开很多子线程同时处理任务,所以使用NSURLSessionDataTask性能不好
  • NSURLSession使用的注意点:

    • 代理的问题:给NSURLSession设置代理,控制器没有办法释放
      • 问题演示:导航控制器里添加item,点击item,push控制器,重写控制器的dealloc方法,点击开始下载(设置了代理),dealloc方法没有调用,控制器没有被释放
      • 解决问题:
        • invalidateAndCancel马上退出
        • finishTasksAndInvalidate 下载运行完毕之后才退出
        • 在控制器即将消失的时候调用这两个方法,可以在viewDidDisappear方法里面调用

NSURLSessionDownloadTask下载文件

直接使用block下载文件
  • 确定请求路径

  • 创建请求对象

  • 创建会话对象

  • 根据会话对象创建一个downloadTask

    • NSURLSessionDownloadTask--6个方法
    • downloadTaskWithRequest:completionHandler:
      • 参数一:请求对象
      • 参数二:完成之后的回调
        • location:文件的临时存储路径,该方法内部会自动完成边接收数据边写沙盒的操作
        • response:响应头信息
        • error:错误信息
  • 发送网络请求resume

  • 当文件下载完毕之后,剪切文件到安全的位置(从临时路径转移到cache路径)

    • NSSearchPathForDirectoriesInDomains()
    • stringByAppendingPathComponent:suggestedFileName:
    • 执行文件剪切操作[NSFileManager defaultManager]moveItemAtURL:toURL:[NSURL fileURLWithPath:]error:
  • 当前方法存在问题:没有办法监听文件下载进度

  • 直接使用block块下载文件

    • 优点:内部已经完成了边接收数据边写入到沙盒的操作,解决了内存飙升的问题
    • 缺点:无法监听文件的下载进度
设置代理来下载文件
  • 确定请求路径

  • 创建请求对象

  • 创建会话对象

    • sessionWithConfiguration:delegate:delegateQueue:
  • 创建请求任务NSURLSessionDownloadTask

    • downloadTaskWithRequest:
  • 执行任务resume

  • 遵守代理协议<NSURLSessionDownloadDelegate>

  • 实现代理方法

    • didWriteData:totalBytesWritten:totalBytesExpectedToWrite:写数据的时候调用该方法
      • 参数一:本次写入数据的大小
      • 参数二:写入数据的总大小
      • 参数三:文件的总大小
      • 计算文件下载进度
    • disFinishDownloadingToURL:当下载完成之后会调用该方法
      • 参数location:文件的临时存储路径
      • 剪切文件到caches路径
        • 通过downloadTask可以得到响应头信息
    • didCompleteWithError:请求结束的时候调用
  • 设置代理下载文件

    • 可以监听文件的下载进度
NSURLSessionDownloadTask常见操作
  • 定义一个NSURLSessionDownloadTask属性,懒加载
  • 在懒加载方法里面发送请求,设置代理
  • 遵守协议
  • 实现代理方法
  • 开始start
    • resume
  • 暂停suspend
    • suspend
  • 继续resume
    • resume
  • 取消cancel
    • cancel 该取消方法是不能恢复的
NSURLSessionDownloadTask断点下载
  • 应用场景:文件下载一半,点击了取消,点击恢复下载能够继续下载后面的文件
  • 定义一个NSData *resumeData;
  • 定义NSURLSession属性,懒加载
  • cancel方法里面
    • 调用cancelByProducingResumeData:^(NSData *_Nullable resumeData){}方法
    • 该取消方法是可以恢复的,resumeData是可恢复数据,不是文件本身,是记录信息,记录下载了哪个文件,下载到哪个位置
    • self.resumeData = resumeData;
  • resume方法里面
    • 重新创建网络请求 self.session downloadTaskWithResumeData:根据可恢复下载的数据,创建一个新的网络请求,发送请求

    • 上面的做法只考虑了点击了取消之后恢复的情况,需要进行判断是暂停之后恢复,还是取消之后恢复

      • 判断resumeData有没有值,再根据可恢复下载的数据重新创建网络请求
离线断点下载(DownloadTask是没有办法实现离线断点下载的)
  • 思路:可以在程序退出之前,手动调用cancel方法,拿到可恢复下载数据,尝试保存到沙盒里面,当下次程序启动之后再尝试加载可恢复下载的数据,如果有该数据,那么就直接根据该数据创建一个新的网络请求

    • 注意:在程序退出之前,downloadTask就已经取消了,实际上不可行的,没有办法实现离线断点下载的
  • 总结:

    • 在处理文件下载的时候如果不需要实现离线断点下载功能,那么我们就使用NSURLSessionDownloadTask下载,否则就只能使用NSURLSessionDataTask

文件上传

文件上传分析

  • 文件上传参数
    • 文件参数(上传的文件是什么)
    • 用户名(谁上传了文件)
  • 处理逻辑(步骤)文件上传要发POST请求
    • 确定请求路径
    • 创建可变请求对象
    • 修改请求方法POST
    • 设置请求头:告诉服务器这是一个文件上传请求,请准备接收我的数据
    • 拼接参数-(设置请求体)
      • 按照固定的格式来拼接参数
    • 创建会话对象
    • 根据会话对象创建请求uploadTask请求
    • 发送请求
    • 解析服务器返回的数据
文件上传格式简化:
`分隔符`:----WebKitFormBoundaryjh7urS5p3OcvqXAT

请求头
Content-Type:multipart/form-data; boundary=分隔符

拼接参数的格式
 文件参数
/*
    --分隔符
    Content-Disposition: form-data; name="file"; filename="Snip20160716_103.png"
    Content-Type: image/png
    空行
    文件数据
 */
 非文件参数
/*
    --分隔符
    Content-Disposition: form-data; name="username"
    空行
    xiaomage
*/
 结尾标识
/*
--分隔符--
*/

文件上传实现

  • 先写思路再写代码
#define Kboundary @"----WebKitFormBoundaryjh7urS5p3OcvqXAT
"
确定请求路径
创建可变的请求对象
修改请求方法为POST
设置请求头
//Content-Type:multipart/form-data; boundary=分隔符
[NSString stringWithFormat:@"multipart/form-data;boundary = %@",Kboundary]
[request setValue:forHTTPHeaderField:@"Content-Type"]
拼接参数- 设置请求头
NSData *data = 提供一个方法拼接参数
//!!!!无效被忽略request.HTTPBody = data;
创建会话对象
根据会话对象创建uploadTask请求
uploadTaskWithRequest:fromData:completionHandler:
     参数一:请求对象
     参数二:要传递的是本应该设置为请求体的参数
     参数三:当上传完成的时候调用
         data:响应体 
         response:响应头信息
发送请求resume
解析服务器返回的数据JSON数据解析
按照固定形式拼接参数
#define Kboundary  @"----WebKitFormBoundaryjh7urS5p3OcvqXAT"
#define KNewLine [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]
-(NSData *)getBodyData
{
    NSMutableData *data = [NSMutableData data];
    
    //01 文件参数
    /*
     --分隔符
     Content-Disposition: form-data; name="file"; filename="Snip20160716_103.png"
     Content-Type: image/png
     空行
     文件数据
     */
    
    [data appendData:[[NSString stringWithFormat:@"--%@",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    //file 文件参数 参数名 == username
    //filename 文件上传到服务器之后以什么名称来保存
    [data appendData:[@"Content-Disposition: form-data; name=\"file\"; filename=\"123.png\"" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    
    //Content-Type 文件的数据类型
    //file和username是参数
    //分隔符可以随意些,但是不能有中文
    [data appendData:[@"Content-Type: image/png" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    [data appendData:KNewLine];
    NSData *imageData = [NSData dataWithContentsOfFile:@"/Users/xiaomage/Desktop/Snip20160716_125.png"];
    [data appendData:imageData];
    [data appendData:KNewLine];
    
    //02 非文件参数
    /*
     --分隔符
     Content-Disposition: form-data; name="username"
     空行
     xiaomage
     */
    [data appendData:[[NSString stringWithFormat:@"--%@",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    //username 参数名称
    [data appendData:[@"Content-Disposition: form-data; name=\"username\"" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    [data appendData:KNewLine];
    [data appendData:[@"xiaomage" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    
    //03 结尾标识
    /*
     --分隔符--
     */
    [data appendData:[[NSString stringWithFormat:@"--%@--",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
    //拼接
    return data;
}

监听文件上传进度

  • 代理方法
    • 创建会话对象
      • sessionWithConfiguration:delegate:delegateQueue:
    • 遵守协议
    • 实现代理方法didSendBodyData:...
      • 参数一:本次上传的数据大小
      • 参数二:已经上传数据的总大小
      • 参数三:文件的总大小

获得文件的MIMEType

  • Content-Type 又叫做MIMEType
  • 怎么获得MIMEType?
    • 发送请求,得到相应头信息(MIMEType)
    • response.MIMEType
    • 百度MIMEType,查表
    • 使用C语言的函数来获取
      • 导入MobileCoreServices
    • 设置为通用的二进制数据类型
      • application/octet-stream
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

    //01 发送请求|得到响应头信息(MIMEType)
    //02 直接百度查表 http://www.w3school.com.cn/media/media_mimeref.asp
    //03 使用C语言的函数来获取
    //04 设置为通用的二进制数据类型 application/octet-stream
   NSLog(@"%@",[self mimeTypeForFileAtPath:@"/Users/xiaomage/Desktop/Snip20160716_103.png"]);
}


- (NSString *)mimeTypeForFileAtPath:(NSString *)path
{
    if (![[[NSFileManager alloc] init] fileExistsAtPath:path]) {
        return nil;
    }
    
    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
    CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
    CFRelease(UTI);
    if (!MIMEType) {
        return @"application/octet-stream";
    }
    return (__bridge NSString *)(MIMEType);
}


-(void)getMimetype
{
   
    
    [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL fileURLWithPath:@"/Users/xiaomage/Desktop/上课笔记.h"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        //MIMEType = 大类型/小类型
        NSLog(@"%@",response.MIMEType);
    }] resume];
}

文件的压缩解压缩

  • 第三方框架ZipArchive
    • SSZipArchive
    • 报错:需要依赖于某个库,这个库不存在
      • 尝试添加这个库General/BuildPhrase
      • libz.1.1.3
  • 压缩
    • 导入SSZipArchive的头文件
    • 桌面上有两张图片,把这两张图片打包成zip文件
    • 压缩文件createZipFileAtPath:withFilesAtPath:
      • 参数一:打包后文件存储位置
      • 参数二:数组,要压缩的是哪些文件(要压缩的所有文件的文件路径)
      • 注意:开发中,文件是在沙盒里
    • 压缩文件夹creatZipFileAtPath:withContentsOfDirectory:
      • 参数一:打包后文件存储位置
      • 参数二:文件夹的路径
  • 解压缩
    • unzipFileAtPath:toDestination:
      • 参数一:要解压的文件路径
      • 参数二:解压后存储的路径

多值参数

  • 确定请求路径
  • 创建会话对象
    • sharedSession
  • 创建请求任务
    • dataTaskWithURL:
  • 执行任务
  • 解析数据
    • JSON->OC
    • NSJSONSerilization JSONObjectWithData:
    • 中文输出处理
      • 分类:Foundation+Log
      • 控制字典和数组的输出,分类里面重写了系统的方法,决定字典和数组输出的内容
      • 给字典写的分类
        • 创建可变字符串
        • {遍历整个字典,每遍历一次就拼接key、value}
      • 给数组写的分类
        • 创建可变字符串
        • [遍历数组,每遍历一次就把里面的元素变成字符串拼接一次]

NSURLSession配置

  • 在一个控制器里面发多个网络请求

    • 定义NSURLSession属性,懒加载
    • 确定请求路径
    • 创建可变的请求对象
    • 多个网络请求,怎么控制请求的超时时间
      • 配置信息
    • 创建会话对象
    • 发送请求,执行任务
  • 配置信息,控制多个请求的超时时间

    • 懒加载方法里面sessionWithConfiguration:根据配置信息创建会话对象
    • 拿到配置信息,对会话对象进行配置,控制控制器里的所有网络请求
    • configuration.timeoutIntervalForRequest = 10;所有网络请求都会遵守这个配置
    • 设置在蜂窝网络的状态系是否能够发生请求(比如视频播放,检测当前用户的网络情况)
  • 详细笔记:NSURLConfiguration

    • 目的是想取代request
    • defaultSessionConfiguration标准的配置
    • ephmeralSessionConfiguration返回的是预设的配置,没有缓存,无痕浏览
    • backgroundSessionConfiguration 后台,支持后台下载后台上传,会创建一个后台会话,可以在应用程序崩溃,挂起的状态下进行上传下载任务
  • 重要属性

    • HTTPAdditionalHeaders
    • allowCellularAccess 允许蜂窝网络
    • timeoutIntervalForRequest 请求超时时间

总结

  • NSURLSession

    • GET|POST|设置代理
    • POST请求步骤
      • 创建请求路径
      • 创建可变的请求对象
      • 修改请求方法为POST
      • 设置参数
      • 创建会话对象
      • 创建请求任务
      • 发送请求
      • 解析服务器返回的数据
  • 数据解析

    • JSON解析
      • 反序列化处理 JSON->OC
      • 序列化处理 OC->JSON
      • 保存假数据加载假数据
    • XML解析
      • 语法
      • 解析XML
        • DOM:把整个XML文档加载进内存解析 GDataXML
        • SAX:从根元素开始,一个元素一个元素的开始解析 NSXMLParser
  • 文件下载

    • 使用dataWithContentURL:存在一些问题
      • 只能处理小文件,如果文件过大会造成内存飙升
      • 不能监听下载进程
      • 性能不好,contentsOfURL:是在当前线程中执行的
    • NSURLSession(DataTask-代理)
      • 监听文件的下载进度
      • 解决内存飙升问题
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容