开头
- 这是分篇章,整篇请看零基础iOS开发学习日记
访问网络
实际用处
- 获取网络数据
- 上传、下载数据
基础要点
- 访问网络,可以理解为A(客户端)通过一种方式(协议)到B(服务端)送(上传)/拿(下载)/告诉(传输)/听到(取回)消息的过程
- 而这个协议往往是http或者https,这个服务端则是一个网站,而后面的动作则是
post/get/put
等方式 - 在实际访问过程中,只要进行的操作实际上只有,确定地址(访问网站),确定路径(方式)和钥匙(参数),取回消息,而具体显示内容这一工作,一般由浏览器自己去解析,或者另外进行数据处理
- 因为我对网络的理解没有特别深,所以这部分主要以,我在仿写项目中的案例,进行梳理,尽量能梳理清楚,覆盖能用到的功能
- OC中目前访问网络主要用到
NSURLSessionTask
- 下面的案例,是建立在本地上的
apache
服务器,也可以用这网址测试httpbin
请求和响应
- 请求体
request
,包含请求行,请求头,请求体(POST方式有)。请求行包括请求方式,资源地址,协议版本;请求头包含申请格式、处理缓存的方式、显示的客户端型号等;请求体包含POST方式的参数 - 响应体response,包含响应行,响应头,响应体。响应行包含协议的版本,访问的状态结果;响应头包含格式键值对;响应体包含显示的正文
NSURLSessionTask
- OC网络访问的主要类,主要有三个调用函数
GET
实际用处
- 获取网络资源、数据
- 搜索
- 效率高,但是能被浏览器缓存,不安全
基础用法
- 普通访问界面
//确定网站
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/demo.json"];
//生成请求
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//建立访问任务
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//错误判断
if (error) {
NSLog(@"连接错误 %@", error);
return;
}
//响应
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
//响应头状态结果的判断
if (httpResponse.statusCode == 200 || httpResponse.statusCode == 304) {
//解析数据,显示的正文
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
NSLog(@"%@", data);
}
else{
NSLog(@"服务器内部错误");
}
}];
//开始访问
[task resume];
复制代码
- 带参数
NSString *name = @"zhang san";
NSString *pwd = @"123";
NSString *strUrl = [NSString stringWithFormat:@"http://127.0.0.1/php/login.php?username=%@&password=%@", name, pwd];
//当地址中出现空格或者汉子 url返回空。下面这个函数可以转义字符串中的空格
strUrl = [strUrl stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL *url = [NSURL URLWithString:strUrl];
复制代码
小结
- 这里也可以看出,
GET
访问的方式其实就是将需要的参数显行的放在网址中,具体结构为网址?参数1=值&参数2=值....
- 也因为这种结构的原因的,
GET
不适用于传输大量参数,或者传输隐私需求较高的参数
POST
实际用处
- 发送隐私数据,发送比较多的数据
- 不会被浏览器缓存
- 效率低,但是相对安全
基础用法
//确定网址
NSString *strUrl = @"http://127.0.0.1/php/login.php";
NSURL *url = [NSURL URLWithString:strUrl];
//因为要添加请求体,所以要用可变请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//设置请求头 请求体
request.HTTPMethod = @"post";
//添加请求体
NSString *body = @"username=zhang san&password=123";
request.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding];
//建立访问任务
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"连接错误 %@", error);
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200 || httpResponse.statusCode == 304) {
//解析数据
//解析出字符串
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
//解析出键值对
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@", dic[@"userName"]);
}
else{
NSLog(@"服务器内部错误");
}
}];
[task resume];
复制代码
自定义请求体上传文件
- 主要是为了整理一下自定义请求体
- 谷歌浏览器抓不到请求体,我用的safari
- 请求体内容
------WebKitFormBoundaryOMAsfP2G4gU9nai3
Content-Disposition: form-data; name="userfile"; filename="99.png"
Content-Type: image/png
文件二进制数据
------WebKitFormBoundaryOMAsfP2G4gU9nai3--
复制代码
- 对应的请求头中的内容。
Content-Type
是表单类型,由html中设置,----WebKitFormBoundaryUmfoTOZhwXatboQB
是浏览器自己生成的分割符
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryUmfoTOZhwXatboQB
复制代码
- 自定义请求体,
kBOUNDARY
自定义分割符
- (NSData *)makeBody:(NSString *)fieldName andfilePath:(NSString *)filePath{
//可变二进制
NSMutableData *mData = [NSMutableData data];
NSMutableString *mString = [NSMutableString new];
[mString appendFormat:@"--%@\r\n", kBOUNDARY];
//name对应表单名 filename对应文件名
[mString appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, [filePath lastPathComponent]];
//二进制数据格式
[mString appendString:@"Content-Type: application/octet-stream\r\n"];
[mString appendString:@"\r\n"];
[mData appendData:[mString dataUsingEncoding:NSUTF8StringEncoding]];
//上传文件内容的二进制
NSData *data = [NSData dataWithContentsOfFile:filePath];
[mData appendData:data];
NSString *end = [NSString stringWithFormat:@"\r\n--%@--",kBOUNDARY];
[mData appendData:[end dataUsingEncoding:NSUTF8StringEncoding]];
return mData.copy;
}
复制代码
- 设置请求体
request.HTTPBody = [self makeBody:fieldName andfilePath:filePath];
复制代码
- 设置请求头中的
Content-Type
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kBOUNDARY] forHTTPHeaderField:@"Content-Type"];
复制代码
小结
- POST方式其实与GET相差无几,只不过是传参数的方式不同罢了
- 在实际开发中,在面对隐私数据传输的时候,大部分解决方法是通过编码方式,进行数据保护
- 在AFN中参数可以通过参数字典的方式进行传输,也比较方便
- 自定义请求体本质上就是通过拼接字符串,来组成指定格式的数据,在实际开发中,根据前端需要的数据格式,进行对应的拼接和内容填充即可
PUT
基础用法
//获取文件
NSString *path = [[NSBundle mainBundle] pathForResource:@"06.jpg" ofType:nil];
NSURL *url = [NSURL URLWithString:@"https://httpbin.org/put"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//设置访问方式
request.HTTPMethod = @"put";
//获取数据
NSData *data = [NSData dataWithContentsOfFile:path];
NSLog(@"%@", data);
NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:data completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"连接错误 %@", error);
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200 || httpResponse.statusCode == 304) {
//解析数据
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@", dic[@"headers"]);
}
else{
NSLog(@"服务器内部错误");
}
}];
[task resume];
复制代码
- 通过文件路径上传
NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"06.jpg" withExtension:nil];
复制代码
put权限
- 在实际使用时,put需要设置请求头中的
Authorization
,但是现在mac好像配不了上传的webDav
服务器,就只能用httpbin测试一下功能行不行,具体要用的时候,设置请求头就好
[request setValue:@"Basic YWRtaW46MTIzNDU2" forHTTPHeaderField:@"Authorization"];
复制代码
下载
基础用法
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/itcast/images/head1.png"];
NSURLSessionDownloadTask *downloadTask = [[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@", location);
}];
[downloadTask resume];
复制代码
- 但是,这有一个问题。由于
NSURLSessionTask
的特性,下载的数据会保存在tmp的目录下,下载完毕后就会被删除,所以需要另外保存
//获取路径,保存在doc目录下
NSString *saveLocation = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"head1.png"];
//复制到其他位置
[[NSFileManager defaultManager] copyItemAtPath:location.path toPath:saveLocation error:NULL];
复制代码
断点续传
- 断点续传的含义就是,当一个下载开始,在任何时段,都可以进行任意的暂停和继续,并且保存先前下载好的数据
- 那么,就需要用到代理,和全局的任务对象,进行统一操作。另外,基本上所有的对象,都是全局对象
- 控件是通过
storyboard
添加的 - 对象设置,对象的设置结合下面可以更理解
@property (nonatomic, strong) NSURLSession *session; //会话
@property (nonatomic, strong) NSData *resumeData; //缓存数据
@property (nonatomic, strong) NSMutableDictionary *downloadCache; //下载缓存池,管理下载
@property (nonatomic, strong) NSString *urlStr; //下载目标网址
@property (nonatomic, strong) NSString *saveLaction; //保存路径
@property (nonatomic, strong) NSString *fileName; //文件名
@property (nonatomic, strong) NSString *tmpPath; //缓存路径
@property (nonatomic, strong) NSFileManager *fileManager; //文件管理对象,用于判断文件是否存在和文件增删改查
复制代码
- 懒加载,根据下载任务的不同,会话,下载缓存池,文件管理器实际上是不会变的,所以使用懒加载生成
- (NSURLSession *)session{
if (!_session) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
- (NSMutableDictionary *)downloadCache{
if (!_downloadCache) {
_downloadCache = [NSMutableDictionary dictionaryWithCapacity:10];
}
return _downloadCache;
}
- (NSFileManager *)fileManager{
if (!_fileManager) {
_fileManager = [NSFileManager defaultManager];
}
return _fileManager;
}
复制代码
- 设置接口内容,这里就以单个网站做事例,如果要在一个页面控制多个内容的下载,实质上就通过按钮的值来赋值即可,通过数组或者字典来保存
- (void)viewDidLoad {
[super viewDidLoad];
NSString *urlStr = @"http://127.0.0.1/3.zip"; //目标网页
self.fileName = [urlStr lastPathComponent]; //文件名,带扩展名
self.urlStr = urlStr; //作为下载缓存池的标识
NSString *tmpStr = [NSString stringWithFormat:@"%@.tmp", [self.fileName stringByDeletingPathExtension]]; //带tmp扩展名的文件名
self.tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tmpStr]; //缓存路径的文件,用来保存断续下载的数据
self.saveLaction = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:self.fileName]; //文件的保存路径
}
复制代码
- 代理方法,需要注意的是,使用代理,就不要使用回调函数,底层优先使用回调。
//下载完成
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location{
NSLog(@"%@", location);
//复制到其他位置
[self.fileManager copyItemAtPath:location.path toPath:self.saveLaction error:NULL];
NSLog(@"%@", self.saveLaction);
//删除tmp文件
[self.fileManager removeItemAtPath:self.tmpPath error:nil];
//从下载缓存池中移除该下载
[self.downloadCache removeObjectForKey:self.urlStr];
}
//获取进度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
float process = totalBytesWritten * 1.0 / totalBytesExpectedToWrite;
self.processView.progress = process;
NSLog(@"%f", process);
}
//续传,并没有用到,可以用来获取已下载的进度,就要结合app信息的保存里,后面整理了再改
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
float offset = fileOffset * 1.0 / expectedTotalBytes;
NSLog(@"续传 %f", offset);
}
复制代码
- 准备了下载任务,抓到了下载过程的状态,就可以进行开始、暂停、下载的处理。那么,就要明确一下,什么情况下可以开始,什么情况下可以暂停,什么情况下可以继续
- 开始,没有同名下载;保存目录下没有当前文件;缓存的目录下没有缓存数据
- 暂停,有任务
- 继续,已有保存好的数据;当前没有任务
- (void)download{
NSURL *url = [NSURL URLWithString:self.urlStr];
//建立该下载任务
NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithURL:url];
//添加到下载缓存池
self.downloadCache[self.urlStr] = downloadTask;
[downloadTask resume];
}
- (IBAction)startClick:(id)sender {
//存在该下载任务
if (self.downloadCache[self.urlStr]) {
NSLog(@"已经在下载了");
return;
}
//保存目录或者缓存目录存在该文件
if ([self.fileManager fileExistsAtPath:self.saveLaction] || [self.fileManager fileExistsAtPath:self.tmpPath]) {
NSLog(@"文件已经存在");
return;
}
[self download];
}
- (IBAction)pauseClick:(id)sender {
//没有这个下载任务
if (!self.downloadCache[self.urlStr]) {
NSLog(@"没有任务");
return;
}
[self.downloadCache[self.urlStr] cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
//保存续传数据
self.resumeData = resumeData;
//续传数据保存在沙盒
[self.resumeData writeToFile:self.tmpPath atomically:YES];
NSLog(@"已经保存数据");
//给空对象发送消息不做任何处理
//删除该下载任务
self.downloadCache[self.urlStr] = nil;
}];
}
- (IBAction)resumeClick:(id)sender {
//已有下载任务
if (self.downloadCache[self.urlStr]) {
NSLog(@"已经有下载任务了");
return;
}
//从沙盒中读取续传数据
//缓存目录下是否有这个数据
if ([self.fileManager fileExistsAtPath:self.tmpPath]) {
self.resumeData = [NSData dataWithContentsOfFile:self.tmpPath];
NSLog(@"本地有暂停的数据");
}
if (self.resumeData == nil) {
NSLog(@"本地没有暂停的数据");
return;
}
//重新给缓存池添加该下载任务
self.downloadCache[self.urlStr] = [self.session downloadTaskWithResumeData:self.resumeData];
[self.downloadCache[self.urlStr] resume];
self.resumeData = nil;
}
复制代码
断点续传要点总结
- 对于字典来说,无论是
remove
还是直接设置为nil
,还是在没有设置这个键的时候,都不会影响判断。一句话,只要这个键没有对应的值,那么就是nil
,而字典不会管这个字典里有没有这个键 tmp
文件,其实只是个plist,保存缓存文件的相关信息,并不直接保存缓存数据,系统是通过这个文件,寻找缓存数据在进行加载的,有机会了解底层的处理,这里挖个坑
网络数据解析
JSON
XML
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END