一直想总结一下关于iOS的离线数据缓存的方面的问题,然后近期也简单的对AFN进行了再次封装。全部想把这两个结合起来写一下。数据展示型的页面做离线缓存能够有更好的用户体验,用户在离线环境下仍然能够获取一些数据。这里的数据缓存首选肯定是SQLite,轻量级。对数据的存储读取相对于其它几种方式有优势,这里对AFN的封装没有涉及太多业务逻辑层面的需求。主要还是对一些方法再次封装方便使用。解除项目对第三方的耦合性。能够简单的高速的更换底层使用的网络请求代码。这篇主要写离线缓存思路。对AFN的封装仅仅做简单的介绍。

关于XLNetworkApi

XLNetworkApi的一些功能和说明:

  • 使用XLNetworkRequest做一些GET、POST、PUT、DELETE请求,与业务逻辑对接部分直接以数组或者字典的形式返回。
  • 以及网络下载、上传文件。以block的形式返回实时的下载、上传进度,上传文件參数通过模型XLFileConfig去存取。
  • 通过继承于XLDataService来将一些数据处理,模型转化封装起来。于业务逻辑对接返回的是相应的模型,降低Controllor处理数据处理逻辑的压力。

  • 自己定义一些回调的block

    /**
    请求成功block
    */
    typedef void (^requestSuccessBlock)(id responseObj);
    /**
    请求失败block
    */
    typedef void (^requestFailureBlock) (NSError *error);
    /**
    请求响应block
    */
    typedef void (^responseBlock)(id dataObj, NSError *error);
    /**
    监听进度响应block
    */
    typedef void (^progressBlock)(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
  • XLNetworkRequest.m部分实现

    #import "XLNetworkRequest.h"
    #import "AFNetworking.h"
    @implementation XLNetworkRequest
    + (void)getRequest:(NSString *)url params:(NSDictionary *)params success:(requestSuccessBlock)successHandler failure:(requestFailureBlock)failureHandler { AFHTTPRequestOperationManager *manager = [self getRequstManager]; [manager GET:url parameters:params success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
    successHandler(responseObject);
    } failure:^(AFHTTPRequestOperation * _Nullable operation, NSError * _Nonnull error) {
    XLLog(@"------请求失败-------%@",error);
    failureHandler(error);
    }];
    }
  • 下载部分代码

    //下载文件,监听下载进度
    + (void)downloadRequest:(NSString *)url successAndProgress:(progressBlock)progressHandler complete:(responseBlock)completionHandler { NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:sessionConfiguration]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    NSProgress *kProgress = nil; NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:&kProgress destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { NSURL *documentUrl = [[NSFileManager defaultManager] URLForDirectory :NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; return [documentUrl URLByAppendingPathComponent:[response suggestedFilename]]; } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nonnull filePath, NSError * _Nonnull error){
    if (error) {
    XLLog(@"------下载失败-------%@",error);
    }
    completionHandler(response, error);
    }]; [manager setDownloadTaskDidWriteDataBlock:^(NSURLSession * _Nonnull session, NSURLSessionDownloadTask * _Nonnull downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { progressHandler(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); }];
    [downloadTask resume];
    }
  • 上传部分代码

    //上传文件,监听上传进度
    + (void)updateRequest:(NSString *)url params:(NSDictionary *)params fileConfig:(XLFileConfig *)fileConfig successAndProgress:(progressBlock)progressHandler complete:(responseBlock)completionHandler { NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:url parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { [formData appendPartWithFileData:fileConfig.fileData name:fileConfig.name fileName:fileConfig.fileName mimeType:fileConfig.mimeType]; } error:nil]; //获取上传进度
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; [operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { progressHandler(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); }]; [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
    completionHandler(responseObject, nil);
    } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) { completionHandler(nil, error);
    if (error) {
    XLLog(@"------上传失败-------%@",error);
    }
    }]; [operation start];
    }
  • XLDataService.m部分实现

    + (void)getWithUrl:(NSString *)url param:(id)param modelClass:(Class)modelClass responseBlock:(responseBlock)responseDataBlock {
    [XLNetworkRequest getRequest:url params:param success:^(id responseObj) {
    //数组、字典转化为模型数组 dataObj = [self modelTransformationWithResponseObj:responseObj modelClass:modelClass];
    responseDataBlock(dataObj, nil); } failure:^(NSError *error) {
    responseDataBlock(nil, error);
    }];
    }
  • (关键)以下这种方法提供给继承XLDataService的子类重写,将转化为模型的代码写在这里,相似业务的网络数据请求都能够用这个子类去请求数据,直接返回相应的模型数组。
    /**
    数组、字典转化为模型
    */
    + (id)modelTransformationWithResponseObj:(id)responseObj modelClass:(Class)modelClass {
    return nil;
    }
    关于离线数据缓存

    当用户进入程序的展示页面,有三个情况下可能涉及到数据库存取操作,简单画了个图来理解,思路比較简单。主要是一些存取的细节处理。

  • 进入展示页面

    进入页面.png
  • 下拉刷新最新数据

    下拉刷新.png
  • 上拉载入很多其它数据

    上拉载入很多其它.png
  • 须要注意的是。上拉载入很多其它的时候。每次从数据库返回一定数量的数据,而不是一次性将数据所有载入,否则会有内存问题。直到数据库中没有很多其它数据时再发生网络请求,再次将新数据存入数据库。这里存储数据的方式是将server返回每组数据的字典归档成二进制作为数据库字段直接存储,这样存储在模型属性比較多的情况下更有优势,避免每个属性作为一个字段,另外添加了一个idStr字段用来推断数据的唯一性,避免反复存储。

    首先定义一个工具类XLDataBase来做数据库相关的操作,这里用的是第三方的FMDB。
