iOS数据库离线缓存思路和网络层封装

2024-06-11 14:38

本文主要是介绍iOS数据库离线缓存思路和网络层封装,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一直想总结一下关于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 {
    //网络不可用if (![self checkNetworkStatus]) {successHandler(nil);failureHandler(nil);return;}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 {if (![self checkNetworkStatus]) {progressHandler(0, 0, 0);completionHandler(nil, nil);return;}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 {if (![self checkNetworkStatus]) {progressHandler(0, 0, 0);completionHandler(nil, nil);return;}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
  • 需要注意的是,上拉加载更多的时候,每次从数据库返回一定数量的数据,而不是一次性将数据全部加载,否则会有内存问题,直到数据库中没有更多数据时再发生网络请求,再次将新数据存入数据库。这里存储数据的方式是将服务器返回每组数据的字典归档成二进制作为数据库字段直接存储,这样存储在模型属性比较多的情况下更有优势,避免每一个属性作为一个字段,另外增加了一个idStr字段用来判断数据的唯一性,避免重复存储。
    首先定义一个工具类XLDataBase来做数据库相关的操作,这里用的是第三方的FMDB。
#import "XLDataBase.h"
#import "FMDatabase.h"
#import "Item.h"
#import "MJExtension.h"@implementation XLDataBasestatic 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数据库离线缓存思路和网络层封装的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1051450

相关文章

数据库面试必备之MySQL中的乐观锁与悲观锁

《数据库面试必备之MySQL中的乐观锁与悲观锁》:本文主要介绍数据库面试必备之MySQL中乐观锁与悲观锁的相关资料,乐观锁适用于读多写少的场景,通过版本号检查避免冲突,而悲观锁适用于写多读少且对数... 目录一、引言二、乐观锁(一)原理(二)应用场景(三)示例代码三、悲观锁(一)原理(二)应用场景(三)示例

Node.js 数据库 CRUD 项目示例详解(完美解决方案)

《Node.js数据库CRUD项目示例详解(完美解决方案)》:本文主要介绍Node.js数据库CRUD项目示例详解(完美解决方案),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考... 目录项目结构1. 初始化项目2. 配置数据库连接 (config/db.js)3. 创建模型 (models/

鸿蒙中Axios数据请求的封装和配置方法

《鸿蒙中Axios数据请求的封装和配置方法》:本文主要介绍鸿蒙中Axios数据请求的封装和配置方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.配置权限 应用级权限和系统级权限2.配置网络请求的代码3.下载在Entry中 下载AxIOS4.封装Htt

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

Ubuntu中远程连接Mysql数据库的详细图文教程

《Ubuntu中远程连接Mysql数据库的详细图文教程》Ubuntu是一个以桌面应用为主的Linux发行版操作系统,这篇文章主要为大家详细介绍了Ubuntu中远程连接Mysql数据库的详细图文教程,有... 目录1、版本2、检查有没有mysql2.1 查询是否安装了Mysql包2.2 查看Mysql版本2.

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数

Win11安装PostgreSQL数据库的两种方式详细步骤

《Win11安装PostgreSQL数据库的两种方式详细步骤》PostgreSQL是备受业界青睐的关系型数据库,尤其是在地理空间和移动领域,:本文主要介绍Win11安装PostgreSQL数据库的... 目录一、exe文件安装 (推荐)下载安装包1. 选择操作系统2. 跳转到EDB(PostgreSQL 的

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处