IOS 开发(六) — 网络 & 文件管理

527 阅读8分钟

一、网络

APP 内网络基本流程

  • NSURL 系统将字符串进行封装
  • NSURLRequest 在NSURL基础上 进一步封装成url参数
  • NSURLSession 调用系统响应函数进行网络请求发送 一个Session 可以创建多个请求,封装Request为Task,控制状态、开始取消、进度,一个App可以设置多个 Session

资源请求的两种方式

  • 网络资源路径

    • URLWithString
  • 本地文件的路径

    • fileURLWithPath 会自动在前面增加 file://

1-1、创建一个简单的网络请求

1、创建一个listloader,进行数据请求

image.png

2、GTListLoader.h 中创建暴露一个loadListData 的方法


#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN

/**
 列表请求
 */
@interface GTListLoader : NSObject
-(void)loadListData;
@end

NS_ASSUME_NONNULL_END

3、GTListLoader.m 创建执行方法

#import "GTListLoader.h"
@implementation GTListLoader
-(void)loadListData{
}
@end

4、ViewController 的 viewdidLoad 中进行调用

// 引用 创建listLoader属性
#import "GTListLoader.h"
@interface ViewController ()<UITableViewDataSource,UITableViewDelegate>
@property(nonatomic,strong,readwrite) GTListLoader *listLoader;
@end

// 方法内执行 调用loader网络请求

#import "GTListLoader.h"
@implementation GTListLoader
-(void)loadListData{
    NSString *urlString = @"http://v.juhe.cn/toutiao/index?type=top&key=97ad8816fcc2082626eaf798bad3d54e";
//    1、封装request 包含网址和参数
    NSURL *listURL = [NSURL URLWithString:urlString];
//   __unused NSURLRequest *listRequest = [NSURLRequest requestWithURL:listURL];
//    2、dataTask 属于session 这里是默认session
    NSURLSession *session = [NSURLSession sharedSession];
//    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:listRequest];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:listURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"");
    }];
    [dataTask resume];
    
    NSLog(@"");
}
@end

1-2、 NSURLSession

  • 创建 NSURLSession 会话

  • 配置会话 NSURLSession

    • timeout 时间
    • cookie 缓存策略
    • 最大并发数、是否支持蜂窝数据
    • 其他网络行为的行为和策略
    • 根据 Configuration 的不同,提供默认、后台、以及自定义
  • 创建 NSURLSessionTask

    • dataTaskWithRequest
    • downloadTaskWithRequest
  • 四种 Task 的区别 都继承于 NSURLSessionTask

    • dataTask : 处理简单的数据流 如 JSON 数据
    • downloadTask: 大数据下载,断点续传和进度等
    • uploadTask:长传数据
    • streamTask: 流数据

创建好的 sessionTask 是 suspend 挂起状态,调用 resume 开始执行 分成不同阶段,处理不同问题

  • 处理 Response
  • 1、通过 Handler block dataTaskWithRequest: request completionHandler
  • 2、通过 Delegate NSURLSessionDelegate session 相关策略 NSURLSessionTaskDelegate 处理证书、跳转等通用逻辑 (NSURLSessionDataDelegate、NSURLSessionDownloadDelegate、NSURLSessionStreamDelegate)

IOS 9 新增 新特性,默认都要求 HTTPS 进行请求, 项目设置的 info 中设置 NSAppTransportSecurity 属性, 否则会报错 App Transport Security has blocked a cleartext HTTP connection to v.juhe.cn since it is insecure. Use HTTPS instead or add this domain to Exception Domains in your Info.plist.

非 https 需要添加 以下信息

image.png

系统 NSURLSession 加载数据流程

  • 1、创建 & 使用默认 Session
  • 2、通过地址和参数创建 Task
  • 3、 开始 & 取消 Task
  • 4、Handler 中处理数据

1-3、使用开源的网络框架

为什么使用开源网络框架? 解决上文中需要大量设置,需要编码、压缩,session 回调方式也不友好,还要自己设置 resume 等 delegate 等回调,解决代码书写大量并且不优雅的问题。

使用 cocoapods 来集成和管理第三方库

image.png

扩展