#import "XLDataBase.h"
#import "FMDatabase.h"
#import "Item.h"
#import "MJExtension.h" @implementation XLDataBase static FMDatabase *_db; + (void)initialize { NSString *path = [NSString stringWithFormat:@"%@/Library/Caches/Data.db",NSHomeDirectory()];
_db = [FMDatabase databaseWithPath:path];
[_db open];
[_db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_item (id integer PRIMARY KEY, itemDict blob NOT NULL, idStr text NOT NULL)"];
} //存入数据库
+ (void)saveItemDict:(NSDictionary *)itemDict {
//此处把字典归档成二进制数据直接存入数据库。避免加入过多的数据库字段
NSData *dictData = [NSKeyedArchiver archivedDataWithRootObject:itemDict]; [_db executeUpdateWithFormat:@"INSERT INTO t_item (itemDict, idStr) VALUES (%@, %@)",dictData, itemDict[@"id"]];
} //返回所有数据
+ (NSArray *)list { FMResultSet *set = [_db executeQuery:@"SELECT * FROM t_item"];
NSMutableArray *list = [NSMutableArray array]; while (set.next) {
// 获得当前所指向的数据 NSData *dictData = [set objectForColumnName:@"itemDict"];
NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithData:dictData];
[list addObject:[Item mj_objectWithKeyValues:dict]];
}
return list;
} //取出某个范围内的数据
+ (NSArray *)listWithRange:(NSRange)range { NSString *SQL = [NSString stringWithFormat:@"SELECT * FROM t_item LIMIT %lu, %lu",range.location, range.length];
FMResultSet *set = [_db executeQuery:SQL];
NSMutableArray *list = [NSMutableArray array]; while (set.next) {
NSData *dictData = [set objectForColumnName:@"itemDict"];
NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithData:dictData];
[list addObject:[Item mj_objectWithKeyValues:dict]];
}
return list;
} //通过一组数据的唯一标识推断数据是否存在
+ (BOOL)isExistWithId:(NSString *)idStr
{
BOOL isExist = NO; FMResultSet *resultSet= [_db executeQuery:@"SELECT * FROM t_item where idStr = ?",idStr];
while ([resultSet next]) {
if([resultSet stringForColumn:@"idStr"]) {
isExist = YES;
}else{
isExist = NO;
}
}
return isExist;
}
@end
  • 一些继承于XLDataService的子类的数据库存储和模型转换的逻辑代码
