objectBox
objectbox的优点请看官方的介绍。objectBox有各个语言版本。之前做Android项目的时候接触过objectBox的Java版本和Kotlin版本,处于“一招鲜吃遍天”的无耻想法,就一直关注着swift版本。终于,在19年的9月底更新了1.0版本。现在更新到1.1.1版本。现在就跟着官方文档尝试下swift的版本。
今天在测试使用objectbox过程中,我遇到了一个问题如下图,当时死磕认为是自己xcode的swift版本与objectbox-swift版本之间的问题。原因是objectbox最新版本使用的swift版本为swift5.1+,但是在xcode的build setting中没有单独罗列出5.1的版本,只有一个大版本5,当时就怀疑是自己没有配置好,还傻傻的给人家提了个issue(哈哈哈~):
解决问题
首先,我们要排除是swift版本问题。既然xcode并没有细分出swift5.1,那么我们就使用命令行来查看:
xcrun swift -version //查看swift版本
看来不是swift版本问题:
~ » xcrun swift -version leg@liuerguaideMac-mini
Apple Swift version 5.1.2 (swiftlang-1100.0.278 clang-1100.0.33.9)
Target: x86_64-apple-darwin19.0.0
然后,经过stackoverflow,我怀疑是cocoapods的问题,然后开始尝试:
sudo gem install cocoapods-deintegrate cocoapods-clean //安装依赖
~/Desktop/DLL/MyDesktop/iOS/DataBaseTest(master*) » sudo gem install cocoapods-deintegrate cocoapods-clean
Password:
Successfully installed cocoapods-deintegrate-1.0.4
Parsing documentation for cocoapods-deintegrate-1.0.4
Done installing documentation for cocoapods-deintegrate after 0 seconds
Fetching cocoapods-clean-0.0.1.gem
Successfully installed cocoapods-clean-0.0.1
Parsing documentation for cocoapods-clean-0.0.1
Installing ri documentation for cocoapods-clean-0.0.1
Done installing documentation for cocoapods-clean after 0 seconds
2 gems installed
删除cocoapods的依赖库:
pod deintegrate
pod clean
最后再重新安装:
pod install
通过重新安装objectbox的依赖,问题得到解决。
ObjectBox-swift的使用
安装:
pod 'ObjectBox'
pod install --repo-update
Pods/ObjectBox/setup.rb
在xcode中,在Building Seeting中将Enable Bitcode更改为NO。
objectbox的简单使用:
@IBAction func insertByBox(_ sender: Any) {
if textFBox.text?.count == 0 {
return
}
spinner.startAnimating()
let start = Date().timeIntervalSince1970
let count = Int(self.textFBox.text!)!
DispatchQueue.global().async(group: group, execute: DispatchWorkItem.init(block: {
autoreleasepool {
//批量插入操作
let productEnities: [ProductEntity] = (0...count).map({ (index) -> ProductEntity in
self.initProudcutEnityData(index)
})
try! self.store?.runInTransaction {
let productEnityBox = self.store?.box(for: ProductEntity.self)
try? productEnityBox?.put(productEnities)
}
}
}))
group.notify(queue: DispatchQueue.main) {
self.spinner.stopAnimating()
let end = Date().timeIntervalSince1970
self.descBox.text = "插入耗时:" + String(end - start)
}
}
@IBAction func readByBox(_ sender: Any) {
spinner.startAnimating()
let start = Date().timeIntervalSince1970
DispatchQueue.global().async(group: group, execute: DispatchWorkItem.init(block: {
let productEnityBox = self.store?.box(for: ProductEntity.self)
let result = try? productEnityBox?.query {
ProductEntity.prdouctPrice.isEqual(to: "1000")
}.build().find()
if let result = result {
print(result.count)
print(result.last?.description as Any)
}
}))
group.notify(queue: DispatchQueue.main) {
self.spinner.stopAnimating()
let end = Date().timeIntervalSince1970
self.descBox.text = "读取耗时:" + String(end - start)
}
}
@IBAction func clearBox(_ sender: Any) {
spinner.startAnimating()
let start = Date().timeIntervalSince1970
DispatchQueue.global().async(group: group, execute: DispatchWorkItem.init(block: {
let productEnityBox = self.store?.box(for: ProductEntity.self)
let _ = try? productEnityBox?.removeAll()
}))
group.notify(queue: DispatchQueue.main) {
self.spinner.stopAnimating()
let end = Date().timeIntervalSince1970
self.descBox.text = "清空耗时:" + String(end - start)
}
}
func initProudcutEnityData(_ index: Int) -> ProductEntity {
let product = ProductEntity()
product.prdouctName = "香蕉" + String(index)
product.prdouctID = "1"
product.prdouctDes = "来自菲律宾的香蕉"
product.prdouctPrice = NSString(format: "%d", index) as String
product.prdouctUom = "KG"
product.prdouctCategory = "进口"
return product
}
Realm
Realm同样是一个跨平台的移动数据库引擎,支持swift、Java、Object-C、JavaScript三个常见语言,具体特性请移步到官网查看。
cocoapods安装
pod 'RealmSwift'
为了调试方便,你可以安装官方推荐的调试工具Realm Studio。
常用类介绍
Realms
一个Realm就是一个数据库容器的实例。我们可以简单的初始化一个实例对象:
let realm = try! Realm()
try! realm.write {
realm.add(myDog)
}
使用Realm()这种方式初始化,默认会在Document文件夹下创建一个名叫default.realm的文件。所以,你在多个地方使用Realm()这种方式初始化来获取实例,实际上是操作的同一个数据库实例。
但是,如果你要做本地持久化的时候如果考虑用户切换的情况的话,就需要手动配置下:
func setDefaultRealmForUser(username: String) {
var config = Realm.Configuration()
config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("\(username).realm")
Realm.Configuration.defaultConfiguration = config
}
你还可以控制Realm的版本,存储位置,是否可写入等:
let config = Realm.Configuration(
// 获取Bundle
fileURL: Bundle.main.url(forResource: "MyBundledData", withExtension: "realm"),
// 配置只可以读取,不能写入的权限
readOnly: true)
let realm = try! Realm(configuration: config)
// 只能做读取的操作
let results = realm.objects(Dog.self).filter("age > 5")
如果你只是希望在本次APP启动后,将数据临时存储到内存中,那么你可以使用以下方式:
let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "MyInMemoryRealm"))
在内存中使用Realm除了不会保存数据到本地外,Realm其他的功能均可以正常使用。如果你的数据不需要在本地持久化,那么可以选择这种在内存中临时使用的方式。但是,由于数据作用域的问题,你需要使用强引用来保证Realm实例的存在,否则Realm有可能被销毁掉,从而导致数据的丢失。
错误处理
像其他的I/O操作一样,常见Realm实例时有可能会因为资源受限而失败。不过不需要过多担心,实际上这种实例化失败只会发生在第一次你实例化时,所以你可以使用swift的异常处理机制:
do {
let realm = try Realm()
} catch let error as NSError {
// 处理错误
}
压缩Realm文件
Realm存储其文件所占的大小总是要比你想要存储的对象的实际大小要大,原因在于其内部的实现机制,你可以看官方文档对于线程相关的解释。为了避免对系统的额外开销,Realm很少在运行时对其文件进行压缩,取而代之的方式是它们以特定的大小增量增长,新数据被写入被跟踪文件内的未使用空间中。这样就可能出现在Realm文件中存在很大一部分未使用空间的情况.为了解决这种情况,你可以通过配置,选择在第一次打开Realm的时候进行压缩:
let config = Realm.Configuration(shouldCompactOnLaunch: { totalBytes, usedBytes in
// totalBytes refers to the size of the file on disk in bytes (data + free space)
// usedBytes refers to the number of bytes used by data in the file
// Compact if the file is over 100MB in size and less than 50% 'used'
let oneHundredMB = 100 * 1024 * 1024
return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5
})
do {
// Realm is compacted on the first open if the configuration block conditions were met.
let realm = try Realm(configuration: config)
} catch {
// handle error compacting or opening Realm
}
注意:压缩操作的工作方式是读取Realm文件的全部内容,将其重写到其他位置的新文件中,然后替换原始文件。如果文件中的数据量巨大,这可能是一项开销很大的操作。并且,如果另一个进程正在访问Realm,即使满足配置的条件,也将跳过压缩。这是因为在访问Realm时无法安全地执行压缩。
realm的简单使用:
func initProudcutData(_ index: Int) -> Product {
let product = Product()
product.prdouctName = "香蕉" + String(index)
product.prdouctID = "1"
product.prdouctDes = "来自菲律宾的香蕉"
product.prdouctPrice = NSString(format: "%d", index) as String
product.prdouctUom = "KG"
product.prdouctCategory = "进口"
return product
}
@IBAction func clearData(_ sender: Any) {
spinner.startAnimating()
let start = Date().timeIntervalSince1970
DispatchQueue.global().async(group: group, execute: DispatchWorkItem.init(block: {
let realm = try! Realm()
try! realm.write {
realm.deleteAll()
}
}))
group.notify(queue: DispatchQueue.main) {
self.spinner.stopAnimating()
let end = Date().timeIntervalSince1970
self.desc.text = "清空耗时:" + String(end - start)
}
}
@IBAction func insertData(_ sender: Any) {
if textF.text?.count == 0 {
return
}
spinner.startAnimating()
let start = Date().timeIntervalSince1970
let count = Int(self.textF.text!)!
DispatchQueue.global().async(group: group, execute: DispatchWorkItem.init(block: {
autoreleasepool {
var productArray: [Product] = []
for index in 0...count {
productArray.append(self.initProudcutData(index))
}
let realm = try! Realm()
realm.beginWrite()
realm.add(productArray)
try! realm.commitWrite()
}
}))
group.notify(queue: DispatchQueue.main) {
self.spinner.stopAnimating()
let end = Date().timeIntervalSince1970
self.desc.text = "插入耗时:" + String(end - start)
}
}
@IBAction func readData(_ sender: Any) {
spinner.startAnimating()
let start = Date().timeIntervalSince1970
DispatchQueue.global().async(group: group, execute: DispatchWorkItem.init(block: {
let realm = try! Realm()
let result = realm.objects(Product.self).filter("prdouctPrice='1000'")
print(result.count)
print(result.first?.description as Any)
}))
group.notify(queue: DispatchQueue.main) {
self.spinner.stopAnimating()
let end = Date().timeIntervalSince1970
self.desc.text = "读取耗时:" + String(end - start)
}
}
这个是Demo的下载地址。