NSURLSession

来源:http://www.chinese-glasses.com 作者:Web前端 人气:119 发布时间:2020-03-14
摘要:最近在做关于文件下载的需求:前端调用接口,然后对返回数据进行过滤、格式化,然后按表格内容拼接生成csv文件,让用户下载。 多值参数 确定请求路径 URLWithString:@" http://192.168.1

最近在做关于文件下载的需求:前端调用接口,然后对返回数据进行过滤、格式化,然后按表格内容拼接生成csv文件,让用户下载。

多值参数

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

具体实现方式如下:

文件的压缩解压缩

  • 第三方框架ZipArchive
    • SSZipArchive
    • 报错:需要依赖于某个库,这个库不存在
      • 尝试添加这个库General/BuildPhrase
      • libz.1.1.3
  • 压缩
    • 导入SSZipArchive的头文件
    • 桌面上有两张图片,把这两张图片打包成zip文件
    • 压缩文件createZipFileAtPath:withFilesAtPath:
      • 参数一:打包后文件存储位置
      • 参数二:数组,要压缩的是哪些文件(要压缩的所有文件的文件路径)
      • 注意:开发中,文件是在沙盒里
    • 压缩文件夹creatZipFileAtPath:withContentsOfDirectory:
      • 参数一:打包后文件存储位置
      • 参数二:文件夹的路径
  • 解压缩
    • unzipFileAtPath:toDestination:
      • 参数一:要解压的文件路径
      • 参数二:解压后存储的路径
  1. 数据中存在 ‘,‘ 逗号问题处理:将整个数据用双引号(英文格式)包裹起来,这样会显示成一个单元格。

小文件下载

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

2. 拼接后的数据如何直接下载,不需要用户在操作:动态创建a标签,并调用点击事件

常用操作
  • 定义一个请求任务属性,写一个懒加载
    • NSURLSessionDataTask 属性用strong修饰
      • 在cancel方法之后手动清空
  • 开始start
    • [self.dataTask resume]
  • 暂停suspend
    • [self.dataTask suspend]
    • 暂停是可以恢复的
  • 恢复resume
    • [self.dataTask resume]
  • 取消cancel
    • [self.dataTask cancel]
    • 手动给指针(NSURLSessionDataTask)清空
    • 取消不可以恢复
      • cancel代表整个请求已经结束了
    • 怎么实现点击取消可以恢复下载?
      • self.dataTask = nil

时间: 2019-11-20阅读: 246标签: 文件

监听文件上传进度

  • 代理方法
    • 创建会话对象
      • sessionWithConfiguration:delegate:delegateQueue:
    • 遵守协议
    • 实现代理方法didSendBodyData:...
      • 参数一:本次上传的数据大小
      • 参数二:已经上传数据的总大小
      • 参数三:文件的总大小
  1. 中文乱码解决方案:在数据前面加上字符串"ufeff"即可

代理方法的使用

  • 确定请求路径
  • 创建请求对象
  • 创建|获取会话对象
    • 自定义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:
      • 解析数据
let sourceData = { head: [ ‘时间‘, ‘成交价格‘, ‘成交数量‘, ‘手续费‘, ‘成交金额‘, ], data: [ {time: ‘2019-10-17 14:54:52‘, tradePrice: ‘30.0022.001.32 TWD‘, fee:‘0 TWD‘, tradeAmount: ‘660.00‘,}, {time: ‘2019-10-17 14:54:36‘, tradePrice: ‘30.0089.005.34 TWD‘, fee:‘0 TWD‘, tradeAmount: ‘2,670.00‘,}, {time: ‘2019-10-17 14:54:07‘, tradePrice: ‘21.00500.0021 TWD‘, fee:‘0 TWD‘, tradeAmount: ‘10,500.00‘,}, ]}// 格式化const data = [sourceData.head.join(‘,‘)].concat(sourceData.data.map(item = { return [ item.time, `"${item.tradePrice}"`, `"${item.fee}"`, `"${item.tradeAmount}"`, ].join(‘,‘)}))// 创建Blob对象 传入一个合适的MIME类型const blob = new Blob([‘ufeff‘ + data.join(‘n‘)], {type: ‘text/csv,charset=UTF-8‘}); // 参考链接 -CN/docs/Web/API/Blob// 使用 Blob 创建一个指向类型化数组的URLconst csvUrl = URL.createObjectURL(blob); // 参考链接 -CN/docs/Web/API/URL/createObjectURLlet link = document.createElement(‘a‘); link.download = `details_${new Date().getTime()}.csv`; //文件名字 link.href = csvUrl;// 触发下载link.click();

文件下载

注意事项:

