php mysql sqlite缓存_使用SQLite缓存数据分析与实现 | 六阿哥博客

前言:如果你准备看这篇文章,并且在此之前并没有使用过SQLite,建议先看

封装思想

在讲解SQLite之前,先聊聊封装思想。。写代码的都知道,面向对象三大思想(有些说四大,这个不是重点)之一的封装性,这里就举一个栗子来说明一下什么是封装。

比如控制器需要数据,控制器就跟模型说,我要数据,请给我数据。对于数据是怎么来的,控制器并不关心,控制器只关心模型有没有给他数据。

然后模型呢?控制器找模型要数据,模型也没有啊,所以模型去找数据访问层要数据,数据访问层的数据是怎么来的,模型也不关心,他关心的也只是有没有给他数据。

然后数据访问层呢?数据访问层自己也是没有数据的,他也得去找他的上一级要数据啊。

找谁要呢?这里就要分两种情况,第一种是本地有数据,他将本地数据直接返回给模型,模型再返回给我。第二种情况是本地没有数据,他就去网络请求类要,最终网络请求类才和后端交互,去后端请求数据,并将数据请求回来后保存到本地缓存,并返回给数据访问层,数据访问层再返回给模型,模型返回给控制器。

所以,这样就形成了一个数据请求和数据响应的链条:

f95f31470d3c17afee010215fba5c8b3.gif

上面栗子中提到了数据访问层(Data Access Layer),可能有些朋友对这个词比较陌生,你就当做是模型和数据之间的桥梁就行了。

需求分析

这里我以 六阿哥客户端 的(首页列表数据)为例,来一步步实现数据缓存。

f95f31470d3c17afee010215fba5c8b3.gif

需求:列表数据如果本地有缓存,就直接加载本地数据,如果没有才去请求网络。并且在下拉刷新的时候清除列表的缓存数据,并把新请求回来的数据缓存到本地。

这里涉及到数据处理的类有,网络工具类、数据访问层类、列表模型类、控制器。当然我们这里并不是直接使用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)")

}

}

}

代码有些多,请把 数据请求和数据响应的流程,剩下的都是一些基本代码了。而关于缓存策略,缓存清除和缓存保存,则是按照自己的项目需求来针对性实现。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部