Realm for iOS

Realm 是除了 CoreDataSqlite 之外的第三个选择,一个近几年移动端数据库的全新方案,一直保持着活跃的更新,而且引起了iOS开发圈广泛的关注。它的使用上也比 CoreData 简单方便,更重要的是快,性能更好。


Using the Realm framework

初始化和配置 realm

1
2
3
4
5
6
7
8
let realm = try! Realm()

// Get our Realm file's parent directory
let folderPath = realm.configuration.fileURL!.deletingLastPathComponent().path

// Disable file protection for this directory
try! FileManager.default.setAttributes([FileAttributeKey(rawValue: NSFileProtectionKey): NSFileProtectionNone],
ofItemAtPath: folderPath)

可以为每个登录的用户设置独立的数据库

1
2
3
4
5
6
7
8
9
func setDefaultRealmForUser(username: String) {
var config = Realm.Configuration()

// Use the default directory, but replace the filename with the username
config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("\(username).realm")

// Set this as the configuration used for the default Realm
Realm.Configuration.defaultConfiguration = config
}

realm 对数据库的操作都是在 model 层,并且自动更新。

Models

基于 Realmmodel 都必须继承自 Object

Realm 支持的属性字段类型包括: Bool, Int, Int8, Int16, Int32, Int64, Double, Float, String, Date, and Data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person: Object {
// Optional string property, defaulting to nil
@objc dynamic var name: String? = nil

// Optional int property, defaulting to nil
// RealmOptional properties should always be declared with `let`,
// as assigning to them directly will not work as desired
let age = RealmOptional<Int>()
}

let realm = try! Realm()
try! realm.write() {
var person = realm.create(Person.self, value: ["Jane", 27])
// Reading from or modifying a `RealmOptional` is done via the `value` property
person.age.value = 28
}

添加主键,可以指定某个属性字段为主键,但必须唯一。

1
2
3
4
5
6
7
8
class Person: Object {
@objc dynamic var id = 0
@objc dynamic var name = ""

override static func primaryKey() -> String? {
return "id"
}
}

添加索引,可以查询速度

1
2
3
4
5
6
7
8
class Book: Object {
@objc dynamic var price = 0
@objc dynamic var title = ""

override static func indexedProperties() -> [String] {
return ["title"]
}
}

Relationship

realm 中我们可以指定 Realm objects 之间的关系,这样方便关联查询。

Many-to-one

多对一的关系

1
2
3
4
5
6
7
8
class Dog: Object {
// ... other property declarations
@objc dynamic var owner: Person? // to-one relationships must be optional
}

let jim = Person()
let rex = Dog()
rex.owner = jim

Many-to-many

多对多的关系, 通过 List 属性指定

1
2
3
4
5
6
7
8
class Person: Object {
// ... other property declarations
let dogs = List<Dog>()
}

let someDogs = realm.objects(Dog.self).filter("name contains 'Fido'")
jim.dogs.append(objectsIn: someDogs)
jim.dogs.append(rex)

Writes

对 Reaml objects 的所有写相关的操作都必须在 transaction 事务中进行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Get the default Realm
let realm = try! Realm()
// You only need to do this once (per thread)

// Add to the Realm inside a transaction
try! realm.write {
realm.add(myDog)
}

// Update an object with a transaction
try! realm.write {
author.name = "Thomas Pynchon"
}

// Create or Insert an record
try! realm.write {
realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: true)
}

// Delete an object with a transaction
try! realm.write {
realm.delete(cheeseBook)
}

Queries

通过 Realm, 我们可以很方便的查询数据库

1
2
// retrieves all Dogs from the default Realm
let dogs = realm.objects(Dog.self)

通过指定查询条件查询

1
2
3
4
5
6
// Query using a predicate string
var tanDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'")

// Query using an NSPredicate
let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
tanDogs = realm.objects(Dog.self).filter(predicate)

还可以通过指定 keyPath 对查询结果排序

1
2
3
4
5
6
7
8
9
10
11
lass 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")

Migrations

Realm 数据库迁移也比较方便,在 Realm.Configuration 中配置 migrationBlock 就可以手动或自动迁移

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
// Inside your application(application:didFinishLaunchingWithOptions:)

let config = Realm.Configuration(
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
schemaVersion: 1,

// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// The enumerateObjects(ofType:_:) method iterates
// over every Person object stored in the Realm file
migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in
// combine name fields into a single field
let firstName = oldObject!["firstName"] as! String
let lastName = oldObject!["lastName"] as! String
newObject!["fullName"] = "\(firstName) \(lastName)"
}
}
})

// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config

// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
let realm = try! Realm()

Realm notifications

Realm notifications 是一个消息通知机制,通过添加 realm.observe,当我们对数据库进行更新操作时,就会收到对应的消息。在这里就很做方便刷新 UI 等操作。

1
2
3
4
5
6
7
// Observe Realm Notifications
let token = realm.observe { notification, realm in
viewController.updateUI()
}

// later
token.invalidate()