尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Swift中数据库GRDB的使用方式记录

Swift中数据库GRDB的使用方式记录 文章目录一、前言二、简单实用方式三、拓展功能1、拓展自动更新功能一、前言本文记录一下Swift数据库GRDB的简单实用方式这是一个第三方的数据库。开源地址如下:GRDB: https://github.com/groue/GRDB.swift里面记录了基本的使用方式二、简单实用方式在引入库后这里记录一下简单的测试代码定义一个实体结构importFoundationimportGRDBstructPlayer:Codable,Identifiable,FetchableRecord,PersistableRecord{varid:Stringvarname:Stringvarscore:IntenumColumns{staticletnameColumn(CodingKeys.name)staticletscoreColumn(CodingKeys.score)}}数据库管理类GRDBManager//// GRDBManager.swift// maxclean//// Created by lbe on 2025/7/21.//importFoundationimportCombineimportGRDBclassGRDBManager{letdbQueue:DatabaseQueue// 私有化初始化方法确保只有成功创建数据库才会返回实例privateinit(dbQueue:DatabaseQueue){self.dbQueuedbQueue}// 静态工厂方法确保数据库初始化成功staticfunccreate()throws-GRDBManager{letfileManagerFileManager.default// 1. 创建应用支持目录letappSupportURLtryfileManager.url(for:.applicationSupportDirectory,in:.userDomainMask,appropriateFor:nil,create:true)// 2. 创建数据库子目录letdirectoryURLappSupportURL.appendingPathComponent(MyDatabase,isDirectory:true)tryfileManager.createDirectory(at:directoryURL,withIntermediateDirectories:true)// 3. 初始化数据库letdatabaseURLdirectoryURL.appendingPathComponent(db.sqlite)letdbQueuetryDatabaseQueue(path:databaseURL.path)// 4. 返回初始化成功的实例returnGRDBManager(dbQueue:dbQueue)}// 提供便捷初始化方法失败时返回nilstaticfunccreateOrNil()-GRDBManager?{do{returntrycreate()}catchleterrorasDatabaseError{print(数据库错误:\(error))returnnil}catch{print(未知错误:\(error))returnnil}}funccreateTable(){do{// 2. Define the database schematrydbQueue.write{dbintrydb.create(table:player){tint.primaryKey(id,.text)t.column(name,.text).notNull()t.column(score,.integer).notNull()}}print(数据表创建成功)}catchleterrorasDatabaseError{print(数据库错误:\(error))}catch{print(未知错误:\(error))}}funcinserData(){do{trydbQueue.write{dbintryPlayer(id:1,name:Arthur,score:100).insert(db)tryPlayer(id:2,name:Barbara,score:1000).insert(db)}print(数据插入成功)}catchleterrorasDatabaseError{print(数据库错误:\(error))}catch{print(未知错误:\(error))}}funcloadData(){do{trydbQueue.read{dbin// let player try Player.find(db, id: 1)letbestPlayerstryPlayer.order(\.score.desc).limit(10).fetchAll(db)print(YM--数据数量:\(bestPlayers.count))bestPlayers.forEach{playerinprint(YM--id:\(player.id)--name:\(player.name)--- score:\(player.score))}}}catchleterrorasDatabaseError{print(数据库错误:\(error))}catch{print(未知错误:\(error))}}funcdelData(){do{trydbQueue.write{dbinletcounttryPlayer.deleteAll(db)print(数据删除成功, 删除数量:\(count))}}catchleterrorasDatabaseError{print(数据库错误:\(error))}catch{print(未知错误:\(error))}}funcupdateData(){do{trydbQueue.write{dbin// Create database tablevarplayertryPlayer.find(db,id:1)// Update a recordplayer.score10tryplayer.update(db)// try player.updateChanges(db){ $0.score 10 }// Delete a record// try player.delete(db)}}catchleterrorasDatabaseError{print(数据改变异常,\(error))}catch{print(数据改变未知异常)}}varcancellable:AnyCancellable?//需要局部持久化否则函数执行结束后自动结束监听funcdataObserver(){// Define the observed valueprint(数据监听的开始)letobservationValueObservation.tracking{dbinprint(获取所有要监听的数据)letresulttryPlayer.fetchAll(db)print(数据监听的数量:\(result.count))returnresult}//// let _ observation.start(// in: dbQueue,// onError: { error in print(观察错误: \(error)) },// onChange: { players in// // 重新加载数据或更新UI// print(Fresh players:)// })//// Combineself.cancellableobservation.publisher(in:dbQueue).sink(receiveCompletion:{completioninprint(观察错误:\(completion))},receiveValue:{(players:[Player])inprint(Fresh players:\(players))})// cancellable.cancel()}//通过sql语句进行查找数据funcexecuteWine(){do{// 通过定义的数据模型执行sqllet_trydbQueue.read{dbintryPlayer.fetchAll(db,sql:SELECT * FROM wine)}// 获取查询的原始结果, 查询的结果为行trydbQueue.read{dbinifletrowtryRow.fetchOne(db,sql:SELECT * FROM wine WHERE id ?,arguments:[1]){letname:Stringrow[name]print(name)}}// 获取查询的原始结果查询的结果为列具体内容为值trydbQueue.read{dbinleturlstryURL.fetchCursor(db,sql:SELECT url FROM wine)whileleturltryurls.next(){print(url)}}// 其余查询方式// try Row.fetchCursor(...) // A Cursor of Row// try Row.fetchAll(...) // [Row]// try Row.fetchSet(...) // SetRow// try Row.fetchOne(...) // Row?}catch{}}funcgrdbError(){do{// 通过定义的数据模型执行sqllet_trydbQueue.read{dbintryPlayer.fetchAll(db,sql:SELECT * FROM wine)}}catchleterrorasDatabaseError{// The SQLite error code: 19 (SQLITE_CONSTRAINT)error.resultCode// The extended error code: 787 (SQLITE_CONSTRAINT_FOREIGNKEY)error.extendedResultCode// The eventual SQLite message: FOREIGN KEY constraint failederror.message// The eventual erroneous SQL query// INSERT INTO pet (masterId, name) VALUES (?, ?)error.sql// The eventual SQL arguments// [1, Bobby]error.arguments// Full error description// SQLite error 19: FOREIGN KEY constraint failed -// while executing INSERT INTO pet (masterId, name) VALUES (?, ?)error.description}catch{print(未知异常)}// 以下是其余演示程序 一// do {// try ...// } catch let error as DatabaseError {// switch error {// case DatabaseError.SQLITE_CONSTRAINT_FOREIGNKEY:// // foreign key constraint error// case DatabaseError.SQLITE_CONSTRAINT:// // any other constraint error// default:// // any other database error// }// }// 以下是其余演示程序 二// do {// try ...// } catch DatabaseError.SQLITE_CONSTRAINT_FOREIGNKEY {// // foreign key constraint error// } catch DatabaseError.SQLITE_CONSTRAINT {// // any other constraint error// } catch {// // any other database error// }}}演示页面:GRDBContentView//// GRDBContentView.swift// maxclean//// Created by lbe on 2025/7/21.//importFoundationimportSwiftUIstructGRDBContentView:View{StateprivatevargrdbManager:GRDBManager?varbody:someView{VStack{Text(这是一个第三方数据库GRDB的演示程序)Button{grdbManagerGRDBManager.createOrNil()}label:{Text(创建数据库)}Button{self.grdbManager?.createTable()}label:{Text(生成数据表)}Button{self.grdbManager?.inserData()}label:{Text(插入数据)}Button{self.grdbManager?.loadData()}label:{Text(查询数据)}Button{self.grdbManager?.updateData()}label:{Text(更改数据)}Button{self.grdbManager?.delData()}label:{Text(删除数据)}Button{self.grdbManager?.dataObserver()}label:{Text(监听数据变化)}Button{self.grdbManager?.grdbError()}label:{Text(错误信息详情看代码)}}}}#Preview{GRDBContentView()}三、拓展功能1、拓展自动更新功能这里简单演示下跟UI绑定的自动更新功能定义两个类型PhotoClusterstructPhotoCluster:Identifiable,Hashable,EquatablepublicfinalclassPhotoRecord:Record定义一个自动请求数据的接口逻辑/// 相似照片数据structClusterRequest:ValueObservationQueryable{staticvardefaultValue:[PhotoCluster]{[]}privatelettype:PhotoType?init(type:PhotoType?nil){self.typetype}funcfetch(_db:Database)throws-[PhotoCluster]{varphotostryPhotoRecord.filter(PhotoRecord.Columns.clusterId0).fetchAll(db)iflettytype{photosphotos.filter({$0.typety})}varmap:[Int64:[PhotoRecord]][:]forphotoinphotos{map[photo.clusterId,default:[]].append(photo)}varresultmap.compactMap{letrecords$0.value.sorted(by:{$0.score$1.score})returnPhotoCluster(id:$0.key,records:records)}result.sort(by:{$0.date$1.date})returnresult}}然后UI中进行使用// 第一种方式可以直接获取数据Query(ClusterRequest(type:.livePhoto))varlivePhotoModels:[PhotoCluster]// 第二种方式可以通过传递参数获取数据init(photoType:PhotoType,_groupId:Int64?nil){self._clustersQuery(ClusterRequest(type:photoType))}下面是对数据进行改造通过Published在ViewModel中获取数据然后这样可以做其余额外操作更改代码为以下MainActorclassPhotoListViewModel:ObservableObject{Publishedvarclusters:[PhotoCluster][]init(photoType:PhotoType){self.photoTypephotoTypeobserve()}privatevartask:TaskVoid,Never?privatefuncobserve(){letrequestClusterRequest(type:photoType)letobservationValueObservation.tracking{dbintryrequest.fetch(db)}// 用 async stream 来接收数据库变化taskTask{do{fortryawaitclustersinobservation.values(in:IQADatabase.shared.dbReader){self.clustersclusters}}catch{print(Database observation error:\(error))}}}deinit{task?.cancel()}}structPhotoListView:View{// 封装 clustersprivatevarclusters:[PhotoCluster]{viewModel.clusters}// 使用 QueryClusterRequest 的写法会导致数据变动时候没有办法重新处理选中状态的逻辑所以用跟UI解绑的方式获取数据// ... for clusters { // 显示每个UI }}
返回列表