依赖管理第三方资源
创建一个 project
-   多项目共用代码逻辑
-   开源项目集成
project & workspace
多个 project 合成 一个 workspace
-   大量第三方库的使用,相互引用和依赖
-   依赖配置、编译参数、几十更新、版本管理

常用的代码管理方式
- git subModule 基于 Git 管理 使用简单 功能少 debug 方便
- CocoaPods Ruby 语言 安装和使用 中心化管理 生成 workspace debug方便
- Carthage Swift 语言 安装和使用 去中心化的管理 提供framework 文件 debug不便
CocoaPods 用来集成 https://cocoapods.org 使用最多 
工作流程
1、根据 podfile 中根据配置 去服务器上去下载响应代码,集成成 Pods
2、当下载好后,根据写的依赖关系,根据 Pods 和 我们 MyProject 一起生成 workspace

1、安装 cocoapods

brew install cocoapods

2、新建 podfile 文件 image.png

3、下载

pod install 
pod update

image.png

GTListLoader.m

-(void)loadListData{
    // 使用开源框架
    [[AFHTTPSessionManager manager] GET:@"http://v.juhe.cn/toutiao/index?type=top&key=97ad001bfcc2082e2eeaf798bad3d54e" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
        NSLog(@"");
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"");
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"");
    }];
}

1-4、JSON 格式解析

image.png

NSError *jsonError;
id jsonObj = [NSJSONSerialization JSONObjectWithData:data options:0 error: &jsonError];

image.png

可见流格式已经被解析成了字典

1-5、结构化数据

解析后的是一个字典对象

  • 字典和数组 - 语意不明 & 使用不便
  • 使用结构化的数据 表示特定的对象
  • 通过头文件语意明确 & 处理简单的逻辑
  • JSON --> @[Model,Model...]

image.png

1、 定义字段,创建方法

GTListItem.h

@interface GTListItem: NSObject
@property(nonatomic,copy,readwrite) NSString *category;
@property(nonatomic,copy,readwrite) NSString *picUrl;
@property(nonatomic,copy,readwrite) NSString *uniqueKey;
@property(nonatomic,copy,readwrite) NSString *title;
@property(nonatomic,copy,readwrite) NSString *date;
@property(nonatomic,copy,readwrite) NSString *authorName;
@property(nonatomic,copy,readwrite) NSString *articleUrl;

// 这个函数解析赋值上面的所有属性
-(void)configWithDictionary:(NSDictionary *)dictionary;

2、进行字段与接口数据的转化映射

GTListItem.m

#import "GTListItem.h"

@implementation GTListItem

-(void)configWithDictionary:(NSDictionary *)dictionary{
#warning 类型是否匹配
    self.category = [dictionary objectForKey: @"category"];
    self.picUrl  = [dictionary objectForKey: @"thumbnail_pic_s"];
    self.uniqueKey  = [dictionary objectForKey: @"uniquekey"];
    self.title  = [dictionary objectForKey: @"title"];
    self.date  = [dictionary objectForKey: @"date"];
    self.authorName  = [dictionary objectForKey: @"author_name"];
    self.articleUrl  = [dictionary objectForKey: @"url"];
};
@end

3、接口中调用赋值

NSURLSessionDataTask *dataTask = [session dataTaskWithURL:listURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSError *jsonError;
        id jsonObj = [NSJSONSerialization JSONObjectWithData:data options:0 error: &jsonError];
        // 从jsonObj 中取 result 在取 data
        NSArray *dataArray = [((NSDictionary *)[((NSDictionary *)jsonObj) objectForKey:@"result"]) objectForKey:@"data"];
        // 赋值
        NSMutableArray *listItemArray = @[].mutableCopy;
        for(NSDictionary *info in dataArray){
            GTListItem *listItem = [[GTListItem alloc] init];
            // 调用 listItem 上的方法进行赋值
            [listItem configWithDictionary:info];
            // 添加到数组上
            [listItemArray addObject:listItem];
        }
        NSLog(@"");
    }];

上面是手动从Modal 转化到 JSON的过程, 但是类型很容易出错, 写起来也比较繁琐, 下面是一些 JSON 到 Model 转化的一些开源, 一行代码即可解决

