一直想总结一下关于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. Knockout学习之监控数组

    监控数组 单个监控属性.组合属性虽然可以解决大部分的问题,但是还有很多是他们无法做到的,比如在一组数据中进行移除添加,所以这节我们将要学习监控数组. 由于监控属性是由ko的observable构造,那 ...

  2. V-rep学习笔记:机器人逆运动学解算

    IK groups and IK elements VREP中使用IK groups和IK elements来进行正/逆运动学计算,一个IK group可以包含一个或者多个IK elements: I ...

  3. 2、redis原生的命令操作不同数据类型

    一.常用数据类型简介: redis常用五种数据类型:string,hash,list,set,zset(sorted set). 1.String类型 String是最简单的类型,一个key对应一个v ...

  4. python3新特点

    #xiaodeng #python 3 #1.编码方式和性能 ''' 1.py3运行效率更快 2.默认源文件编码ASCII变为UTF-8,以前文件前加入的coding=utf-8不再需要 3.针对un ...

  5. 使用MSTSC远程登录时提示证书无效的解决方法

    On your local machine Open Windows command prompt type: gpedit.msc -> Press Enter -> a new win ...

  6. [解决思路]ORA-01078: failure in processing system parameters LRM-00109: could not open parameter file

    oracle数据库,服务器异常断电,导致数据库不能启动.... 错误提示: SQL> startup ORA-01078: failure in processing system parame ...

  7. 转:ArcEngine10.0+VS2010+MFC 扫盲贴 .

    项目用vc6.0+MapObject写的,现在却要升级成AE10,研究了好多天的AE10.0和c++,中间的曲折也不想多说了,废话少数,下面是实现ArcEngine基本功能的,仅供参考,如能帮到同仁们 ...

  8. 【linux】系统初始化的shell脚本

    根据参考网上的一些文章,总结出来一个系统初始化的shell脚本 1.初始化脚本 #!/bin/bash cat << EOF +------------------------------ ...

  9. oracle收购Mysql后,Mysql的安装配置方法

    自从Oracle收购MySQL后,略微发生了一些小小的变化,原来mysql安装完成后默认是没有密码的,但是新版的mysql安装完成后oracle提供了一个free password放着/root/.m ...

  10. Redis学习之路(001)- Redis介绍以及安装(Linux)

    redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcached类似,但很大程度补偿了memcached的不足,它支持存储的value类型相对更多,包括strin ...