零基础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
喜欢就支持一下吧
点赞0 分享