#import "GetTableViewData.h"
#import "XLDataBase.h" @implementation GetTableViewData //重写父类方法
+ (id)modelTransformationWithResponseObj:(id)responseObj modelClass:(Class)modelClass {
NSArray *lists = responseObj[@"data"][@"list"];
NSMutableArray *array = [NSMutableArray array];
for (NSDictionary *dict in lists) {
[modelClass mj_setupReplacedKeyFromPropertyName:^NSDictionary *{
return @{ @"ID" : @"id" };
}];
[array addObject:[modelClass mj_objectWithKeyValues:dict]]; //通过idStr先推断数据是否存储过,假设没有。网络请求新数据存入数据库
if (![XLDataBase isExistWithId:dict[@"id"]]) {
//存数据库
NSLog(@"存入数据库");
[XLDataBase saveItemDict:dict];
}
}
return array;
}
  • 以下是一些控制器的代码实现:
#import "ViewController.h"
#import "GetTableViewData.h"
#import "Item.h"
#import "XLDataBase.h"
#import "ItemCell.h"
#import "MJRefresh.h"
#define URL_TABLEVIEW @"https://api.108tian.com/mobile/v3/EventList?cityId=1&step=10&theme=0&page=%lu" @interface ViewController () <UITableViewDataSource, UITableViewDelegate>
{
NSMutableArray *_dataArray;
UITableView *_tableView;
NSInteger _currentPage;//当前数据相应的page
}
@end @implementation ViewController
#pragma mark Life cycle
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self createTableView];
_dataArray = [NSMutableArray array];
} - (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSRange range = NSMakeRange(0, 10);
//假设数据库有数据则读取,不发送网络请求
if ([[XLDataBase listWithRange:range] count]) {
[_dataArray addObjectsFromArray:[XLDataBase listWithRange:range]];
NSLog(@"从数据库载入");
}else{
[self getTableViewDataWithPage:0];
}
} #pragma mark UI
- (void)createTableView {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.rowHeight = 100.0;
[self.view addSubview:_tableView]; _tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
[self loadNewData];
}];
_tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
[self loadMoreData];
}];
} #pragma mark GetDataSoure
- (void)getTableViewDataWithPage:(NSInteger)page {
NSLog(@"发送网络请求!");
NSString *url = [NSString stringWithFormat:URL_TABLEVIEW, page];
[GetTableViewData getWithUrl:url param:nil modelClass:[Item class] responseBlock:^(id dataObj, NSError *error) {
[_dataArray addObjectsFromArray:dataObj];
[_tableView reloadData];
[_tableView.mj_header endRefreshing];
[_tableView.mj_footer endRefreshing];
}];
} - (void)loadNewData {
NSLog(@"下拉刷新");
_currentPage = 0;
[_dataArray removeAllObjects];
[self getTableViewDataWithPage:_currentPage];
} - (void)loadMoreData {
NSLog(@"上拉载入");
_currentPage ++;
NSRange range = NSMakeRange(_currentPage * 10, 10);
if ([[XLDataBase listWithRange:range] count]) {
[_dataArray addObjectsFromArray:[XLDataBase listWithRange:range]];
[_tableView reloadData];
[_tableView.mj_footer endRefreshing];
NSLog(@"数据库载入%lu条很多其它数据",[[XLDataBase listWithRange:range] count]);
}else{
//数据库没很多其它数据时再网络请求
[self getTableViewDataWithPage:_currentPage];
}
} #pragma mark UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _dataArray.count;
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ItemCell *cell = [ItemCell itemCellWithTableView:tableView];
cell.item = _dataArray[indexPath.row];
return cell;
}
@end

最后附上代码的下载地址。重要的部分代码中都有对应的凝视和文字打印,执行程序能够非常直观的表现。

https://github.com/ShelinShelin/OffLineCache.git

有考虑不周的地方,希望大家能提出一些意见,非常乐意与大家互相交流。

