一、NSURLSession
1、简介
- iOS7.0 推出,用于替代 NSURLConnection。
- 支持后台运行的网络任务。
- 暂停,停止,重启网络任务,不需要NSOperation 封装。 > 请求可以使用同样的 配置容器。
- 不同的 session 可以使用不同的私有存储。
- block 和代理可以同时起作用。
- 直接从文件系统上传,下载。
结构图
- 1、为了方便程序员使用,苹果提供了一个全局 session
- 2、所有的 任务(Task)都是由 session 发起的
- 3、所有的任务默认是挂起的,需要 resume
- 4、使用 NSURLSession 后,NSURLRequest 通常只用于 指定 HTTP 请求方法,而其他的额外信息都是通过 NSUSLSessionConfiguration 设置
2、代码演练--获取JSON数据
#pragma mark - 详细的写法
// 加载数据
- (void)loadData{
// 1.创建 url
NSURL *url = [NSURL URLWithString:@"http://localhost/demo.json"];
// 2.为了程序员方便开发,苹果提供了一个全局的 session 对象
// 获取全局的会话对象
// 在 session 中,request绝大多数情况下是可以省略的
NSURLSession *session = [NSURLSession sharedSession];
// 3.获得数据任务对象
// **** 所有的任务都是由 session 发起的,不要通过 alloc init 创建任务对象
// **** 任务的回调默认的就是异步的
// **** 如果需要更新 UI,需要注意主线程回调
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
id result =[NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"result = %@ ---%@",result,[NSThread currentThread]);
}];
// 4.继续任务
[task resume];
}
#pragma mark - 简化的写法
- (void)loadData2{
// 1.创建 url
NSURL *url = [NSURL URLWithString:@"http://localhost/demo.json"];
// 2.执行任务
[self dataTask:url finished:^(id obj) {
NSLog(@"obj = %@ -- %@",obj,[NSThread currentThread]);
}];
}
- (void)dataTask:(NSURL *)url finished:(void (^)(id obj))finished{
NSAssert(finished != nil, @"必须传人回调");
// **** 所以的任务都是由 session 发起的,不要通过 alloc init 创建任务对象
// **** 任务的回调默认的就是异步的
// **** 如果需要更新 UI,需要注意主线程回调
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
id result =[NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
// NSLog(@"result = %@ ---%@",result,[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
finished(result);
});
}] resume];
}
二、下载和解压缩
1、NSURLSession下载文件
- (void)download{
// 1.创建下载 url
NSURL *url = [NSURL URLWithString:@"http://localhost/1234.mp4"];
NSLog(@"开始下载");
// 2.开始下载
[[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSLog(@"location = %@ -- %@",location,[NSThread currentThread]);
NSLog(@"下载完成");
// 异步解压缩
}] resume];
}
细节:
- 从Xcode6.0 到 Xcode 6.3 内存占用一直很高,差不多是文件大小
的 2.5 倍。 - 文件下载完成后会被自动删除!思考为什么?
- 大多数情况下,下载的文件类型以‘zip’居多,可以节约用户的流量。
- 下载完成后解压缩。
- 压缩包就可以删除。
2、解压缩zip包
- (void)download{
// 1.创建下载 url
NSURL *url = [NSURL URLWithString:@"http://localhost/itcast/images.zip"];
NSLog(@"开始下载");
// 2.开始下载
[[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSLog(@"location = %@ -- %@",location,[NSThread currentThread]);
NSLog(@"下载完成");
// 获得沙盒缓存文件夹路径
NSString *cacheDir = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"pkxing"];
// 异步解压缩
// location.path:没有‘协议头’的路径
// location.absoluteString:包含 ‘协议头‘’的完成 url 路径
// 注意:解压缩路径最好自己新建一个专属的文件夹,因为 zip 包中可能有多个文件。
[SSZipArchive unzipFileAtPath:location.path toDestination:cacheDir delegate:self];
}] resume];
}
#pragma mark - SSZipArchiveDelegate 代理方法
/**
* 跟踪解压缩进度
*
* @param loaded 已经解压的大小
* @param total 要解压的文件大小
*/
- (void)zipArchiveProgressEvent:(NSInteger)loaded total:(NSInteger)total {
NSLog(@"%f",(CGFloat)loaded/ total);
}
压缩
// 将指定路径下的文件打包到指定的 zip 文件中
[SSZipArchive createZipFileAtPath:@"/users/pkxing/desktop/abc.zip" withFilesAtPaths:@[@"/users/pkxing/desktop/多赢商城.ipa"]];
// 将指定的文件夹下所有的文件打包到指定的zip 文件中
[SSZipArchive createZipFileAtPath:@"/users/pkxing/desktop/abc.zip" withContentsOfDirectory:@"/users/pkxing/desktop/课件PPT模板"];
3、下载进度
-
1、监听如何监听下载进度?
- 通知/代理/block/KVO(监听属性值,极少用在这种情况)
- 查看是否可以使用有代理:进入头文件, 从 interface 向上滚两下查看是否有对应的协议。
- 查看是否可以使用有通知:看头文件底部。
- 查看是否可以使用有block:通常和方法在一起。
- 通知/代理/block/KVO(监听属性值,极少用在这种情况)
-
2、要监听session下载进度使用的是代理,此时不能使用'sharedSession'方法创建会话对象
- 使用 sharedSession 获得的对象是全局的对象。
- 多处地方调用获得的对象都是同一个会话对象,而代理又是一对一的。
3、如果发起的任务传递了completionHandler回调,不会触发代理方法。
- (void)download{
// 1.创建下载 url
NSURL *url = [NSURL URLWithString:@"http://localhost/1234.mp4"];
NSLog(@"开始下载");
// 2.开始下载
/*
[[self.session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSLog(@"location = %@ -- %@",location,[NSThread currentThread]);
NSLog(@"下载完成");
}] resume];
*/
[[self.session downloadTaskWithURL:url] resume];
}
#pragma mark - NSURLSessionDownloadDelegate
/**
* 下载完毕回调
*
* @param session 会话对象
* @param downloadTask 下载任务对象
* @param location 下载路径
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSLog(@"location = %@",location);
}
/**
* 接收到数据回调
*
* @param session 会话对象
* @param downloadTask 下载任务对象
* @param bytesWritten 本次下载字节数
* @param totalBytesWritten 已经下载字节数
* @param totalBytesExpectedToWrite 总大小字节数
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
// 计算进度
CGFloat progress = (CGFloat)totalBytesWritten / totalBytesExpectedToWrite;
NSLog(@"progress = %f---%@",progress,[NSThread currentThread]);
}
/**
* 续传代理方法:没有什么用
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
NSLog(@"---%s",__func__);
}
#pragma mark - 懒加载会话对象
- (NSURLSession *)session {
if (_session == nil) {
// 创建会话配置对象
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
/**
参数:
configuration:会话配置,大多使用默认的。
delegate:代理,一般是控制器
delegateQueue:代理回调的队列,可以传入 nil.
*/
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
4、队列的选择
#pragma mark - 懒加载会话对象
- (NSURLSession *)session {
if (_session == nil) {
// 创建会话配置对象
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
参数:
configuration:会话配置,大多使用默认的。
delegate:代理,一般是控制器。
delegateQueue:代理回调的队列。
可以传人 nil,传人 nil 等价于[[NSOperationQueue alloc] init]。
传人[NSOperationQueue mainQueue],表示代理方法在主队列异步执行。
如果代理方法中没有耗时操作,则选择主队列,有耗时操作,则选择异步队列。
下载本身是由一个独立的线程完成。无论选择什么队列,都不会影响主线程。
5、暂停和继续01
/**
* 开始下载
*/
- (IBAction)start{
// 1.创建下载 url
NSURL *url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/2a/25677/QQ_V4.0.3_setup.1435732931.dmg"];
self.task = [self.session downloadTaskWithURL:url];
[self.task resume];
}
/**
* 暂停下载
*/
- (IBAction)pause{
// 只有运行的任务才需要挂起
if (self.task.state == NSURLSessionTaskStateRunning) {
NSLog(@"pause = %@",self.task);
[self.task suspend];
}
}
/**
* 继续下载
*/
- (IBAction)resume{
// 只有被挂起的任务才需要继续
if (self.task.state == NSURLSessionTaskStateSuspended) {
NSLog(@"resume = %@",self.task);
[self.task resume];
}
}
#pragma mark - NSURLSessionDownloadDelegate
/**
* 下载完毕回调
*
* @param session 会话对象
* @param downloadTask 下载任务对象
* @param location 下载路径
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSLog(@"location = %@",location);
}
/**
* 接收到数据回调
*
* @param session 会话对象
* @param downloadTask 下载任务对象
* @param bytesWritten 本次下载字节数
* @param totalBytesWritten 已经下载字节数
* @param totalBytesExpectedToWrite 总大小字节数
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
// 计算进度
CGFloat progress = (CGFloat)totalBytesWritten / totalBytesExpectedToWrite;
NSLog(@"progress = %f---%@",progress,[NSThread currentThread]);
// 回到主线程更新进度条
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.progress = progress;
});
}
/**
* 续传代理方法:没有什么用
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
NSLog(@"---%s",__func__);
}
#pragma mark - 懒加载会话对象
- (NSURLSession *)session {
if (_session == nil) {
// 创建会话配置对象
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
/**
参数:
configuration:会话配置,大多使用默认的。
delegate:代理,一般是控制器
delegateQueue:代理回调的队列,可以传入 nil.
*/
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
NSURLConnection和 NSURLSessionTask 对比
NSURLConnection 不能挂起,只能开始和取消,一旦取消,如果需要再次启动,需要新建connection
NSURLSessionTask 可以挂起/继续/取消/完成
6、暂停和继续02
// 记录续传数据
@property(nonatomic,strong) NSData *resumeData;
/**
* 开始下载
*/
- (IBAction)start{
// 1.创建下载 url
NSURL *url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/2a/25677/QQ_V4.0.3_setup.1435732931.dmg"];
self.task = [self.session downloadTaskWithURL:url];
[self.task resume];r
}
/**
* 暂停下载
*/
- (IBAction)pause{
// 如果任务已经被取消,不希望再次执行 block
// 在 oc中,可以给 nil 对象发送任何消息
[self.task cancelByProducingResumeData:^(NSData *resumeData) {
NSLog(@"length = %zd",resumeData.length);
// 记录续传数据
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];
}
resumeData:
该参数包含了继续下载文件的位置信息。也就是说,当你下载了10M得文件数据,暂停了。那么你下次继续下载的时候是从第10M这个位置开始的,而不是从文件最开始的位置开始下载。因而为了保存这些信息,所以才定义了这个NSData类型的这个属性:resumeData
下载完成后,将文件移动到指定的文件夹下面
#pragma mark - NSURLSessionDownloadDelegate
/**
* 下载完毕回调
*
* @param session 会话对象
* @param downloadTask 下载任务对象
* @param location 下载路径
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
// 获得 cache 文件夹路径
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 获得文件名
NSString *filePath = [cache stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
// 将下载好的文件移动到指定的文件夹
[[NSFileManager defaultManager] moveItemAtPath:location.path toPath:filePath error:NULL];
}
7、加载续传数据
/**
NSURLSession中,断点续传的关键点就在 resumeData
1. 一旦取消任务,在resumeData 中会记录住当前下载的信息,格式是 plist 的
2. 可以将续传数据写入磁盘
3. 程序重新运行,从磁盘加载 resumeData,修改其中保存的`临时文件名`,因为每一次启动 路径会发生变化
4. 使用 resumeData 开启一个续传任务!
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.2.dmg"];
self.url = url;
// 判断沙盒中是否有缓存数据?如果有,加载缓存数据
self.resumeData = [self loadResumeData:url];
if (self.resumeData != nil) {
// 使用缓存数据新建下载任务
self.task = [self.session downloadTaskWithResumeData:self.resumeData];
} else {
// 如果没有,直接下载
self.task = [self.session downloadTaskWithURL:url];
}
[self.task resume];
}
// 根据 URL 加载沙盒中的缓存数据
/**
如果程序再次运行,NSHomeDirectory会发生变化!在iOS 8.0才会有!
需要解决的,将缓存数据中的路径名修改成正确的
*/
- (NSData *)loadResumeData:(NSURL *)url {
// 1. 判断文件是否存在
NSString *filePath = [self resumeDataPath:url];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
// 以字典的方式加载续传数据
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
// 1. 取出保存的 - 临时文件的目录
// 临时目录/CFNetworkDownload_p78VgR.tmp
NSString *localPath = dict[@"NSURLSessionResumeInfoLocalPath"];
NSString *fileName = localPath.lastPathComponent;
// 计算得到正确的临时文件目录
localPath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
// 重新设置字典的键值
dict[@"NSURLSessionResumeInfoLocalPath"] = localPath;
// 字典转二进制数据,序列化(Plist的序列化)
return [NSPropertyListSerialization dataWithPropertyList:dict format:NSPropertyListXMLFormat_v1_0 options:0 error:NULL];
}
return nil;
}
/**
* 保存续传数据的文件路径
保存到`临时文件夹`中 - 保存成 "url.字符串的 md5.~resume"
*/
- (NSString *)resumeDataPath:(NSURL *)url {
// 1. 取得 md5 的字符串
NSString *fileName = url.absoluteString.md5String;
// 2. 拼接临时文件夹
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
// 3. 拼接扩展名,避免冲突
path = [path stringByAppendingPathExtension:@"~resume"];
NSLog(@"续传数据文件路径 %@", path);
return path;
}
// 暂停
- (IBAction)pause {
NSLog(@"暂停");
// 取消下载任务 可以给 nil 发送任何消息,不会有任何不良反应
[self.task cancelByProducingResumeData:^(NSData *resumeData) {
NSLog(@"续传数据长度 %tu", resumeData.length);
// 将续传数据写入磁盘
[resumeData writeToFile:[self resumeDataPath:self.url] atomically:YES];
// 记录续传数据
self.resumeData = resumeData;
// 释放任务
self.task = nil;
}];
}
// 继续
- (IBAction)resume {
NSLog(@"继续");
if (self.resumeData == nil) {
NSLog(@"没有续传数据");
return;
}
self.task = [self.session downloadTaskWithResumeData:self.resumeData];
// 释放续传数据
self.resumeData = nil;
// 继续任务
[self.task resume];
}
三、WebDav
WebDav服务器是基于 Apache 的,使用的是 HTTP 协议,可以当作网络文件服务器使用。上传文件的大小没有限制。
1、WebDav 的配置
WebDav完全可以当成一个网络共享的文件服务器使用!
# 切换目录
$ cd /etc/apache2
$ sudo vim httpd.conf
# 查找httpd-dav.conf
/httpd-dav.conf
"删除行首#"
# 将光标定位到行首
0
# 删除行首的注释
x
# 保存退出
:wq
# 切换目录
$ cd /etc/apache2/extra
# 备份文件(只要备份一次就行)
$ sudo cp httpd-dav.conf httpd-dav.conf.bak
# 编辑配置文件
$ sudo vim httpd-dav.conf
"将Digest修改为Basic"
# 查找Digest
/Digest
# 进入编辑模式
i
# 返回到命令行模式
如果MAC系统是10.11,则需要按下图修改对应的路径。
ESC
# 保存退出
:wq
# 切换目录,可以使用鼠标拖拽的方式
$ cd 保存put脚本的目录
# 以管理员权限运行put配置脚本
$ sudo ./put
设置两次密码: 123456
如果MAC系统是10.11,则会在用户根目录下生成三个文件,如下图:
注意:要在Mac 10.10以上配置Web-dav还需要在httpd.conf中打开以下三个模块
LoadModule dav_module libexec/apache2/mod_dav.so
LoadModule dav_fs_module libexec/apache2/mod_dav_fs.so
LoadModule auth_digest_module libexec/apache2/mod_auth_digest.so
2、WebDav 上传文件(PUT)
先上传图片,再换成大文件(视频),不修改上传的路径,让后面上传的大文件覆盖之前的图片。
- (void)webDavUpload{
// 1.URL -- 要上传文件的完整网络路径,包括文件名
NSURL *url = [NSURL URLWithString:@"http://192.168.1.105/uploads/abc.png"];
// 2.创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 2.1 设置请求方法
request.HTTPMethod = @"PUT";
// 2.2 设置身份验证
[request setValue:[self authString] forHTTPHeaderField:@"Authorization"];
// 3.上传
NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"001.png" withExtension:nil];
[[[NSURLSession sharedSession] uploadTaskWithRequest:request fromFile:fileUrl completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"data = %@,response = %@,error = %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding],response,error);
}] resume];
}
/**
* 获得授权字符字符串
*/
- (NSString *)authString{
NSString *str = @"admin:123456";
return [@"BASIC " stringByAppendingString:[self base64:str]];
}
/**
* 将字符串进行 base64编码,返回编码后的字符串
*/
-(NSString *)base64:(NSString *)string{
// 转换成二进制数据
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
// 返回 base64编码后的字符串
return [data base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
}
状态码
401 Unauthorized:没有权限。需要身份验证
201 新增文件,创建成功。
204 没有内容,文件覆盖成功,服务器不知道该告诉我们什么,所以没有内容返回。
授权的字符串格式
BASIC (admin:123456).base64。其中admin是用户名,123456是密码。
3、WebDav 上传进度跟进
/**
* 获得授权字符字符串
*/
- (NSString *)authString{
NSString *str = @"admin:123456";
return [@"BASIC " stringByAppendingString:[self base64:str]];
}
/**
* 将字符串进行 base64编码,返回编码后的字符串
*/
-(NSString *)base64:(NSString *)string{
// 转换成二进制数据
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
// 返回 base64编码后的字符串
return [data base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
}
#pragma mark - 上传操作
- (void)webDavUpload{
// 1.URL -- 要上传文件的完整网络路径,包括文件名
NSURL *url = [NSURL URLWithString:@"http://192.168.1.105/uploads/abc.png"];
// 2.创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 2.1 设置请求方法
request.HTTPMethod = @"PUT";
// 2.2 设置身份验证
[request setValue:[self authString] forHTTPHeaderField:@"Authorization"];
// 3.上传
NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"18-POST上传文件演练.mp4" withExtension:nil];
[[self.session uploadTaskWithRequest:request fromFile:fileUrl completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"data = %@,response = %@,error = %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding],response,error);
}] resume];
}
#pragma mark - NSURLSessionTaskDelegate 代理方法
/**
* 上传进度的跟进,只要实现这个方法就可以了
* @param bytesSent 本次上传字节数
* @param totalBytesSent 已经上传的总字节数
* @param totalBytesExpectedToSend 要上传文件的总大小
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
CGFloat progress = (CGFloat)totalBytesSent / totalBytesExpectedToSend;
NSLog(@"progress = %f",progress);
}
#pragma mark - 懒加载会话
- (NSURLSession *)session {
if (_session == nil) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
4、WebDav 删除文件(Delete)
- (void)webDavDelete{
// 1.URL -- 指定要删除文件的 url
NSURL *url = [NSURL URLWithString:@"http://192.168.1.105/uploads/abc.mp4"];
// 2.创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 2.1 设置请求方法
request.HTTPMethod = @"DELETE";
// 2.2 设置身份验证
[request setValue:[self authString] forHTTPHeaderField:@"Authorization"];
// 3.删除
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"data = %@,response = %@,error = %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding],response,error);
}] resume];
}
状态码
401 Unauthorized:没有权限。需要身份验证
404 Not Found 文件不存在
204 删除成功
5、WebDav GET/HEAD文件
- (void)webDavGet{
NSURL *url = [NSURL URLWithString:@"http://192.168.1.105/uploads/abc.png"];
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// 将返回的数据写到文件
[data writeToFile:@"/users/pkxing/desktop/abc.mp4" atomically:YES];
NSLog(@"%@===",response);
}] resume];
}
/**
GET & HEAD 都不需要身份验证,只是获取资源,不会破坏资源!
PUT & DELETE 会修改服务器资源,需要有权限的人执行,需要身份验证
*/
- (void)webDavHead {
// 1. URL,要删除的文件网络路径
NSURL *url = [NSURL URLWithString:@"http://192.168.40.2/uploads/321.png"];
// 2. request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 2.1 HTTP方法
request.HTTPMethod = @"HEAD";
// 3. session
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[data writeToFile:@"/Users/apple/Desktop/aa.mp4" atomically:YES];
// *** 不要只跟踪 data,否则会以为什么也没有发生
NSLog(@"%@ | %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], response);
}] resume];
}
6、WebDav总结(PUT/DELETE/GET/HEAD)
-
需要权限
- DELETE:删除资源
- PUT:新增或修改资源
-
不需要权限
- WebDav的'GET/HEAD' 请求不需要身份验证,因为对服务器的资源没有任何的破坏。
不支持POST上传,POST方法通常需要脚本的支持,提交给服务器的是二进制数据,同时告诉服务器数据类型。
四、NSURLSession注意点
The session object keeps a strong reference to the delegate until your app explicitly invalidates the session. If you do not invalidate the session by calling the invalidateAndCancel or resetWithCompletionHandler: method, your app leaks memory.
1、 在什么时候取消网络会话?
方法一:在viewWillDisappear 取消网络会话
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// 取消网络会话
[self.session invalidateAndCancel];
self.session = nil;
}
方法二:在每一个任务完成后,取消会话,不推荐
[self.session finishTasksAndInvalidate];
self.session = nil;
完成任务并取消会话(会话一旦取消就无法再创建任务)
会造成 session 频繁的销毁&创建
Attempted to create a task in a session that has been invalidated
错误原因:尝试在一个被取消的会话中创建一个任务。
方法三:建立一个‘网络管理单列’,单独负责所有的网络访问操作。
五、NSURLSession--POST上传
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"001.png" ofType:nil];
NSDictionary *fileDict = @{@"other.png":[NSData dataWithContentsOfFile:filePath]};
// 数据参数
NSDictionary *params = @{@"username":@"zhang"};
[self upload:fileDict fieldName:@"userfile[]" params:params];
}
// 分割符
#define boundary @"itheima"
#pragma mark - 上传操作
- (void)upload:(NSDictionary *)fileDict fieldName:(NSString *)fieldName params:(NSDictionary *)params{
// 1.URL -- 负责上传文件的脚本
NSURL *url = [NSURL URLWithString:@"http://192.168.1.105/post/upload-m.php"];
// 2.创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 2.1 设置请求方法
request.HTTPMethod = @"POST";
// 设置Content-Type
//Content-Type multipart/form-data;boundary=传值NB
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
// 不需要设置请求体 request.HTTPBody
// 获得文件数据
// session 上传的数据 通过 fromData 指定
NSData *fromData = [self fileData:fileDict fieldName:fieldName params:params];
[[[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:fromData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"%@--%@ -- %@",[NSJSONSerialization JSONObjectWithData:data options:0 error:NULL],response,error);
}] resume];
}
/**
* 返回要上传文件的文件二进制数据
*/
- (NSData *)fileData:(NSDictionary *)fileDict fieldName:(NSString *)fieldName params:(NSDictionary *)params{
NSMutableData *dataM = [NSMutableData data];
// 遍历文件字典 ---> 文件数据
[fileDict enumerateKeysAndObjectsUsingBlock:^(NSString *fileName, NSData *fileData, BOOL *stop) {
NSMutableString *strM = [NSMutableString string];
[strM appendFormat:@"--%@\r\n",boundary];
[strM appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",fieldName,fileName];
[strM appendString:@"Content-Type: application/octet-stream \r\n\r\n"];
// 插入 strM
[dataM appendData:[strM dataUsingEncoding:NSUTF8StringEncoding]];
// 插入 文件二进制数据
[dataM appendData:fileData];
// 插入 \r\n
[dataM appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 遍历普通参数
[params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSMutableString *strM = [NSMutableString string];
[strM appendFormat:@"--%@\r\n",boundary];
[strM appendFormat:@"Content-Disposition: form-data; name=\"%@\" \r\n\r\n",key];
[strM appendFormat:@"%@",obj];
// 插入 普通参数
[dataM appendData:[strM dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 插入 结束标记
NSString *tail = [NSString stringWithFormat:@"\r\n--%@--",boundary];
[dataM appendData:[tail dataUsingEncoding:NSUTF8StringEncoding]];
return dataM;
}
六、HTTPS
1、HTTPS 原理
Https是基于安全目的的Http通道,其安全基础由SSL层来保证。最初由netscape公司研发,主要提供了通讯双方的身份认证和加密通信方法。现在广泛应用于互联网上安全敏感通讯。
-
Https与Http主要区别
- 协议基础不同:Https在Http下加入了SSL层,
- 通讯方式不同:Https在数据通信之前需要客户端、服务器进行握手(身份认证),建立连接后,传输数据经过加密,通信端口443。Http传输数据不加密,明文,通信端口80。
-
SSL协议基础
- SSL协议位于TCP/IP协议与各种应用层协议之间,本身又分为两层:
- SSL记录协议(SSL Record Protocol):建立在可靠传输层协议(TCP)之上,为上层协议提供数据封装、压缩、加密等基本功能。
- SSL握手协议(SSL Handshake Procotol):在SSL记录协议之上,用于实际数据传输前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
- SSL协议位于TCP/IP协议与各种应用层协议之间,本身又分为两层:
SSL协议通信过程
(1) 浏览器发送一个连接请求给服务器,服务器将自己的证书(包含服务器公钥S_PuKey)、对称加密算法种类及其他相关信息返回客户端;
(2) 客户端浏览器检查服务器传送到CA证书是否由自己信赖的CA中心签发。若是,执行4步;否则,给客户一个警告信息:询问是否继续访问。
(3) 客户端浏览器比较证书里的信息,如证书有效期、服务器域名和公钥S_PK,与服务器传回的信息是否一致,如果一致,则浏览器完成对服务器的身份认证。
(4) 服务器要求客户端发送客户端证书(包含客户端公钥C_PuKey)、支持的对称加密方案及其他相关信息。收到后,服务器进行相同的身份认证,若没有通过验证,则拒绝连接;
(5) 服务器根据客户端浏览器发送到密码种类,选择一种加密程度最高的方案,用客户端公钥C_PuKey加密后通知到浏览器;
(6) 客户端通过私钥C_PrKey解密后,得知服务器选择的加密方案,并选择一个通话密钥key,接着用服务器公钥S_PuKey加密后发送给服务器;
(7) 服务器接收到的浏览器传送到消息,用私钥S_PrKey解密,获得通话密钥key。
(8) 接下来的数据传输都使用该对称密钥key进行加密。
上面所述的是双向认证 SSL 协议的具体通讯过程,服务器和用户双方必须都有证书。由此可见,SSL协议是通过非对称密钥机制保证双方身份认证,并完成建立连接,在实际数据通信时通过对称密钥机制保障数据安全性
2、NSURLSession--HTTPS
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 创建 url
NSURL *url = [NSURL URLWithString:@"https://mail.itcast.cn"];
// 发起请求
[[self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"data = %@,response = %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding],response);
}] resume];
}
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9843)
错误原因:没有信任证书
如何信任证书?
通过代理方法告诉服务器信任证书
#pragma mark - NSURLSessionTaskDelegate 代理方法
// 收到服务器发过来的证书后回调
// 提示:此代理方法中的代码是固定的,只要会 cmd+c / cmd + v 就可以了
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
NSLog(@"protectionSpace - %@",challenge.protectionSpace);
// 判断是否是信任服务器证书
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
// 使用受保护空间的服务器信任创建凭据
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 通过 completionHandler 告诉服务器信任证书
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
参数介绍
challenge '挑战' 安全质询,询问是否信任证书
completionHandler 对证书处置的回调
- NSURLSessionAuthChallengeDisposition: 通过该参数告诉服务器如何处置证书
NSURLSessionAuthChallengeUseCredential = 0, 使用指定的凭据,即信任服务器证书
NSURLSessionAuthChallengePerformDefaultHandling = 1, 默认处理,忽略凭据。
NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, 整个请求取消,忽略凭据
NSURLSessionAuthChallengeRejectProtectionSpace = 3, 本次拒绝,下次再试
- NSURLCredential
#pragma mark - 懒加载 session
- (NSURLSession *)session {
if (_session == nil) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
3、NSURLConnection--HTTPS
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 创建 url 对象
NSURL *url = [NSURL URLWithString:@"https://mail.itcast.cn"];
// 创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 发送请求
[NSURLConnection connectionWithRequest:request delegate:self];
}
#pragma mark - NSURLConnection 代理方法
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
NSLog(@"change = %@",challenge.protectionSpace);
// 判断是否是服务器信任证书
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
// 创建凭据
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 发送信任告诉服务器信任证书
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
}
/**
* 接收到服务器响应
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// 清空数据
[self.data setData:nil];
}
/**
* 接收到服务器返回的数据调用
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// 拼接数据
[self.data appendData:data];
}
/**
* 请求完毕调用
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(@"self.data = %@",self.data);
}
- (NSMutableData *)data {
if (_data == nil) {
_data = [[NSMutableData alloc] init];
}
return _data;
}
七、AFNetworking
1、AFN介绍
- AFN
- 目前国内开发网络应用使用最多的第三方框架
- 是专为 MAC OS & iOS 设计的一套网络框架
- 对 NSURLConnection 和 NSURLSession 做了封装
- 提供有丰富的 API
- 提供了完善的错误解决方案
- 使用非常简单
学习第三方框架的步骤
-
获取框架
- 克隆代码 $ git clone https://github.com/AFNetworking/AFNetworking.git
- 更新代码 $ git pull
运行演示程序
编写测试程序
2、AFN演练
2.1、AFN--Get/Post
1、GET请求
- (void)get{
// 创建请求管理器
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
// 发送请求
[manager GET:@"http://192.168.1.105/demo.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"%@---%@",responseObject,[responseObject class]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"error = %@",error);
}];
}
2、Get请求
- (void)getLogin01{
// 创建请求管理器
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
// 发送登录请求
[manager GET:@"http://192.168.1.105/login.php?username=zhangsan&password=zhang" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"%@---%@",responseObject,[responseObject class]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"error = %@",error);
}];
}
3、Get请求
- (void)getLogin02{
// 创建请求管理器
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
// 封装请求参数
NSDictionary *params = @{@"username":@"张三",@"password":@"zhang"};
// 发送登录请求
[manager GET:@"http://192.168.1.105/login.php" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"%@---%@",responseObject,[responseObject class]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"error = %@",error);
}];
}
4、POST请求
- (void)postLogin{
// 创建请求管理器
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
// 封装请求参数
NSDictionary *params = @{@"username":@"zhangsan",@"password":@"zhang"};
// 发送登录请求
[manager POST:@"http://192.168.1.105/login.php" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"%@---%@",responseObject,[responseObject class]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"error = %@",error);
}];
}
5、AFN的好处
没有了 URL 的概念
完成回调的结果已经做好了序列化
完成回调在主线程,不需要考虑线程间的通信
GET请求的参数可以使用字典封装,不需要再记住 URL 的拼接格式
不需要添加百分号转义,中文,特殊字符(空格,&)等。OC增加百分转义的方法不能转义所有特殊字符,AFN处理的很好。
POST请求不用设置HTTPMethod/HTTPBody
2.2、AFN--SAX解析
- (void)xml{
// 创建请求管理器
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
// 设置响应解析器为 xml 解析器
manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
// 发送登录请求
[manager GET:@"http://192.168.1.105/videos.xml" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"%@---%@",responseObject,[responseObject class]);
[HMSAXVideo saxParser:responseObject finished:^(NSArray *data) {
NSLog(@"%@",data);
}]
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"error = %@",error);
}];
}
2.3、AFN文件上传
- (void)postUpload {
AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager];
// 上传
NSDictionary *params = @{@"username": @"da xiagua"};
[mgr POST:@"http://localhost/upload/upload-m.php" parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
/**
参数
1. 本地文件 URL
2. name: 负责上传文件的字段名,咨询公司的后端程序员,或者有文档
3. error
*/
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"04.jpg" withExtension:nil];
[formData appendPartWithFileURL:fileURL name:@"userfile[]" error:NULL];
// 上传多个文件
/**
参数
1. 本地文件 URL
2. name: 负责上传文件的字段名,咨询公司的后端程序员,或者有文档
3. fileName: 保存在服务器的文件名
4. mimeType: 告诉服务器上传文件的类型1
5. error
*/
NSURL *fileURL2 = [[NSBundle mainBundle] URLForResource:@"AppIcon.jpg" withExtension:nil];
[formData appendPartWithFileURL:fileURL2 name:@"userfile[]" fileName:@"001.jpg" mimeType:@"application/octet-stream" error:NULL];
} success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"%@", responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"%@", error);
}];
}
2.4、AFN 文件下载
- (void)download{
// 1. url
NSURL *url = [NSURL URLWithString:@"http://localhost/123.mp4"];
// 2. AFN 上传
AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager];
// 3. request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 4. 开始下载任务
// *** 学习第三方框架的好处:可以发现自己的知识空缺点,跟大牛直接学习!
// iOS 7.0 之后推出的,专门用于跟踪进度的类,可以跟踪`进度树`
NSProgress *progress = nil;
[[mgr downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
NSLog(@"file = %@",targetPath);
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(@"response = %@,filePath = %@",response,filePath);
}] resume];
// 此处已经获得进度对象 - `监听`进度
NSLog(@"%@", progress);
// KVO监听进度
[progress addObserver:self forKeyPath:@"completedUnitCount" options:0 context:nil];
}
// 是所有 KVO 统一调用的一个方法,最好判断一下对象类型
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// NSLog(@"%@", object);
// 判断对象是否是 NSProgress 对象
if ([object isKindOfClass:[NSProgress class]]) {
NSProgress *p = object;
NSLog(@"%lld - %lld", p.completedUnitCount, p.totalUnitCount);
NSLog(@"%@ - %@", p.localizedDescription, p.localizedAdditionalDescription);
// *** 显示百分比
NSLog(@"%f", p.fractionCompleted);
}
}