Realm是一个跨平台的移动数据库引擎,于2014 年7月发布,是一个跨平台的移动数据库引擎,专门为移动应用的数据持久化而生。其目的是要取代 Core Data 和 SQLite。目前支持iOS、Android平台,同时支持Objective-C、Swift、Java、React Native、Xamarin等多种编程语言* Realm并不是对SQLite或者CoreData的简单封装, 是由核心数据引擎C++打造,是拥有独立的数据库存储引擎,可以方便、高效的完成数据库的各种操作.
Realm官网
Realm官方文档
Realm GitHub
- 跨平台:现在很多应用都是要兼顾iOS和Android两个平台同时开发。如果两个平台都能使用相同的数据库,那就不用考虑内部数据的架构不同,使用Realm提供的API,可以使数据持久化层在两个平台上无差异化的转换。
- 简单易用:Core Data 和 SQLite 冗余、繁杂的知识和代码足以吓退绝大多数刚入门的开发者,而换用 Realm,则可以极大地减少学习成本,立即学会本地化存储的方法。毫不吹嘘的说,把官方最新文档完整看一遍,就完全可以上手开发了。
- 可视化:Realm 还提供了一个轻量级的数据库查看工具,在Mac Appstore 可以下载“Realm Browser”这个工具,开发者可以查看数据库当中的内容,执行简单的插入和删除数据的操作。毕竟,很多时候,开发者使用数据库的理由是因为要提供一些所谓的“知识库”。
- Realm 支持以下的属性类型:Bool、Int8、Int16、Int32、Int64、Double、Float、String、Date(精度到秒)以及Data.
- 也可以使用
List<object>
和Object
来建立诸如一对多、一对一之类的关系模型,此外Object
的子类也支持此功能。
pod 'RealmSwift'
(1)先去 Realm 的官网去下载最新框架:http://static.realm.io/downloads/swift/latest
(2)拖拽 RealmSwift.framework 和 Realm.framework 文件到”Embedded Binaries”选项中。选中 Copy items if needed 并点击 Finish
Realm
数据库之前,可以对其进行配置。 通过创建一个 Realm.Configuration
的对象实例,然后配置相应的属性。 通过创建并自定义相关的配置值,使得您可以实现个性化的设置,包括如下方面:
- 对于本地 Realm 数据库而言,可以配置 Realm 文件在磁盘上的路径;
- 对于可同步 Realm 数据库而言,可以配置管理该 Realm 数据库的用户,以及 Realm 数据库在 Realm 对象服务器上的远程路径;
- 对于架构版本之间发生变化的 Realm 数据库而言,可以通过迁移功能来控制旧架构的 Realm 数据该如何更新到最新的架构。
- 对于存储的数据量过大、或者数据频繁发生变化的 Realm 数据库而言,可以通过压缩功能来控制 Realm 文件该如何实现压缩,从而确保能高效地利用磁盘空间。
Realm
实例的时候,通过向 Realm(configuration: config)
方法传递该配置对象,或者通过 Realm.Configuration.defaultConfiguration = config
方法,将默认 Realm
数据库的默认配置设置为我们所需的配置。Realm
数据库,并且当前账户所使用的数据库将作为默认 Realm
数据库来使用:func setDefaultRealmForUser(username: String) {
var config = Realm.Configuration()
// 使用默认的目录,但是请将文件名替换为用户名
config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("\(username).realm")
// 将该配置设置为默认 Realm 配置
Realm.Configuration.defaultConfiguration = config
}
let config = Realm.Configuration(
// 获取需要打包文件的 URL 路径
fileURL: Bundle.main.url(forResource: "MyBundledData", withExtension: "realm"),
// 以只读模式打开文件,因为应用数据包并不可写
readOnly: ``true``)`
// 通过配置打开 Realm 数据库
let realm = try! Realm(configuration: config)
// 通过配置打开 Realm 数据库
let results = realm.objects(Dog.self).filter("age > 5")
Realm
对象服务器上的 Realm 数据库同样也可以使用 Realm.Configuration
和相关的工厂方法进行配置,这与之前创建本地 Realm
数据库的做法基本类似,只不过在 Realm.Configuration
中,需要将 syncConfiguration
属性设置为 SyncConfiguration
。 可同步 Realm
数据库 (synchronized Realm
) 可通过 URL 地址来进行定位。// 创建配置
let syncServerURL = URL(string: "realm://localhost:9080/~/userRealm")!
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: syncServerURL))
// 打开远程 Realm 数据库
let realm = try! Realm(configuration: config)
// 任何对此 Realm 数据库所做的操作,都会同步到所有设备上!
Realm
数据库的操作需要耗费大量时间的话,比如说需要执行迁移、压缩 或者需要从可同步 Realm
数据库下载远程内容,那么建议使用 asyncOpen API
。 这使得您可以在调度到指定队列之前,在后台线程中执行任意的初始化工作。 当可同步 Realm
数据库只能以只读权限打开的时候,那么必须使用 asyncOpen
。Realm.Configuration
中的 inMemoryIdentifier
属性,而不是 fileURL
属性,这样就能够创建一个完全在内存中运行的 Realm 数据库 (in-memory Realm
),它将不会存储在磁盘当中。 设置 inMemoryIdentifier
会将 fileURL
置为 nil
(反之亦然)。let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "MyInMemoryRealm"))
在某些情况下,例如清除缓存、或者重置整个数据集之类的操作,那么就可能需要从磁盘中将 Realm
文件给完全删除掉。
除非必要,否则 Realm
数据库会避免将数据复制到内存中,因此由 Realm
管理的所有对象都会包含磁盘上文件的引用,并且必须在文件被安全删除之前完成释放。 这包括从 Realm
读取到(或者添加到 Realm 中)的所有对象,包括所有 List
、Results
和 ThreadSafeReference
对象,以及 Realm
本身。
实际上,这意味着对 Realm
文件的删除,要么在应用启动时、在打开 Realm
数据库之前完成,要么只在显式声明的自动释放池 中打开 Realm
数据库,然后在自动释放池后面进行删除。 这样才能够确保所有的 Realm
对象都能够成功释放。
最后,尽管不是必须的,不过您应当将 Realm
辅助文件连同 Realm
文件一起删除,以完全清除所有的相关文件。
autoreleasepool {
// 在这里进行所有的 Realm 操作
}
let realmURL = Realm.Configuration.defaultConfiguration.fileURL!
let realmURLs = [
realmURL,
realmURL.appendingPathExtension("lock"),
realmURL.appendingPathExtension("note"),
realmURL.appendingPathExtension("management")
]
for URL in realmURLs {
do {
try FileManager.default.removeItem(at: URL)
} catch {
// 错误处理
}
}
Person
模型:class Person: Object {
@objc dynamic var firstName = ""
@objc dynamic var lastName = ""
@objc dynamic var age = 0
}
fullname
属性, 而不是将“姓”和“名”分离开来。class Person: Object {
@objc dynamic var fullName = ""
@objc dynamic var age = 0
}
Realm
就会注意到代码和硬盘上数据不匹配。 每当这时,您必须进行数据迁移,否则当您试图打开这个文件的话 Realm
就会抛出错误。// 在(application:didFinishLaunchingWithOptions:)中进行配置
let config = Realm.Configuration(
// 设置新的架构版本。这个版本号必须高于之前所用的版本号
// (如果您之前从未设置过架构版本,那么这个版本号设置为 0)
schemaVersion: 1,
// 设置闭包,这个闭包将会在打开低于上面所设置版本号的 Realm 数据库的时候被自动调用
migrationBlock: { migration, oldSchemaVersion in
// 目前我们还未进行数据迁移,因此 oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// 什么都不要做!Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构
}
})
// 告诉 Realm 为默认的 Realm 数据库使用这个新的配置对象
Realm.Configuration.defaultConfiguration = config
// 现在我们已经告诉了 Realm 如何处理架构的变化,打开文件之后将会自动执行迁移
let realm = try! Realm()
fullName
),这样才有意义。Migration().enumerateObjects(_:_:)
来枚举特定类型的每个 Object
对象,然后执行必要的迁移逻辑。注意,对枚举中每个已存在的 Object
实例来说,应该是通过访问 oldObject
对象进行访问,而更新之后的实例应该通过 newObject
进行访问:// 在 application(application:didFinishLaunchingWithOptions:) 中进行配置
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
// enumerateObjects(ofType:_:) 方法遍历了存储在 Realm 文件中的每一个“Person”对象
migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in
// 将名字进行合并,存放在 fullName 域中
let firstName = oldObject!["firstName"] as! String
let lastName = oldObject!["lastName"] as! String
newObject!["fullName"] = "\(firstName) \(lastName)"
}
}
})
Person
类中的yearsSinceBirth
属性重命名为age
,修改配置生效后,对应的表字段也会改过来。// Inside your application(application:didFinishLaunchingWithOptions:)
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// The renaming operation should be done outside of calls to `enumerateObjects(ofType: _:)`.
migration.renameProperty(onType: Person.className(), from: "yearsSinceBirth", to: "age")
}
})
(1)加密后的 Realm文件不能跨平台使用(因为 NSFileProtection 只有 iOS 才可以使用)
(2)Realm 文件不能在没有密码保护的 iOS 设备中进行加密。为了避免这些问题(或者您想构建一个 OS X 的应用),可以使用 Realm 提供的加密方法。
(3)加密过的 Realm 只会带来很少的额外资源占用(通常最多只会比平常慢10%)。
Realm
支持在创建 Realm 数据库时采用64位的密钥对数据库文件进行 AES-256+SHA2
加密。/***** 在创建 Realm 数据库时采用64位的密钥对数据库文件进行 AES-256+SHA2 加密 ****/
// 产生随机密钥
var key = Data(count: 64)
_ = key.withUnsafeMutableBytes { bytes in
SecRandomCopyBytes(kSecRandomDefault, 64, bytes)
}
// 打开加密文件
let config = Realm.Configuration(encryptionKey: key)
let realm:Realm
do {
realm = try Realm(configuration: config)
} catch let error as NSError {
// 如果密钥错误,error 会提示数据库不可访问
fatalError("Error opening realm: \(error)")
}
// 和往常一样使用 Realm 即可
let dogs = realm.objects(Book.self).filter("name contains Fido")
AES-256
来进行加密和解密,并用 SHA-2 HMAC
来进行验证。 每次您要获取一个 Realm 实例时,您都需要提供一次相同的密钥。Realm
只会带来很少的额外资源占用(通常最多只会比平常慢10%)let realm = try! Realm()
try! realm.write {
realm.add(myDog)
}
Realm
对象可以被实例化,还可作为未管理对象使用(例如,还未添加到 Realm 数据库),并且使用方式与其它正常 Swift 对象无异。然而,如果要在线程之间共享对象,或者在应用启动后反复使用,那么您必须将这些对象添加到 Realm 数据库中。向 Realm 数据库中添加对象必须在写入事务内完成。由于写入事务将会产生无法忽略的性能消耗,因此您应当检视您的代码,以确保尽可能减少写入事务的数量。注意:Realm 的写入操作是同步以及阻塞进行的,它并不会异步执行。如果线程 A 开始进行写入操作,然后线程 B 在线程 A 结束之前,对相同的 Realm 数据库也执行了写入操作,那么线程 A 必须要在线程 B 的写入操作发生之前,结束并提交其事务。写入事务会在 beginWrite() 执行时自动刷新,因此重复写入并不会产生竞争条件。
let myDog = Dog()
myDog.name = "Fido"
myDog.age = 1
try! realm.write {
realm.add(myDog)
}
let myPuppy = realm.objects(Dog.self).filter("age == 1").first
try! realm.write {
myPuppy!.age = 2
}
print("age of my dog: \(myDog.age)") // => 2
import Foundation
import RealmSwift
//消费类型
class ConsumeType: Object {
//类型名
@objc dynamic var name = ""
}
//消费条目
class ConsumeItem: Object {
//条目名
@objc dynamic var name = ""
//金额
@objc dynamic var cost = 0.00
//时间
@objc dynamic var date = Date()
//所属消费类别
@objc dynamic var type: ConsumeType?
}
import UIKit
import RealmSwift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//使用默认的数据库`
let realm = try! Realm()
//查询所有的消费记录
let items = realm.objects(ConsumeItem.self)
//已经有记录的话就不插入了
if items.count>0 {
return
}
//创建两个消费类型
let type1 = ConsumeType()
type1.name = "购物"
let type2 = ConsumeType()
type2.name = "娱乐"
//创建三个消费记录
let item1 = ConsumeItem(value: ["买一台电脑", 5999.00, Date(), type1]) //可使用数组创建`
let item2 = ConsumeItem()
item2.name = "看一场电影"
item2.cost = 30.00
item2.date = Date(timeIntervalSinceNow: -36000)
item2.type = type2
let item3 = ConsumeItem()
item3.name = "买一包泡面"
item3.cost = 2.50
item3.date = Date(timeIntervalSinceNow: -72000)
item3.type = type1
// 数据持久化操作(类型记录也会自动添加的)`
try! realm.write {
realm.add(item1)
realm.add(item2)
realm.add(item3)
}
//打印出数据库地址
print(realm.configuration.fileURL ?? "")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
// 在事务中更新对象
try! realm.write {
author.name = "Thomas Pynchon"
}
Object
、Result
和 List
均允许使用 键值编码(KVC
)。 当您需要在运行时决定何种属性需要进行更新的时候, 这个方法就非常有用了。KVC
是一个很好的做法, 这样就不用承受遍历集合时为每个项目创建访问器 所带来的性能损耗。let persons = realm.objects(Person.self)
try! realm.write {
persons.first?.setValue(true, forKeyPath: "isFirst")
// 将每个 person 对象的 planet 属性设置为 "Earth"
persons.setValue("Earth", forKeyPath: "planet")
}
Realm().add(_:update:)
,从而让 Realm
基于主键来自动更新或者添加对象。// 创建一个 book 对象,其主键与之前存储的 book 对象相同
let cheeseBook = Book()
cheeseBook.title = "Cheese recipes"
cheeseBook.price = 9000
cheeseBook.id = 1
// 更新这个 id = 1 的 book
try! realm.write {
realm.add(cheeseBook, update: true)
}
// 假设主键为 `1` 的 "Book" 对象已经存在
try! realm.write {
realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: true)
// book 对象的 `title` 属性仍旧保持不变
}
Realm().delete(_:)
方法。// cheeseBook 存储在 Realm 数据库中
// 在事务中删除对象
try! realm.write {
realm.delete(cheeseBook)
}
Realm
数据库当中的所有数据。请注意,Realm
文件会保留在磁盘上所占用的空间,从而为以后的对象预留足够的空间,从而实现快速存储。// 从 Realm 数据库中删除所有对象
try! realm.write {
realm.deleteAll()
}
Results
实例,其中包含了一组 Object
对象。Results
的接口与 Array
基本相同,并且可以使用索引下标来访问包含在 Results 当中的对象。与 Array
所不同的是,Results
只能持有一个 Object
子类类型。Realm
中都是延迟加载的。只有当属性被访问时,数据才会被读取。Results
当中的 Object
来直接遍历关系图。Results
将时刻与 Realm
数据库当中的数据保持一致, 如有可能,会在后台线程中执行再一次查询操作。Realm
数据库中检索对象的最基本方法是 Realm().objects(_:)
,这个方法将会返回 Object
子类类型在默认 Realm
数据库当中的查询到的所有数据,并以 Results
实例的形式返回。let dogs = realm.objects(Dog.self) // 从默认的 Realm 数据库中遍历所有 Dog 对象
NSPredicate
有所了解的话,那么您就已经掌握了在 Realm
中进行查询的方法了。Objects
、Realm
、List
和 Results
均提供了相关的方法,从而只需传递 NSPredicate
实例、断言字符串、或者断言格式化字符串来查询特定的 Object
实例,这与对 NSArray
进行查询所类似。Results().filter(_:...)
方法,从默认 Realm
数据库中遍历出所有棕黄色、名字以 “B” 开头的狗狗:// 使用断言字符串来查询
var tanDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'")
// 使用 NSPredicate 来查询
let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
tanDogs = realm.objects(Dog.self).filter(predicate)
Results
允许您指定一个排序标准,然后基于关键路径、属性或者多个排序描述符来进行排序。例如,下列代码让上述示例中返回的 Dog 对象按名字进行升序排序:// 对颜色为棕黄色、名字以 "B" 开头的狗狗进行排序
let sortedDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'").sorted(byKeyPath: "name")
class Person: Object {
@objc dynamic var name = ""
@objc dynamic var dog: Dog?
}
class Dog: Object {
@objc dynamic var name = ""
@objc dynamic var age = 0
}
let dogOwners = realm.objects(Person.self)
let ownersByDogAge = dogOwners.sorted(byKeyPath: "dog.age")
请注意,
sorted(byKeyPath:)
和sorted(byProperty:)
不支持 将多个属性用作排序基准,此外也无法链式排序(只有最后一个sorted
调用会被使用)。 如果要对多个属性进行排序,请使用sorted(by:)
方法,然后向其中输入多个SortDescriptor
对象。
let tanDogs = realm.objects(Dog.self).filter("color = 'tan'")
let tanDogsWithBNames = tanDogs.filter("name BEGINSWITH 'B'")
Object
实例是底层数据的动态体现,其会自动进行更新,这意味着您无需去重新检索结果。它们会直接映射出 Realm
数据库在当前线程中的状态,包括当前线程上的写入事务。唯一的例外是,在使用 for…in 枚举时,它会将刚开始遍历时满足匹配条件的所有对象给遍历完,即使在遍历过程中有对象被过滤器修改或者删除。let puppies = realm.objects(Dog.self).filter("age < 2")
puppies.count // => 0
try! realm.write {
realm.create(Dog.self, value: ["name": "Fido", "age": 1])
}
puppies.count // => 1
Results
对象均有此特性,无论是匹配查询出来的还是链式查询出来的。Results
属性不仅让 Realm
数据库保证高速和高效,同时还让代码更为简洁、更加灵活。例如,如果视图控制器基于查询结果来实现,那么您可以将 Results
存储在属性当中,这样每次访问就不需要刷新以确保数据最新了。Realm
通知,以了解 Realm
数据何时发生了更新,比如说可以决定应用 UI 何时进行刷新,而无需重新检索 Results
。Results
不变的唯一情况是在快速枚举的时候,这样就可以在枚举过程中,对匹配条件的对象进行修改。try! realm.write {
for person in realm.objects(Person.self).filter("age == 10") {
person.age += 1
}
}
SQLite
中的 “LIMIT” 关键字)。这通常是很有必要的,可以避免一次性从硬盘中读取太多的数据,或者将太多查询结果加载到内存当中。Realm
中的检索是惰性的,因此这行这种分页行为是没有必要的。因为 Realm
只会在检索到的结果被明确访问时,才会从其中加载对象。Results
对象一样简单,只需要读出您所需要的对象即可。
// 循环读取出前 5 个 Dog 对象
// 从而限制从磁盘中读取的对象数量
let dogs = try! Realm().objects(Dog.self)
for i in 0..<5 {
let dog = dogs[i]
// ...
}
Realm
在底层数据库中重写了 setters
和 getters
方法,所以您不可以在您的对象上再对其进行重写。Realm
忽略属性,该属性的访问起可以被重写, 并且可以调用其他的 getter
和 setter
方法。自动增长属性:Realm
没有线程且进程安全的自动增长属性机制,而这在其他数据库中常常用来产生主键。然而,在绝大多数情况下,对于主键来说,我们需要的是一个唯一的、自动生成的值,因此没有必要使用顺序的、连续的、整数的 ID 作为主键,因此一个独一无二的字符串主键通常就能满足需求了。一个常见的模式是将默认的属性值设置为 NSUUID().UUIDString
以产生一个唯一的字符串 ID。
自动增长属性另一种常见的动机是为了维持插入之后的顺序。在某些情况下,这可以通过向某个 List
中添加对象,或者使用 NSDate()
默认值的 createdAt
属性。
Object.ignoredProperties()
可以防止 Realm
存储数据模型的某个属性。Realm 将不会干涉这些属性的常规操作,它们将由成员变量(var)提供支持,并且您能够轻易重写它们的 setter
和 getter
。class Person: Object {
@objc dynamic var tmpID = 0
var name: String { // 计算属性将被自动忽略
return "\(firstName) \(lastName)"
}
@objc dynamic var firstName = ""
@objc dynamic var lastName = ""
override static func ignoredProperties() -> [String] {
return ["tmpID"]
}
}
Object.primaryKey()
可以设置模型的主键。Realm
之后,该对象的主键将不可修改。class Person: Object {
@objc dynamic var id = 0
@objc dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
Object.indexedProperties()
方法可以为数据模型中需要添加索引的属性建立索引:class Book: Object {
@objc dynamic var price = 0
@objc dynamic var title = ""
override static func indexedProperties() -> [String] {
return ["title"]
}
}
class Dog: Object {
@objc dynamic var name = ""
@objc dynamic var age = 0
// Realm 并不会存储这个属性,因为这个属性只定义了 getter
// 定义“owners”,和 Person.dogs 建立反向关系
let owners = LinkingObjects(fromType: Person.self, property: "dogs")
}
List
中可以包含简单类型的 Object
,表面上和可变的 Array
非常类似。List
只能够包含 Object
类型,不能包含诸如String
之类的基础类型。class Person: Object {
... // 其余的属性声明
let dogs = List<Dog>()
}
// 这里我们就可以使用已存在的狗狗对象来完成初始化
let aPerson = Person(value: ["李四", 30, [aDog, anotherDog]])
// 还可以使用多重嵌套
let aPerson = Person(value: ["李四", 30, [["小黑", 5], ["旺财", 6]]])
let someDogs = realm.objects(Dog.self).filter("name contains '小白'")
ZhangSan.dogs.append(objectsIn: someDogs)
ZhangSan.dogs.append(dahuang)
Objective‑C
中访问 Realm Swift
模型的话,那么注意所有 List
以及 RealmOptional
属性都不可用(就像其他 Swift 独有的数据类型一样)——如果有必要的话,您可以添加封装的 getter 和 setter 方法,将其在 NSNumber 或者 NSArray 之间进行转化。此外,早于 Xcode 7 Beta 5 之前的版本有一个 已知的Swift bug,它会导致自动生成的 Objective‑C 头文件(-Swift.h)无法通过编译。您就必须将 List 类型的属性设置为 private 或者 internal。class MyModel: Object {
@objc dynamic var myValue = ""
convenience init(myValue: String) {
self.init() // 请注意这里使用的是 'self' 而不是 'super'
self.myValue = myValue
}
}
Realm
数据库发生变化时,就会发送 Realm
通知;如果只有个别对象被修改、添加或者删除,那么就会发送集合通知。Run Loop
// 获取 Realm 通知
let token = realm.observe { notification, realm in
viewController.updateUI()
}
// 随后
token.invalidate()
RealmCollectionChange
参数来访问这些变更。该对象存放了受删除 (deletions
)、插入 (insertions
) 以及修改 (modifications
) 所影响的索引信息。Realm
支持对象级别的通知。可以在特定的 Realm
对象上进行通知的注册,对象被删除、修改时获取相应的通知。class StepCounter: Object {
@objc dynamic var steps = 0
}
let stepCounter = StepCounter()
let realm = try! Realm()
try! realm.write {
realm.add(stepCounter)
}
var token : NotificationToken?
token = stepCounter.observe { change in
switch change {
case .change(let properties):
for property in properties {
if property.name == "steps" && property.newValue as! Int > 1000 {
print("Congratulations, you've exceeded 1000 steps.")
token = nil
}
}
case .error(let error):
print("An error occurred: \(error)")
case .deleted:
print("The object was deleted.")
}
}
{
"channels": [
{
"name_en": "Personal Radio",
"seq_id": 0,
"abbr_en": "My",
"name": "私人兆赫",
"channel_id": 0
},
{
"name": "华语",
"seq_id": 0,
"abbr_en": "",
"channel_id": "1",
"name_en": ""
},
{
"name": "欧美",
"seq_id": 1,
"abbr_en": "",
"channel_id": "2",
"name_en": ""
}
]
}
class DoubanChannel:Object {
//频道id
@objc dynamic var channel_id = ""
//频道名称
@objc dynamic var name = ""
//频道英文名称
@objc dynamic var name_en = ""
//排序
@objc dynamic var seq_id = 0
@objc dynamic var abbr_en = ""
//设置主键
override static func primaryKey() -> String? {
return "channel_id"
}
}
import UIKit
import RealmSwift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 调用API
let url = URL(string: "http://www.douban.com/j/app/radio/channels")!
let response = try! Data(contentsOf: url)
// 对 JSON 的回应数据进行反序列化操作
let json = try! JSONSerialization.jsonObject(with: response,
options: .allowFragments) as! [String:Any]
let channels = json["channels"] as! [[String:Any]]
let realm = try! Realm()
try! realm.write {
// 为数组中的每个元素保存一个对象(以及其依赖对象)
for channel in channels {
if channel["seq_id"] as! Int == 0 {continue} //第一个频道数据有问题,丢弃掉
realm.create(DoubanChannel.self, value: channel, update: true)
}
}
print(realm.configuration.fileURL ?? "")
}
}
类名称
的长度最大只能存储57
个 UTF8 字符。属性名称
的长度最大只能支持63
个 UTF8 字符。- Data 和 String 属性不能保存超过 16 MB 大小的数据。如果要存储大量的数据,可通过将其分解为16MB 大小的块,或者直接存储在文件系统中,然后将文件路径存储在 Realm 中。如果您的应用试图存储一个大于 16MB 的单一属性,系统将在运行时抛出异常。
- 对字符串进行排序以及不区分大小写查询只支持“基础拉丁字符集”、“拉丁字符补充集”、“拉丁文扩展字符集 A” 以及”拉丁文扩展字符集 B“(UTF-8 的范围在 0~591 之间)。
- 每个单独的 Realm 文件大小无法超过应用在 iOS 系统中所被允许使用的内存量——这个量对于每个设备而言都是不同的,并且还取决于当时内存空间的碎片化情况(关于此问题有一个相关的 Radar:rdar://17119975)。如果您需要存储海量数据的话,那么可以选择使用多个 Realm 文件并进行映射。
- 在不同的线程中使用同一个
Realm
文件,必须每一个线程初始化一个新的Realm
实例。- 不支持跨线程共享
Realm
实例。Realm
实例要访问相同的Realm
文件还必须使用相同的Realm.Configuration
*** Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.'******* First throw call stack:****(**** 0 CoreFoundation 0x000000011479f34b __exceptionPreprocess + 171**** 1 libobjc.A.dylib 0x00000001164
如果程序崩溃了,出现以上错误,那就是因为你访问Realm数据的时候,使用的Realm对象所在的线程和当前线程不一致。
RLMResults * results = [self selectUserWithAccid:bhUser.accid]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addOrUpdateObject:results[0]];
}];
});
***** Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread'******* First throw call stack:****(**** 0 CoreFoundation 0x000000011517a34b __exceptionPreprocess + 171**** 1 libobjc.A.dylib 0x0000000116
一般来说 Realm 数据库比 SQLite 数据库在硬盘上占用的空间更少。如果您的 Realm 文件大小超出了您的想象,这可能是因为您数据库中的 RLMRealm中包含了旧版本数据。
为了使您的数据有相同的显示方式,Realm 只在循环迭代开始的时候才更新数据版本。这意味着,如果您从 Realm 读取了一些数据并进行了在一个锁定的线程中进行长时间的运行,然后在其他线程进行读写 Realm 数据库的话,那么版本将不会被更新,Realm 将保存中间版本的数据,但是这些数据已经没有用了,这导致了文件大小的增长。这部分空间会在下次写入操作时被重复利用。这些操作可以通过调用writeCopyToPath:error:来实现。
通过调用invalidate,来告诉 Realm 您不再需要那些拷贝到 Realm 的数据了。这可以使我们不必跟踪这些对象的中间版本。在下次出现新版本时,再进行版本更新。
您可能在 Realm 使用Grand Central Dispatch时也发现了这个问题。在 dispatch 结束后自动释放调度队列(dispatch queue)时,调度队列(dispatch queue)没有随着程序释放。这造成了直到
RLMRealm 对象被释放后,Realm 中间版本的数据空间才会被再利用。为了避免这个问题,您应该在 dispatch 队列中,使用一个显式的自动调度队列(dispatch queue)。
Realm 没有线程/进程安全的自动增长属性机制,这在其他数据库中常常用来产生主键。然而,在绝大多数情况下,对于主键来说,我们需要的是一个唯一的、自动生成的值,因此没有必要使用顺序的、连续的、整数的 ID 作为主键。
在这种情况下,一个独一无二的字符串主键通常就能满足需求了。一个常见的模式是将默认的属性值设置为 [[NSUUID UUID] UUIDString]
以产生一个唯一的字符串 ID。
自动增长属性另一种常见的动机是为了维持插入之后的顺序。在某些情况下,这可以通过向某个 RLMArray中添加对象,或者使用 [NSDate date]默认值的createdAt属性。
Realm 允许模型能够生成更多的子类,也允许跨模型进行代码复用,但是由于某些 Cocoa 特性使得运行时中丰富的类多态无法使用。以下是可以完成的操作:
父类中的类方法,实例方法和属性可以被它的子类所继承
子类中可以在方法以及函数中使用父类作为参数
以下是不能完成的:
多态类之间的转换(例如子类转换成子类,子类转换成父类,父类转换成子类等)
同时对多个类进行检索
多类容器 (RLMArray以及 RLMResults)
这一点也是比较蛋疼。
Realm支持以下的属性类型:BOOL、bool、int、NSInteger、long、long long、float、double、NSString、NSDate、NSData以及 被特殊类型标记的NSNumber。CGFloat属性的支持被取消了,因为它不具备平台独立性。
这里就是不支持集合,比如说NSArray,NSMutableArray,NSDictionary,NSMutableDictionary,NSSet,NSMutableSet。如果服务器传来的一个字典,key是一个字符串,对应的value就是一个数组,这时候就想存储这个数组就比较困难了。当然Realm里面是有集合的,就是RLMArray,这里面装的都是RLMObject。
所以我们想解决这个问题,就需要把数据里面的东西都取出来,如果是model,就先自己接收一下,然后转换成RLMObject的model,再存储到RLMArray里面去,这样转换一遍,还是可以的做到的。
如果能设置主键,请尽量设置主键,因为这样方便我们更新数据,我们可以很方便的调用addOrUpdateObject:
或者 createOrUpdateInRealm:withValue:
方法进行更新。这样就不需要先根据主键,查询出数据,然后再去更新。有了主键以后,这两步操作可以一步完成。
[realm transactionWithBlock:^{
[self.realm beginWriteTransaction];
[self convertToRLMUserWith:bhUser To:[self convertToRLMUserWith:bhUser To:nil]];
[self.realm commitWriteTransaction];
}];
出错信息如下:
*** Terminating app due to uncaught exception 'RLMException', reason: 'The Realm is already in a write transaction'******* First throw call stack:****(**** 0 CoreFoundation 0x0000000112e2d34b __exceptionPreprocess + 171**** 1 libobjc.A.dylib 0x00000001
很多开发者应该都会对Core Data
和Sqlite3
或者FMDB
,自己封装一个类似Helper
的单例。于是我也在这里封装了一个单例,在新建完Realm数据库的时候strong持有一个Realm的对象。然后之后的访问中只需要读取这个单例持有的Realm对象就可以拿到数据库了。
想法是好的,但是同一个Realm
对象是不支持跨线程操作realm
数据库的。
Realm
通过确保每个线程始终拥有 Realm
的一个快照,以便让并发运行变得十分轻松。你可以同时有任意数目的线程访问同一个 Realm
文件,并且由于每个线程都有对应的快照,因此线程之间绝不会产生影响。需要注意的一件事情就是不能让多个线程都持有同一个 Realm
对象的 实例 。如果多个线程需要访问同一个对象,那么它们分别会获取自己所需要的实例(否则在一个线程上发生的更改就会造成其他线程得到不完整或者不一致的数据)。
其实let realm = try! Realm(configuration: config)
(OC代码:RLMRealm *realm = [RLMRealm defaultRealm];
) 这句话就是获取了当前realm
对象的一个实例,其实实现就是拿到单例。所以我们每次在子线程里面不要再去读取我们自己封装持有的realm
实例了,直接调用系统的这个方法即可,能保证访问不出错。