JSON Model 开源项目

  • 简化 NSData --- JSON ---- Model 流程
  • 避免类型转化错误/属性和对象不一致
  • 相互转换
  • 常用的开源项目
    • YYModel / Mantle / MJExtension

image.png

完整列表加载流程

  • 通过网络接口加载数据
  • 使用 NSJsonSerialization 解析处理网络请求
  • 数据 Model 化
  • 列表加载 Model 数组

1-6、Modal 应用到列表中

1、 定义一个 block

GTListLoader.m

// 声明一下GTListItem 使用的时候在引用
@class GTListItem;
NS_ASSUME_NONNULL_BEGIN
typedef void(^GTListLoaderfinishBlock)(BOOL success, NSArray<GTListItem *> *dataArray);

// 改造。 加载接口通过 block 去解析返回的结果
//-(void)loadListData;
-(void)loadListDataWithFinishBlock:(GTListLoaderfinishBlock)finishBlock;

2、更改请求

GTListLoader.h

//-(void)loadListData{

// 改造请求
-(void)loadListDataWithFinishBlock:(GTListLoaderfinishBlock)finishBlock{
  //...
  
  // 希望所有回调都在主线程,增加一个dispatch
    dispatch_async(dispatch_get_main_queue(), ^{
        if(finishBlock){
            finishBlock(error == **nil**, listItemArray.copy);
        }
    });
}

3、修改 loadListData 的调用

ViewController.m

@property(nonatomic,strong,readwrite) NSArray *dataArray;

//...
//  [self.listLoader loadListData];
//  改造 loadListData 调用的方法 处理 self 循环引用
__weak typeof(self)wself = self;
[self.listLoader loadListDataWithFinishBlock:^(BOOL success, NSArray<GTListItem *> * _Nonnull dataArray) {
    __strong typeof(wself) strongSelf = wself;    
    // 赋值
    strongSelf.dataArray = dataArray;
    NSLog(@"");
}];

4、修改 layoutTableViewCell 方法 GTNormalTableViewCell.h


#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class GTListItem;

/**
 点击删除按钮
 */

@protocol GYNormalTableViewCellDelegate <NSObject>
- (void)tableViewCell:(UITableViewCell *)tableViewCell clickDeleteButton:(UIButton *)deleteButton;

@end

/**
 新闻列表cell
 */
@interface GTNormalTableViewCell : UITableViewCell

// 暴露这个方法
//-(void) layoutTableViewCell;
- (void) layoutTableViewCellWithItem: (GTListItem *)item;

@end
NS_ASSUME_NONNULL_END

5、修改 GTListItem 中 layoutTableViewCell 代码逻辑

GTNormalTableViewCell.m

#import "GTListItem.h"
- (void) layoutTableViewCellWithItem: (GTListItem *)item{
//-(void) layoutTableViewCell{
    self.titleLabel.text =item.title;
    self.sourceLabel.text=item.authorName;
    [self.sourceLabel sizeToFit];
    
    self.commentLabel.text= item.category;
    [self.commentLabel sizeToFit];
    self.commentLabel.frame = CGRectMake(self.sourceLabel.frame.origin.x + self.sourceLabel.frame.size.width + 15, self.commentLabel.frame.origin.y , self.commentLabel.frame.size.width,self.commentLabel.frame.size.height);
    
    self.timeLabel.text= item.date;
    [self.timeLabel sizeToFit];
    
    self.timeLabel.frame = CGRectMake(self.commentLabel.frame.origin.x + self.commentLabel.frame.size.width + 15, self.timeLabel.frame.origin.y , self.timeLabel.frame.size.width,self.timeLabel.frame.size.height);
    
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:item.picUrl]]];
    
    self.rightImageView.image = image;
}

6、调用 layoutTableViewCellWithItem

ViewController.m

// 每次的 TableView 需要布局的时候调用这个方法
// [cell layoutTableViewCell];
   [cell layoutTableViewCellWithItem:[self.dataArray objectAtIndex:indexPath.row]];

image.png

1-7、 展示加载详情页内容

1、初始化传入 页面url

GTDetailViewController.m

// 文章url

@property(nonatomic,copy,readwrite) NSString *articleUrl;

//...

