一、网络
APP 内网络基本流程
- NSURL 系统将字符串进行封装
- NSURLRequest 在NSURL基础上 进一步封装成url参数
- NSURLSession 调用系统响应函数进行网络请求发送 一个Session 可以创建多个请求,封装Request为Task,控制状态、开始取消、进度,一个App可以设置多个 Session
资源请求的两种方式
-
网络资源路径
- URLWithString
-
本地文件的路径
- fileURLWithPath 会自动在前面增加 file://
1-1、创建一个简单的网络请求
1、创建一个listloader,进行数据请求
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 需要添加 以下信息
系统 NSURLSession 加载数据流程
- 1、创建 & 使用默认 Session
- 2、通过地址和参数创建 Task
- 3、 开始 & 取消 Task
- 4、Handler 中处理数据
1-3、使用开源的网络框架
为什么使用开源网络框架? 解决上文中需要大量设置,需要编码、压缩,session 回调方式也不友好,还要自己设置 resume 等 delegate 等回调,解决代码书写大量并且不优雅的问题。
使用 cocoapods 来集成和管理第三方库
扩展
依赖管理第三方资源
创建一个 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 文件
3、下载
pod install
pod update
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 格式解析
NSError *jsonError;
id jsonObj = [NSJSONSerialization JSONObjectWithData:data options:0 error: &jsonError];
可见流格式已经被解析成了字典
1-5、结构化数据
解析后的是一个字典对象
- 字典和数组 - 语意不明 & 使用不便
- 使用结构化的数据 表示特定的对象
- 通过头文件语意明确 & 处理简单的逻辑
- JSON --> @[Model,Model...]
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
完整列表加载流程
- 通过网络接口加载数据
- 使用 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]];
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];
}
二、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
- 提供移动、复制、删除等操作的具体自定义实现
2-3、NSFileHandle
- 读取文件 & 写文件
- 读取指定的长度 & 在指定位置追加/截断
- 截断 & 立即刷新
- 常用于追加数据
-(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];
}