iOS数据库离线缓存思路和网络层封装的更多相关文章

  1. iOS开发——离线缓存

    先搭好架子,有时间了再填充.

  2. (一一六)新浪微博client的离线缓存实现思路

    上一节(一一五)利用NSKeyedArchiver实现随意对象转为二进制介绍了将随意对象转化为二进制数据和还原的方法.可用于实现本节介绍的微博数据离线缓存. 通过新浪官方的API能够发现,返回的微博数 ...

  3. iOS超全开源框架、项目和学习资料汇总--数据库、缓存处理、图像浏览、摄像照相视频音频篇

    iOS超全开源框架.项目和学习资料汇总--数据库.缓存处理.图像浏览.摄像照相视频音频篇 感谢:Ming_en_long 的分享 大神超赞的集合,http://www.jianshu.com/p/f3 ...

  4. iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓存团购数据,浏览记录与收藏记录的批量删除等)

    大致花了一个月时间,利用各种空闲时间,将这个客户端实现了,在这里主要是想记录下,设计的大体思路以及实现过程中遇到的坑...... 这个项目的github地址:https://github.com/wz ...

  5. Java数据库缓存思路

    为什么要用缓存?如果问这个问题说明你还是新手,数据库吞吐量毕竟有限,每秒读写5000次了不起了,如果不用缓存,假设一个页面有100个数据库操作,50个用户并发数据库就歇菜,这样最多能支撑的pv也就50 ...

  6. IOS开发笔记(4)数据离线缓存与读取

    IOS开发笔记(4)数据离线缓存与读取 分类: IOS学习2012-12-06 16:30 7082人阅读 评论(0) 收藏 举报 iosiOSIOS 方法一:一般将服务器第一次返回的数据保存在沙盒里 ...

  7. react native之封装离线缓存框架

    请求数据=>本地有无缓存+缓存数据是否过期 =>可用 =>不可用 将代码封装成一个DataStore.js文件, 这里面主要提供:从本地获取数据,从网络获取数据,创建本地时间戳,请求 ...

  8. 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制

    [原创]分布式之数据库和缓存双写一致性方案解析(三)   正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...

  9. HTML5 离线缓存管理库

    一.HTML5离线缓存技术 支持离线缓存是HTML5中的一个重点,离线缓存就是让用户即使在断网的情况下依然可以正常的运行应用.传统的本地存储数据的方式有 localstorage,sessionsto ...

随机推荐

  1. Definitaion of 'utsname' must be imported from module 'Darwin.POSIX.sys.utsname' before it is required

    https://stackoverflow.com/questions/34430354/objective-c-gettimeofday-must-be-imported

  2. MySQL中 PK NN UQ BIN UN ZF AI 的意思

    PK   Belongs to primary key作为主键 NN   Not Null非空 UQ  Unique index不能重复 BIN  Is binary column存放二进制数据的列 ...

  3. LeetCode 225 Implement Stack using Queues(用队列来实现栈)(*)

    翻译 用队列来实现栈的例如以下操作. push(x) -- 将元素x加入进栈 pop() -- 从栈顶移除元素 top() -- 返回栈顶元素 empty() -- 返回栈是否为空 注意: 你必须使用 ...

  4. XPages访问关系型数据库技术与最佳实践

    XPage 对于 Domino 开发人员的一大好处就是能够很方便和高效的访问关系型数据库.本文通过实例代码展现了在 XPage 中访问关系型数据库的具体步骤 , 同时讲解了一些在 XPage 中高效访 ...

  5. Spring Cloud Edgware Release Notes

    Spring Cloud Edgware builds on Spring Boot 1.5.x. Renamed starters A number of starters did not foll ...

  6. springboot自定义jdbc操作库+基于注解切点AOP

    发布时间:2018-11-08   技术:springboot+aop   概述 springBoot集成了自定义的jdbc操作类及AOP,因为spring自带的JdbcTemplate在实际项目中并 ...

  7. windows用户态程序的Dump

    熟悉Linux的开发人员都知道,在Linux下开发程序,如果程序崩溃了,可以通过配置Core Dump,来让程序崩溃的瞬间产生一个Dump文件,然后通过dump文件来调试程序为什么崩溃.但是windo ...

  8. JavaMail 接收邮件及删除

    解析读取收件箱中邮件: import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io. ...

  9. WinForm中 事件 委托 多线程的应用【以一个下载进度条为例】

    第一步:首先我们创建一个winfor的项目 第二步:我们建一个窗体 在一个窗体里面 打开一个另外的窗体 另外的窗体有一个按钮 点击后就开始下载 下载完成后 在注册窗体上面 显示下载完成(达到在一个窗体 ...

  10. 如何关闭Golang中的HTTP连接 How to Close Golang's HTTP connection

    我们的一个服务是用Go写的,在测试的时候发现几个小时之后它就会core掉,而且core的时候没有打出任何堆栈信息,简单分析后发现该服务中的几个HTTP服务的连接数不断增长,而我们的开发机的fd lim ...