/**
 传入url 展示详情页
 */
-(instancetype)initWithUrlString:(NSString *)urlString{
    self= [super init];
    if(self){
        self.articleUrl = urlString;
    }
    return self;
};

2、暴露函数方法

GTDetailViewController.h

#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface GTDetailViewController : UIViewController
/**
 初始化函数 暴露 文章底层页
 */
-(instancetype)initWithUrlString:(NSString *)urlString;
@end
NS_ASSUME_NONNULL_END

3、修改 didSelectRowAtIndexPath 方法, 点击时传入文章 url

ViewController.m

// 引入 
#import "GTListItem.h"

// 点击哪个cell,获取index 实现一个点击进入对应页面的逻辑, 新建一个 uiviewcontroller
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    // 进入新建的ViewController
    // UIViewController *controller = [[UIViewController alloc] init];
    // 进入GTDetailViewController
    
    // 通过 index 索引 获取dataArray 中的 每个信息
    GTListItem *item = [self.dataArray objectAtIndex:indexPath.row];
    GTDetailViewController *controller = [[GTDetailViewController alloc] initWithUrlString: item.articleUrl];
//    GTDetailViewController *controller = [[GTDetailViewController alloc] init];
    
    controller.view.backgroundColor = [UIColor systemPinkColor];
    controller.title = [NSString stringWithFormat:@"%@", @(indexPath.row)];
    [self.navigationController pushViewController:controller animated:YES];
}

output1.gif

二、IOS 文件管理

2-1、IOS 沙盒文件结构

沙盒机制 只能访问程序自己的目录,每个 App 特有的文件夹

  • Docunment 可以进行备份和恢复,体积较大,一般存档用户数据
  • Library 开发者最长使用的文件夹,可以自定义子文件夹 Preferences: 用户偏好设置,USUserDefault,支持备份 Cache: 不需要缓存,体积较大,一般的删除缓存操作
  • tmp 临时文件不会备份,启动时有可能被清除

NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);


// 调用
[self _getSandBoxPath];
    

-(void)_getSandBoxPath{
    NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
    
    NSLog(@"");
}

2-2、文件管理 NSFileManager

  • 单例,提供 App 内文件 & 文件夹 管理功能
  • 创建文件、删除文件、查询文件、移动和复制等
  • 读取文件内容 & 属性
  • 通过 NSURL 或者 NSString 作为 Path

NSFileManagerDelegate

  • 提供移动、复制、删除等操作的具体自定义实现

image.png

2-3、NSFileHandle

  • 读取文件 & 写文件
  • 读取指定的长度 & 在指定位置追加/截断
  • 截断 & 立即刷新
  • 常用于追加数据

image.png

image.png



-(void)_getSandBoxPath{
    // document 文件夹路径
    NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
    
    NSString *cachePath = [pathArray firstObject];
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    NSString *dataPath = [cachePath stringByAppendingPathComponent:@"GTData"];
    
    NSError *creatError;
    
    // 新建一个 GTData 的文件夹
    [fileManager createDirectoryAtPath:dataPath withIntermediateDirectories:YES attributes:nil error:&creatError];
    
    // 新建一个 文件地址
    NSString *listDataPath = [dataPath stringByAppendingPathComponent:@"list"];
    // [fileManager createFileAtPath:listDataPath contents:nil attributes:nil];
    // 写入数据
    NSData *listData = [@"abc" dataUsingEncoding:NSUTF8StringEncoding];
    // 给文件写入数据
    [fileManager createFileAtPath:listDataPath contents:listData attributes:nil];
    
    
    // 查询文件
    BOOL fileExit = [fileManager fileExistsAtPath:listDataPath];
    
    
    // 删除
//    if(fileExit){
//        [fileManager removeItemAtPath:listDataPath error:nil];
//    }
    
    NSLog(@"");
    
    // 创建 handler 追加文件内容操作
    NSFileHandle *fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:listDataPath];
    
    [fileHandler seekToEndOfFile];
    // 追加 def => abcdef
    [fileHandler writeData:[@"def" dataUsingEncoding:NSUTF8StringEncoding]];
    
    [fileHandler synchronizeFile];
    
    [fileHandler closeFile];
    
}