Realm 是一款專為移動端設計的高性能數據庫框架,其 Swift 版本提供了類型安全、響應式的數據持久化方案。本文系統梳理 Realm Swift 的核心特性,涵蓋數據建模、關聯關係、查詢優化、線程處理等關鍵技術點,幫助開發者快速掌握最佳實踐。
Realm Swift 原生支援以下基礎類型:
String、NSStringInt、Int8、Int16、Int32、Int64Float、DoubleBoolNSData、NSDateRealmOptional、Object、List所有模型類必須繼承 Object,屬性聲明需遵循特定規範:
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
let owner = LinkingObjects(fromType: Person.self, property: "dogs")
}
| 類型 | 非可選聲明 | 可選聲明 |
|---|---|---|
| Bool | dynamic var value = false | let value = RealmOptional<Bool>() |
| Int | dynamic var value = 0 | let value = RealmOptional<Int>() |
| String | dynamic var value = "" | dynamic var value: String? = nil |
| Object | 不支援 | dynamic var value: Class? |
| List | let value = List<Type>() | 必須非可選 |
關鍵規則: Object 僅能聲明為可選;List 和 RealmOptional 必須非可選;所有 var 屬性必須標註 dynamic。
class Dog: Object {
dynamic var owner: Person? // 必須可選
}
class Person: Object {
let dogs = List<Dog>()
}
class Dog: Object {
var owners: [Person] {
return linkingObjects(Person.self, forProperty: "dogs")
}
}
透過重寫 indexedProperties() 為頻繁查詢的屬性添加索引:
class Book: Object {
dynamic var price = 0
dynamic var title = ""
override static func indexedProperties() -> [String] {
return ["title"]
}
}
支援索引的類型:字符串、整數、布爾值、NSDate。權衡: 索引大幅提升等值查詢(=、IN)速度,但會降低插入性能。
Realm 物件具備即時更新特性:
let myDog = Dog()
myDog.name = "Fido"
myDog.age = 1
try! realm.write { realm.add(myDog) }
let myPuppy = realm.objects(Dog).filter("age == 1").first
try! realm.write { myPuppy!.age = 2 }
print("age of my dog: \(myDog.age)") // 輸出 2
配合 Realm Notifications 或 KVO 可實現 UI 自動刷新。
class Person: Object {
dynamic var id = 0
dynamic var name = ""
dynamic var tmpID = 0
override static func primaryKey() -> String? {
return "id"
}
override static func ignoredProperties() -> [String] {
return ["tmpID"]
}
var fullName: String {
return "\(firstName) \(lastName)" // 唯讀屬性自動忽略
}
}
主鍵一旦寫入不可修改,且隱含唯一性約束。
所有寫操作(增、刪、改)必須在 write transaction 中執行。 為減少資源佔用,應合併事務、避免頻繁提交。
// 方式一:逐屬性賦值
var myDog = Dog()
myDog.name = "Rex"
myDog.age = 10
// 方式二:字典初始化
let myOtherDog = Dog(value: ["name": "Pluto", "age": 3])
// 方式三:數組初始化
let myThirdDog = Dog(value: ["Fido", 5])
let anotherPerson = Person(value: ["Jane", 30, [["Buster", 5], ["Buddy", 6]]])
try! realm.write {
realm.add(cheeseBook, update: true) // 需主鍵存在
realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: true)
}
Realm 查詢返回 Results 集合,特性如下:
// 字符串 predicate
var tanDogs = realm.objects(Dog).filter("color = 'tan' AND name BEGINSWITH 'B'")
// NSPredicate
let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
tanDogs = realm.objects(Dog).filter(predicate)
// 鏈式過濾
tanDogs = realm.objects(Dog).filter("color = 'tan'").filter("name BEGINSWITH 'B'")
==、<=、<、>=、>、!=、BETWEEN(數值、日期)==、!=、BEGINSWITH、CONTAINS、ENDSWITH,支援 [c] 忽略大小寫AND、OR、NOTIN、ANY@count、@min、@max、@sum、@avglet sortedDogs = realm.objects(Dog)
.filter("color = 'tan' AND name BEGINSWITH 'B'")
.sorted("name", ascending: false)
// 限制結果數量(惰性加載無需 LIMIT)
let dogs = try! Realm().objects(Dog)
for i in 0..<5 { let dog = dogs[i] }
func setDefaultRealmForUser(username: String) {
var config = Realm.Configuration()
config.path = NSURL.fileURLWithPath(config.path!)
.URLByDeletingLastPathComponent?
.URLByAppendingPathComponent("\(username).realm")
.path
Realm.Configuration.defaultConfiguration = config
}
let config = Realm.Configuration(
path: NSBundle.mainBundle().pathForResource("MyBundledData", ofType: "realm"),
readOnly: true
)
let realm = try! Realm(configuration: config)
適用於臨時數據、測試場景,無磁盤 I/O 開銷。注意: 必須保持強引用防止 ARC 回收。
核心原則: 不能跨線程共享 Realm、Object、Results、List 實例。
Object:透過主鍵重新查詢Results:重新構建 filter/NSPredicateautorefresh = false)refresh()commitWrite() 會觸發一次刷新let queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT)
dispatch_async(queue) {
autoreleasepool {
let realm = try! Realm()
for idx1 in 0..<1000 {
realm.beginWrite()
for idx2 in 0..<1000 {
realm.create(Person.self, value: [
"name": "\(idx1)",
"birthdate": NSDate(timeIntervalSince1970: NSTimeInterval(idx2))
])
}
try! realm.commitWrite()
}
}
}
不支援直接導入,需先轉換:
try! realm.write {
let json = try! NSJSONSerialization.JSONObjectWithData(data, options: [])
realm.create(City.self, value: json, update: true)
}
注意事項: Float 需用 NSNumber;NSDate/NSData 需預先轉換;JSON null 會導致必填字段異常;未定義屬性會被忽略。
支援 Realm、Results、List、AnyRealmCollection 級別通知,Token 必須強引用持有:
let token = realm.addNotificationBlock { notification, realm in
viewController.updateUI()
}
token.stop()
Schema 版本升級時必須提供遷移塊:
Realm.Configuration.defaultConfiguration.schemaVersion = 2
Realm.Configuration.defaultConfiguration.migrationBlock = { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
migration.enumerate(Person.className()) { oldObject, newObject in
let firstName = oldObject!["firstName"] as! String
let lastName = oldObject!["lastName"] as! String
newObject!["fullName"] = "\(firstName) \(lastName)"
}
}
if oldSchemaVersion < 2 {
newObject!["email"] = ""
}
}
線性遷移原則: 需處理跨版本升級(如 v0→v3),而非僅相鄰版本。
支援 AES-256 + SHA2(64 字節密鑰),性能損耗 <10%。加密後無法用 Realm Browser 查看,每次開啟需提供 encryptionKey。密鑰建議存儲於 Keychain。