php mysql sqlite缓存_使用SQLite缓存数据分析与实现 | 六阿哥博客
前言:如果你准备看这篇文章,并且在此之前并没有使用过SQLite,建议先看
封装思想
在讲解SQLite之前,先聊聊封装思想。。写代码的都知道,面向对象三大思想(有些说四大,这个不是重点)之一的封装性,这里就举一个栗子来说明一下什么是封装。
比如控制器需要数据,控制器就跟模型说,我要数据,请给我数据。对于数据是怎么来的,控制器并不关心,控制器只关心模型有没有给他数据。
然后模型呢?控制器找模型要数据,模型也没有啊,所以模型去找数据访问层要数据,数据访问层的数据是怎么来的,模型也不关心,他关心的也只是有没有给他数据。
然后数据访问层呢?数据访问层自己也是没有数据的,他也得去找他的上一级要数据啊。
找谁要呢?这里就要分两种情况,第一种是本地有数据,他将本地数据直接返回给模型,模型再返回给我。第二种情况是本地没有数据,他就去网络请求类要,最终网络请求类才和后端交互,去后端请求数据,并将数据请求回来后保存到本地缓存,并返回给数据访问层,数据访问层再返回给模型,模型返回给控制器。
所以,这样就形成了一个数据请求和数据响应的链条:

上面栗子中提到了数据访问层(Data Access Layer),可能有些朋友对这个词比较陌生,你就当做是模型和数据之间的桥梁就行了。
需求分析
这里我以 六阿哥客户端 的(首页列表数据)为例,来一步步实现数据缓存。