解决内存飙升的问题- 输出流的基本使用
  • 解决内存飙升问题:
    • NSFileHandle文件句柄指针
    • 输出流NSOutputStream
      • 创建输出流(路径)如果该路径对应的文件不存在那么会自动创建一个空的文件
      • 开始任务(打开输出流)
      • 当接收到数据之后使用输出流来写数据
      • 关闭输出流
  • 输出流的基本使用(水管)
    • 用输出流实现离线断点下载
    • 当接收到服务器响应的时候创建输出流
    • didReceiveResponse:
      • 创建输出流NSOutputStream alloc initToFileAtPath:append:
        • 参数一:文件的路径(全路径)
        • 参数二:是否追加拼接YES
      • 打开输出流,开始任务
        • [stream open]把服务器的数据引入到客户端
        • 定义一个输出流属性
      • 写数据 self.stream write:maxLength:
        • 参数一:传字节,data.bytes
        • 参数二:大小,data.length
      • 关闭输出流
        • [self.stream close]
  • 好处:不需要创建空的文件,也不需要判断
JSON反序列化处理(JSON->OC)
  • 确定请求路径NSURL
  • 创建请求对象NSURLRequest
  • 创建会话对象NSURLSession
  • 10bet,创建task
    • dataTaskWithRequest:completionHander:
  • 执行任务resume
  • 解析数据
    • 解析JSON数据(data本质上是一个JSON数据)
    • NSJSONSerialization JSONObjectWithData:options:error:
      • 参数一:要解析的JSON数据
      • 参数二:选项,枚举[3]
        • MutableContainers生成的字典或者数组是可变的
        • MutableLeaves 生成的字典中的(key)字符串也是可变的
        • AllowFragments 最外层既不是字典也不是数组
        • 默认做法是传0,kNilOptions
      • 参数三:错误信息,传地址
      • 返回值:是一个对象
    • 把一个JSON字符串转成OC的数据类型(字典|数组|其他)

XML(不需要掌握)

  • 学习网站:W3school.com
  • XML文档组成:
    • 文档声明:<?xml version = "1.0"?>
    • 元素Element
      • 开始标签
      • 结束标签
      • 拥有内容的元素:<video>小黄人</video>
      • 没有内容的元素:<video></video>
      • 没有内容的元素简写:<video/>
      • 一个元素可以嵌套若干个子元素(不能出现交叉嵌套)
      • 注意:所有的空格和换行,都会当做具体内容处理
    • 属性Attribute
      • 一个元素里面可以拥有多个属性
      • <video name = "小黄人 " length = "30" />
      • video元素拥有name和length两个属性
      • 属性值必须用“”或者‘’括住
      • 实际上,属性表示的信息也可以用子元素来表示
监听文件下载进度
  • 下载进度 = 当前已经下载的数据大小/文件的总大小
  • 如何获得当前已经下载的数据大小?
    • 第一种方法:self.fileData.length
    • 第二种方法:不断累加data的大小
  • 如何获得文件的总大小?
    • 通过响应头来得到
    • 在didReceiveResponse:方法里面拿响应头
      • 得到本次请求的文件数据大小response.expectedContentLength;
    • 在didReceiveData:方法里面计算进度
      • 1.0*self.fileData.length/self.tatalSize
  • 问题:内存飙升的问题!!
    • 原因:fileData属性变量占用内存空间
    • 解决:把拿到的每一块数据直接写入到沙盒(边接受数据边把数据写入到沙盒)
设置代理来下载文件
  • 确定请求路径

  • 创建请求对象

  • 创建会话对象

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

    • downloadTaskWithRequest:
  • 执行任务resume

  • 遵守代理协议<NSURLSessionDownloadDelegate>

  • 实现代理方法

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

    • 可以监听文件的下载进度

简介

  • 使用步骤
    • 使用NSURLSession会话对象创建Task,然后执行Task
    • Task类型
      • NSURLSessionTask
        • NSURLSessionDataTask(GET/POST)
          • NSURLSessionUploadTask(文件上传)
        • NSURLSessionDownloadTask(文件下载)
  • 获得NSURLSession
    • 方法一:获得共享的Session
      • sharedSession
    • 方法二:自定义Session
      • sessionWithConfiguration:delegate:delegateQueue:
  • 创建NSURLSessionTask
离线断点下载(DownloadTask是没有办法实现离线断点下载的)
  • 思路:可以在程序退出之前,手动调用cancel方法,拿到可恢复下载数据,尝试保存到沙盒里面,当下次程序启动之后再尝试加载可恢复下载的数据,如果有该数据,那么就直接根据该数据创建一个新的网络请求

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

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

数据解析

文件断点下载
  • 应用场景
    • 当用户点击了取消之后想要继续下载文件(默认:从头开始把整个文件下载--缺点:浪费流量)
    • 实现条件
      • 知道应该下载哪一部分数据 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]

JSON和XML比较

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

NSURLSessionDataTask下载文件

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

  • 创建请求对象

  • 创建会话对象

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

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

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

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

  • 直接使用block块下载文件

    • 优点:内部已经完成了边接收数据边写入到沙盒的操作,解决了内存飙升的问题
    • 缺点:无法监听文件的下载进度

获得文件的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];
}
复杂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

文件上传实现

  • 先写思路再写代码
#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 [@"rn" 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;
}

本文由10bet发布于Web前端,转载请注明出处:NSURLSession

关键词:

上一篇:人工智能与前端技术不能说的秘密10bet

下一篇:没有了

频道精选

最火资讯