swift中Realm和objectbox的使用总结

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(哈哈哈~):

error

解决问题

首先,我们要排除是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的下载地址。


   转载规则


《swift中Realm和objectbox的使用总结》 刘星星 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
uni-app中wach和computed的使用总结 uni-app中wach和computed的使用总结
最近做uni-app的项目,适配微信小程序.由于现在微信小程序出台了新的登录授权规范,根据规范和业务需求,首页共有3种展示状态:①未登录,②登录并且有全部权限,③登录有部分权限.该小程序的首页为购物车页面,业务相对复杂,考虑到状态的管理,
2019-12-13
下一篇 
在uni-app中自定义组件与插件 在uni-app中自定义组件与插件
前言在Vue的开发中,我们为了使代码能有更好的复用性,在开发阶段将具有相似业务的模块功能抽取成组件。而对于插件而言,则是通常用来为 Vue 添加全局功能。像我们使用的vue-router,就是一个插件。为了演示组件和插件的用法,我
2019-11-21
  目录