需求:列表数据如果本地有缓存,就直接加载本地数据,如果没有才去请求网络。并且在下拉刷新的时候清除列表的缓存数据,并把新请求回来的数据缓存到本地。
这里涉及到数据处理的类有,网络工具类、数据访问层类、列表模型类、控制器。当然我们这里并不是直接使用SQLite类库来缓存数据,而是通过FMDB(对SQLite进行封装的第三方类库),你也可以通过系统的CoreData来缓存数据。不过,我个人更喜欢FMDB,直接通过SQL语言管理数据。
代码实现
在进行数据缓存前,我们最好先对FMDB进行一次封装,让我们用起来更方便。这里我创建一个工具类JFSQLiteManager,直接贴代码,如果不熟悉FMDB的,建议先看
import UIKit
import FMDB
let NEWS_LIST_HOME_LIST = "jf_newslist_homelist" // 首页 列表页 的 列表 数据表
class JFSQLiteManager: NSObject {
/// FMDB单例
static let shareManager = JFSQLiteManager()
/// sqlite数据库名
private let newsDBName = "news.db"
let dbQueue: FMDatabaseQueue
override init() {
let documentPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last!
let dbPath = "\(documentPath)/\(newsDBName)"
print(dbPath)
// 根据路径创建并打开数据库,开启一个串行队列
dbQueue = FMDatabaseQueue(path: dbPath)
super.init()
// 创建数据表
createNewsListTable(NEWS_LIST_HOME_LIST)
}
private func createNewsListTable(tbname: String) {
let sql = "CREATE TABLE IF NOT EXISTS \(tbname) ( \n" +
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n" +
"classid INTEGER, \n" +
"news TEXT, \n" +
"createTime VARCHAR(30) DEFAULT (datetime('now', 'localtime')) \n" +
");"
dbQueue.inDatabase { (db) in
if db.executeStatements(sql) {
print("创建 \(tbname) 表成功")
} else {
print("创建 \(tbname) 表失败")
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
importUIKit
importFMDB
letNEWS_LIST_HOME_LIST="jf_newslist_homelist"// 首页 列表页 的 列表 数据表
classJFSQLiteManager:NSObject{
/// FMDB单例
staticletshareManager=JFSQLiteManager()
/// sqlite数据库名
privateletnewsDBName="news.db"
letdbQueue:FMDatabaseQueue
overrideinit(){
letdocumentPath=NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.UserDomainMask,true).last!
letdbPath="\(documentPath)/\(newsDBName)"
print(dbPath)
// 根据路径创建并打开数据库,开启一个串行队列
dbQueue=FMDatabaseQueue(path:dbPath)
super.init()
// 创建数据表
createNewsListTable(NEWS_LIST_HOME_LIST)
}
privatefunccreateNewsListTable(tbname:String){
letsql="CREATE TABLE IF NOT EXISTS \(tbname) ( \n"+
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n"+
"classid INTEGER, \n"+
"news TEXT, \n"+
"createTime VARCHAR(30) DEFAULT (datetime('now', 'localtime')) \n"+
");"
dbQueue.inDatabase{(db)in
ifdb.executeStatements(sql){
print("创建 \(tbname) 表成功")
}else{
print("创建 \(tbname) 表失败")
}
}
}
}
控制器向模型请求数据:
private func loadNews(classid: Int, pageIndex: Int, method: Int) {
JFArticleListModel.loadNewsList(classid, pageIndex: pageIndex, type: 1) { (articleListModels, error) in
self.tableView.mj_header.endRefreshing()
self.tableView.mj_footer.endRefreshing()
guard let list = articleListModels where error != true else {
return
}
// id越大,文章越新
let maxId = self.articleList.first?.id ?? "0"
let minId = self.articleList.last?.id ?? "0"
if method == 0 {
// 0下拉加载最新 - 会直接覆盖数据,用最新的10条数据
if Int(maxId) < Int(list[0].id!) {
self.articleList = list
}
} else {
// 1上拉加载更多 - 拼接数据
if Int(minId) > Int(list[0].id!) {
self.articleList = self.articleList + list
} else {
self.tableView.mj_footer.endRefreshingWithNoMoreData()
}
}
self.tableView.reloadData()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
privatefuncloadNews(classid:Int,pageIndex:Int,method:Int){
JFArticleListModel.loadNewsList(classid,pageIndex:pageIndex,type:1){(articleListModels,error)in
self.tableView.mj_header.endRefreshing()
self.tableView.mj_footer.endRefreshing()
guardletlist=articleListModelswhereerror!=trueelse{
return
}
// id越大,文章越新
letmaxId=self.articleList.first?.id??"0"
letminId=self.articleList.last?.id??"0"
ifmethod==0{
// 0下拉加载最新 - 会直接覆盖数据,用最新的10条数据
ifInt(maxId)
self.articleList=list
}
}else{
// 1上拉加载更多 - 拼接数据
ifInt(minId)>Int(list[0].id!){
self.articleList=self.articleList+list
}else{
self.tableView.mj_footer.endRefreshingWithNoMoreData()
}
}
self.tableView.reloadData()
}
}
模型向数据访问层请求数据:
class func loadNewsList(classid: Int, pageIndex: Int, type: Int, finished: (articleListModels: [JFArticleListModel]?, error: NSError?) -> ()) {
// 模型找数据访问层请求数据 - 然后处理数据回调给调用者直接使用
JFNewsDALManager.shareManager.loadNewsList(classid, pageIndex: pageIndex, type: type) { (result, error) in
// 请求失败
if error != nil || result == nil {
finished(articleListModels: nil, error: error)
return
}
// 没有数据了
if result?.count == 0 {
finished(articleListModels: [JFArticleListModel](), error: nil)
return
}
let data = result!.arrayValue
var articleListModels = [JFArticleListModel]()
// 遍历转模型添加数据
for article in data {
let postModel = JFArticleListModel(dict: article.dictionaryObject!)
articleListModels.append(postModel)
}
finished(articleListModels: articleListModels, error: nil)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
classfuncloadNewsList(classid:Int,pageIndex:Int,type:Int,finished:(articleListModels:[JFArticleListModel]?,error:NSError?)->()){
// 模型找数据访问层请求数据 - 然后处理数据回调给调用者直接使用
JFNewsDALManager.shareManager.loadNewsList(classid,pageIndex:pageIndex,type:type){(result,error)in
// 请求失败
iferror!=nil||result==nil{
finished(articleListModels:nil,error:error)
return
}
// 没有数据了
ifresult?.count==0{
finished(articleListModels:[JFArticleListModel](),error:nil)
return
}
letdata=result!.arrayValue
vararticleListModels=[JFArticleListModel]()
// 遍历转模型添加数据
forarticleindata{
letpostModel=JFArticleListModel(dict:article.dictionaryObject!)
articleListModels.append(postModel)
}
finished(articleListModels:articleListModels,error:nil)
}
}
数据访问层判断是去本地还是网络请求数据:
func loadNewsList(classid: Int, pageIndex: Int, type: Int, finished: (result: JSON?, error: NSError?) -> ()) {
// 先从本地加载数据
loadNewsListFromLocation(classid, pageIndex: pageIndex, type: type) { (success, result, error) in
// 本地有数据直接返回
if success == true {
finished(result: result, error: nil)
print("加载了本地数据 \(result)")
return
}
// 本地没有数据才从网络中加载
JFNetworkTool.shareNetworkTool.loadNewsListFromNetwork(classid, pageIndex: pageIndex, type: type) { (success, result, error) in
if success == false || error != nil || result == nil {
finished(result: nil, error: error)
return
}
// 缓存数据到本地
self.saveNewsListData(classid, data: result!, type: type)
finished(result: result, error: nil)
print("加载了远程数据 \(result)")
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
funcloadNewsList(classid:Int,pageIndex:Int,type:Int,finished:(result:JSON?,error:NSError?)->()){
// 先从本地加载数据
loadNewsListFromLocation(classid,pageIndex:pageIndex,type:type){(success,result,error)in
// 本地有数据直接返回
ifsuccess==true{
finished(result:result,error:nil)
print("加载了本地数据 \(result)")
return
}
// 本地没有数据才从网络中加载
JFNetworkTool.shareNetworkTool.loadNewsListFromNetwork(classid,pageIndex:pageIndex,type:type){(success,result,error)in
ifsuccess==false||error!=nil||result==nil{
finished(result:nil,error:error)
return
}
// 缓存数据到本地
self.saveNewsListData(classid,data:result!,type:type)
finished(result:result,error:nil)
print("加载了远程数据 \(result)")
}
}
}
本地请求数据方法:
private func loadNewsListFromLocation(classid: Int, pageIndex: Int, type: Int, finished: NetworkFinished) {
var sql = ""
// 计算分页
let pre_count = (pageIndex - 1) * 20
let oneCount = 20
if classid == 0 {
sql = "SELECT * FROM \(NEWS_LIST_HOME_LIST) ORDER BY id ASC LIMIT \(pre_count), \(oneCount)"
}
JFSQLiteManager.shareManager.dbQueue.inDatabase { (db) in
var array = [JSON]()
let result = try! db.executeQuery(sql, values: nil)
while result.next() {
let newsJson = result.stringForColumn("news")
let json = JSON.parse(newsJson)
array.append(json)
}
if array.count > 0 {
finished(success: true, result: JSON(array), error: nil)
} else {
finished(success: false, result: nil, error: nil)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
privatefuncloadNewsListFromLocation(classid:Int,pageIndex:Int,type:Int,finished:NetworkFinished){
varsql=""
// 计算分页
letpre_count=(pageIndex-1)*20
letoneCount=20
ifclassid==0{
sql="SELECT * FROM \(NEWS_LIST_HOME_LIST) ORDER BY id ASC LIMIT \(pre_count), \(oneCount)"
}
JFSQLiteManager.shareManager.dbQueue.inDatabase{(db)in
vararray=[JSON]()
letresult=try!db.executeQuery(sql,values:nil)
whileresult.next(){
letnewsJson=result.stringForColumn("news")
letjson=JSON.parse(newsJson)
array.append(json)
}
ifarray.count>0{
finished(success:true,result:JSON(array),error:nil)
}else{
finished(success:false,result:nil,error:nil)
}
}
}
远程请求数据的方法:
func loadNewsListFromNetwork(classid: Int, pageIndex: Int, type: Int, finished: NetworkFinished) {
var parameters = [String : AnyObject]()
if type == 1 {
parameters = [
"classid" : classid,
"pageIndex" : pageIndex, // 页码
"pageSize" : 20 // 单页数量
]
} else {
parameters = [
"classid" : classid,
"query" : "isgood",
"pageSize" : 3
]
}
JFNetworkTool.shareNetworkTool.get(ARTICLE_LIST, parameters: parameters) { (success, result, error) -> () in
guard let successResult = result where success == true else {
finished(success: false, result: nil, error: error)
return
}
finished(success: true, result: successResult["data"], error: nil)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
funcloadNewsListFromNetwork(classid:Int,pageIndex:Int,type:Int,finished:NetworkFinished){
varparameters=[String:AnyObject]()
iftype==1{
parameters=[
"classid":classid,
"pageIndex":pageIndex,// 页码
"pageSize":20// 单页数量
]
}else{
parameters=[
"classid":classid,
"query":"isgood",
"pageSize":3
]
}
JFNetworkTool.shareNetworkTool.get(ARTICLE_LIST,parameters:parameters){(success,result,error)->()in
guardletsuccessResult=resultwheresuccess==trueelse{
finished(success:false,result:nil,error:error)
return
}
finished(success:true,result:successResult["data"],error:nil)
}
}
本地缓存数据的方法:
private func saveNewsListData(saveClassid: Int, data: JSON, type: Int) {
var sql = ""
if saveClassid == 0 {
sql = "INSERT INTO \(NEWS_LIST_HOME_LIST) (classid, news) VALUES (?, ?)"
}
JFSQLiteManager.shareManager.dbQueue.inTransaction { (db, rollback) in
guard let array = data.arrayObject as! [[String : AnyObject]]? else {
return
}
// 每一个字典是一条资讯
for dict in array {
// 资讯分类id
let classid = Int(dict["classid"] as! String)!
// 单条资讯json数据
let newsData = try! NSJSONSerialization.dataWithJSONObject(dict, options: NSJSONWritingOptions(rawValue: 0))
let newsJson = String(data: newsData, encoding: NSUTF8StringEncoding)!
if db.executeUpdate(sql, withArgumentsInArray: [classid, newsJson]) {
print("缓存数据成功 - \(classid)")
} else {
print("缓存数据失败 - \(classid)")
rollback.memory = true
break
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
privatefuncsaveNewsListData(saveClassid:Int,data:JSON,type:Int){
varsql=""
ifsaveClassid==0{
sql="INSERT INTO \(NEWS_LIST_HOME_LIST) (classid, news) VALUES (?, ?)"
}
JFSQLiteManager.shareManager.dbQueue.inTransaction{(db,rollback)in
guardletarray=data.arrayObjectas![[String:AnyObject]]?else{
return
}
// 每一个字典是一条资讯
fordictinarray{
// 资讯分类id
letclassid=Int(dict["classid"]as!String)!
// 单条资讯json数据
letnewsData=try!NSJSONSerialization.dataWithJSONObject(dict,options:NSJSONWritingOptions(rawValue:0))
letnewsJson=String(data:newsData,encoding:NSUTF8StringEncoding)!
ifdb.executeUpdate(sql,withArgumentsInArray:[classid,newsJson]){
print("缓存数据成功 - \(classid)")
}else{
print("缓存数据失败 - \(classid)")
rollback.memory=true
break
}
}
}
}
下拉刷新的时候,需要清理当前分类的数据,这里也是调用模型的清理方法:
@objc private func updateNewData() {
// 有网络的时候下拉会自动清除缓存
if true {
JFArticleListModel.cleanCache(classid!)
}
loadNews(classid!, pageIndex: 1, method: 0)
}
1
2
3
4
5
6
7
8
9
@objcprivatefuncupdateNewData(){
// 有网络的时候下拉会自动清除缓存
iftrue{
JFArticleListModel.cleanCache(classid!)
}
loadNews(classid!,pageIndex:1,method:0)
}
然后模型调用数据访问层的清理方法:
class func cleanCache(classid: Int) {
JFNewsDALManager.shareManager.cleanCache(classid)
}
1
2
3
classfunccleanCache(classid:Int){
JFNewsDALManager.shareManager.cleanCache(classid)
}
最终在数据访问层完成清理操作:
func cleanCache(classid: Int) {
var sql = ""
if classid == 0 {
sql = "DELETE FROM \(NEWS_LIST_HOME_TOP); DELETE FROM \(NEWS_LIST_HOME_LIST);"
}
JFSQLiteManager.shareManager.dbQueue.inDatabase { (db) in
if db.executeStatements(sql) {
// print("清空表成功 classid = \(classid)")
} else {
// print("清空表失败 classid = \(classid)")
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
funccleanCache(classid:Int){
varsql=""
ifclassid==0{
sql="DELETE FROM \(NEWS_LIST_HOME_TOP); DELETE FROM \(NEWS_LIST_HOME_LIST);"
}
JFSQLiteManager.shareManager.dbQueue.inDatabase{(db)in
ifdb.executeStatements(sql){
// print("清空表成功 classid = \(classid)")
}else{
// print("清空表失败 classid = \(classid)")
}
}
}
代码有些多,请把 数据请求和数据响应的流程,剩下的都是一些基本代码了。而关于缓存策略,缓存清除和缓存保存,则是按照自己的项目需求来针对性实现。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
