屬性聲明方式
Class => T:Object
class Dog: Object {
// ... other property declarations
dynamic var owner: Person? // to-one relationships must be optional
}
class Person: Object {
// ... other property declarations
let dogs = List()
}
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
var owners: [Person] {
// Realm 並不會存儲這個屬性,因為這個屬性只定義了 getter
// 定義“owners”,和 Person.dogs 建立反向關系
return linkingObjects(Person.self, forProperty: "dogs")
}
}
添加索引屬性,加快查詢速度.
class Book: Object {
dynamic var price = 0
dynamic var title = ""
override static func indexedProperties() -> [String] {
return ["title"]
}
}
索引支持類型
strings integers booleans NSDate
Indexing a property will greatly speed up queries where the property is compared for equality (i.e. the = and IN operators), at the cost of slower insertions.
使用索引增加查詢速度的代價是插入數據時速度會降低
顧名思義當一個數據的內容改變時,它會自動更新該數據的所有實例化對象
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](#Realm notifications) 或 [key-value observation](#key-value observation)實現,後續會詳細描述這2個功能
聲明主鍵之後,對象將被允許查詢,更新速度更加高效,並且要求每個對象保持唯一性。
一旦帶有主鍵的對象被添加到 Realm 之後,該對象的主鍵將不可修改。
class Person: Object {
dynamic var id = 0
dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
重寫 Object.ignoredProperties() 可以防止 Realm 存儲數據模型的某個屬性。Realm 將不會干涉這些屬性的常規操作,它們將由成員變量(ivar)提供支持,並且您能夠輕易重寫它們的 setter 和 getter。
class Person: Object {
dynamic var tmpID = 0
var name: String { // read-only properties are automatically ignored
return "\(firstName) \(lastName)"
}
dynamic var firstName = ""
dynamic var lastName = ""
override static func ignoredProperties() -> [String] {
return ["tmpID"]
}
}
所有寫入操作(添加,修改,刪除)都必須依托一個write transaction.
由於write transaction會占用一定的資源,所以盡量精簡write transaction的個數.當隊列寫入時,只需要一個就write transaction
數據賦值方式:
// (1) Create a Dog object and then set its properties
var myDog = Dog()
myDog.name = "Rex"
myDog.age = 10
// (2) Create a Dog object from a dictionary
let myOtherDog = Dog(value: ["name" : "Pluto", "age": 3])
// (3) Create a Dog object from an array
let myThirdDog = Dog(value: ["Fido", 5])
嵌套賦值方式:
// Instead of using already existing dogs...
let aPerson = Person(value: ["Jane", 30, [aDog, anotherDog]])
// ...we can create them inline
let anotherPerson = Person(value: ["Jane", 30, [["Buster", 5], ["Buddy", 6]]])
write操作是阻塞操作,如果有一個寫操作,那麼其他線程的write操作都會被阻塞.
最好是將write操作放在一個單獨的線程中.
// Create a Person object
let author = Person()
author.name = "David Foster Wallace"
// 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(author)
}
// Update an object with a transaction
try! realm.write {
author.name = "Thomas Pynchon"
}
update:true Object必須具有PrimaryKeys.否則會報錯.
// Creating a book with the same primary key as a previously saved book
let cheeseBook = Book()
cheeseBook.title = "Cheese recipes"
cheeseBook.price = 9000
cheeseBook.id = 1
// Updating book with id = 1
try! realm.write {
realm.add(cheeseBook, update: true)
}
// Assuming a "Book" with a primary key of `1` already exists.
try! realm.write {
realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: true)
// the book's `title` property will remain unchanged.
}
let persons = realm.objects(Person)
try! realm.write {
persons.first?.setValue(true, forKeyPath: "isFirst")
// set each person's planet property to "Earth"
persons.setValue("Earth", forKeyPath: "planet")
}
// let cheeseBook = ... Book stored in Realm
try! realm.write {
// Delete an object with a transaction
realm.delete(cheeseBook)
realm.delete(List)
realm.delete(Results)
// Delete all objects from the realm
realm.deleteAll()
}
通過查詢操作,Realm 將會返回包含 Object 集合的Results實例。Results 的表現和 Array 十分相似,並且包含在 Results 中的對象能夠通過索引下標進行訪問。
所有的查詢(包括查詢和屬性訪問)在 Realm 中都是延遲加載的,只有當屬性被訪問時,才能夠讀取相應的數據。也就是說當沒有使用數據前,進行多次排序或者過濾都是不需要額外cpu時間的
查詢結構不是Copy對象,而是引用對象.所以在Write操作中修改查詢數據,是直接修改數據庫中的數據.
基本查詢語句
let dogs = realm.objects(Dog) // retrieves all Dogs from the default Realm
條件查詢
類似NSPredicate,同時支持NSPredicate.
// Query using a predicate string
var tanDogs = realm.objects(Dog).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).filter(predicate)
var tanDogs = realm.objects(Dog).filter("color = 'tan'").filter("name BEGINSWITH 'B'")
// Sort tan dogs with names starting with "B" by name
let sortedDogs = realm.objects(Dog).filter("color = 'tan' AND name BEGINSWITH 'B'").sorted("name")
//倒序
let sortedDogs = realm.objects(Dog).filter("color = 'tan' AND name BEGINSWITH 'B'").sorted("name" , ascending:false)
結果會自動更新
let puppies = realm.objects(Dog).filter("age < 2")
puppies.count // => 0
try! realm.write {
realm.create(Dog.self, value: ["name": "Fido", "age": 1])
}
puppies.count // => 1
// Loop through the first 5 Dog objects
// restricting the number of objects read from disk
let dogs = try! Realm().objects(Dog)
for i in 0..<5 {
let dog = dogs[i]
// ...
}
Realm.Configuration.defaultConfiguration = config.直接設置默認配置
假設需要快速切換賬戶,可以使用一下代碼
func setDefaultRealmForUser(username: String) {
var config = Realm.Configuration()
// Use the default directory, but replace the filename with the username
config.path = NSURL.fileURLWithPath(config.path!)
.URLByDeletingLastPathComponent?
.URLByAppendingPathComponent("\(username).realm")
.path
// Set this as the configuration used for the default Realm
Realm.Configuration.defaultConfiguration = config
}
指定BundleData中的Realm
let config = Realm.Configuration(
// Get the path to the bundled file
path: NSBundle.mainBundle().pathForResource("MyBundledData", ofType:"realm"),
// Open the file in read-only mode as application bundles are not writeable
readOnly: true)
// Open the Realm with the configuration
let realm = try! Realm(configuration: config)
// Read some data from the bundled Realm
let results = realm.objects(Dog).filter("age > 5")
如果是初始化一個Realm,指定的路徑必須是可寫的
內存中的Realms,沒有保存在磁盤上.
優點:可以快速的訪問數據,而不需要考慮數據持久化的性能開銷.內存Realms只會在temp路徑裡存放幾個文件,用來進行線程間數據同步,不會將Realms中任何數據寫入磁盤中
由於ARC的原因,內存Realms創建的數據必須要有一個強引用,否則會被回收
錯誤只會發生在第一次創建Realms.如果Realms已經創建,以後不會發生錯誤.
do {
let realm = try Realm()
} catch let error as NSError {
// handle error
}
數據拷貝只能是不同Realms都在同一線程中創建的,否則無法實現數據拷貝
realm.create(MyObjectSubclass.self, value: originalObjectInstance)
Realms內部處理的輔助文件,對於使用者來說,就是匯報bug的時候,需要一並提交這些文件
.realm.lock - A lock file for resource locks. .realm.log_a, .realm.log_b - Log files for transaction logs. .realm.note - A named pipe for notifications.Realms可以配置只保存特定的Class,除指定的Class外,其他Class一律不存儲.
let config = Realm.Configuration(objectTypes: [MyClass.self, MyOtherClass.self])
let realm = try! Realm(configuration: config)
刪除本地Realm
Realm在使用的時候,都是強引用,如果需要的話,就用autoreleasepool來包含
如果使用強引用,直接刪除也不會有什麼影響
autoreleasepool {
// all Realm usage here
}
let manager = NSFileManager.defaultManager()
let realmPath = Realm.Configuration.defaultConfiguration.path as! NSString
let realmPaths = [
realmPath as String,
realmPath.stringByAppendingPathExtension("lock")!,
realmPath.stringByAppendingPathExtension("log_a")!,
realmPath.stringByAppendingPathExtension("log_b")!,
realmPath.stringByAppendingPathExtension("note")!
]
for path in realmPaths {
do {
try manager.removeItemAtPath(path)
} catch {
// handle error
}
}
這章主要是說如何使用IOS本地加密,詳情查看官方文檔.
這一章主要是講多線程開發,大量寫入事務最好是放在其他線程中,以防止UI線程被阻塞
只在一個線程中處理所有事情,不需要擔心並發和多線程.(然並卵的話)
Realm在多線程處理上不需要使用線程鎖,只需要注意寫入操作需要在Write事件中.
Realm為了更好的支持多線程處理,它為每個線程都創建了一個視圖(SQL中的視圖概念??).由於每個線程都有自己的snapshots,導致線程之間同步問題.
唯一需要記住的是:你不能在多個線程之間共享同一個Realm對象.如果這樣的話,就會導致一個線程上修改了數據,其他線程無法同步數據.
在其他類型的線程上操作,都是基於Snapshots.
所以最好的處理方法是,保存唯一的一個視圖,這樣就不用擔心多線程並發的問題.
UI線程或者其他添加Runloop的線程上,數據都會自動刷新,除非將Realm.autorefresh設置為NO
其他類型的線程,都是以最後一次修改成功的Realm為snapshot,除非是手動refresh
Realm.commitWrite後,Realm會刷新一次
最好是不要經常性的手動調用refresh(),當你正在刷新,其他線程有其他事務進行處理時,會導致數據”pinned”,進而增大Realm在磁盤上的空間
```
###Passing Instances Across Threads
繼承於NSObject的類,是可以在線程之間傳遞的.
繼承於Realm, Object, Results, or List的類,是無法在線程之間傳遞的.否則會引起崩潰.
多線程之間傳遞數據的解決方案:
* Object:可以通過primary key來實現.
* Results:可以filter或者NSPredicate來實現.
**Realm一些可以多線程操作的屬性和方法,如下**
* Realm: all properties, class methods, and initializers.
* Object: invalidated, objectSchema, realm, class methods, and initializers.
* Results: objectClassName and realm.
* List: invalidated, objectClassName, and realm.
###Using a Realm Across Threads
**想要在不同線程中,訪問同一個Realm文件,就必須要在各自線程中獲取相同配置的Realm實例.就是重新調用Realm.realm().**
let queue = dispatch_queue_create(“test”, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue) {
autoreleasepool {
// Get realm and table instances for this thread
let realm = try! Realm()
// Break up the writing blocks into smaller portions
// by starting a new transaction
for idx1 in 0..<1000 {
realm.beginWrite()
// Add row via dictionary. Property order is ignored.
for idx2 in 0..<1000 {
realm.create(Person.self, value: [
"name": "\(idx1)",
"birthdate": NSDate(timeIntervalSince1970: NSTimeInterval(idx2))
])
}
// Commit the write transaction
// to make this data available to other threads
try! realm.commitWrite()
}
}
}
##JSON
不支持json數據直接導入,但是支持 NSJSONSerialization.JSONObjectWithData(_:options:)轉換的導入.
// A Realm Object that represents a city
class City: Object {
dynamic var city = “”
dynamic var id = 0
// other properties left out …
}
// Insert from NSData containing JSON
try! realm.write {
let json = try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
realm.create(City.self, value: json, update: true)
}
######注意事項
* float properties should be initialized with float-backed NSNumbers(float類型需要使用NSNumber來聲明)* * * * * * * *
* NSDate and NSData properties cannot be automatically inferred from strings, but should be converted to the appropriate type before passing to Realm().create(_:value:update:).(**沒理解**)
* If a JSON null (i.e. NSNull) is supplied for a required property, an exception will be thrown.(如果一個json對象為null,會拋出異常)
* If no property is supplied on insert for a required property, an exception will be thrown.(如果一個聲明屬性,沒有對應的值,會拋出異常)
* Realm will ignore any properties in the JSON not defined by the Object.(當Json對象中有Object沒有聲明變量,會忽略)
##Notifications
多線程中對特定數據有修改時,會發送Notifications.
**需要注意的是,addNotificationBlock返回的Token是必須要被強引用的,否則無法回調**
支持的調用方式:
* Realm.addNotificationBlock(_:)
* AnyRealmCollection.addNotificationBlock(_:)
* Results.addNotificationBlock(_:)
* List.addNotificationBlock(_:)
* NotificationToken.stop()
// Observe Realm Notifications
let token = realm.addNotificationBlock { notification, realm in
viewController.updateUI()
}
// later
token.stop()
// Observe Results Notifications
let token = realm.objects(Person).filter(“age > 5”).addNotificationBlock { results, error in
// results is identical to ‘realm.objects(Person).filter(“age > 5”)’
viewController.updateUI()
}
// later
token.stop()
##Key-Value Observation
**這章略過,由於不熟悉KVO,所以先不學習這章**
Realm objects are Key-Value Observing compliant for most properties. All persisted (non-ignored) properties on your Object subclasses are KVO-compliant, along with the invalidated property on Object and List.
Observing properties of standalone instances of Object subclasses works just like with any other dynamic property, but note that you cannot add an object to a Realm (with realm.add(obj) or other similar methods) while it has any registered observers.
Observing properties of persisted objects works a little differently. With persisted objects, there are three times when the value of a property may change: when you directly assign to it; when you call realm.refresh() or the Realm is automatically refreshed after a write transaction is committed on a different thread; and when you call realm.beginWrite() after changes on a different thread which have not been picked up by a refresh on the current thread.
In the latter two cases, all of the changes made in the write transaction(s) on another thread will be applied at once, and KVO notifications will all be sent at once. Any intermediate steps are discarded, so if in the write transaction you incremented a property from one to ten, on the main thread you’ll get a single notification of a change directly from one to ten. Because properties can change in value when not in a write transaction or even as part of beginning a write transaction, trying to modify persisted Realm objects from within observeValueForKeyPath(_:ofObject:change:context:) is not recommended.
Unlike NSMutableArray properties, observing changes made to List properties does not require using mutableArrayValueForKey(_:), although that is supported for compatiblity with things not written for Realm. Instead, you can simply call the modification methods on List directly, and anyone observing the property it is stored in will be notified. List properties do not need to be marked as dynamic to be observable, unlike normal properties.
In our example apps you can find a short example of using Realm with ReactiveCocoa from Objective?C, and ReactKit from Swift.
##Migrations
**數據遷移,版本迭代時,數據庫常用**
###為什麼要進行數據庫遷移
class Person: Object {
dynamic var firstName = “”
dynamic var lastName = “”
dynamic var age = 0
}
在某個版本更新中,變成了下邊這樣
class Person: Object {
dynamic var fullName = “”
dynamic var age = 0
}
那麼就需要用到數據遷移了.
###Performing a Migration
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)”
})
}
};
###Adding more versions
Realm.Configuration.defaultConfiguration.schemaVersion = 2;
Realm.Configuration.defaultConfiguration.migrationBlock = {migration, oldSchemaVersion in
if oldSchemaVersion < 10 {
migration.enumerate(Person.className(), { (oldObject, newObject) in
if oldSchemaVersion < 1 {
let firstName = oldObject![“firstName”] as! String
let lastName = oldObject![“lastName”] as! String
newObject![“fullName”] = “(firstName) (lastName)”
}
// Add the `email` property to Realms with a schema version of 0 or 1
if oldSchemaVersion < 2 {
newObject!["email"] = ""
}
})
}
};
###Linear Migrations
需要考慮跨版本的數據庫遷移,例如v0直接升級到v3版本,而不是只考慮v2升級到v3.
##Encryption
**Realm的加密只支持OS X , IOS , WatchKit.但是不支持watchOS**
Realm的加密方式為:**key為64字節,AES-256+SHA2**
**加密過的 Realm 只會帶來很少的額外資源占用(通常最多只會比平常慢10%)**
**注:如果數據庫加密後,由於不知道加密方式,即使有原始key,也無法獲取解密key,所以無法用Realm Browser查看.**
**注:如果數據庫加密,每次獲取Realm實例時,必須使用encryptionKey.**
func getKey() -> NSData {
// Identifier for our keychain entry - should be unique for your application
let keychainIdentifier = "io.Realm.Test"
let keychainIdentifierData = keychainIdentifier.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// First check in the keychain for an existing key
var query: [NSString: AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keychainIdentifierData,
kSecAttrKeySizeInBits: 512,
kSecReturnData: true
]
// To avoid Swift optimization bug, should use withUnsafeMutablePointer() function to retrieve the keychain item
// See also: http://stackoverflow.com/questions/24145838/querying-ios-keychain-using-swift/27721328#27721328
var dataTypeRef: AnyObject?
var status = withUnsafeMutablePointer(&dataTypeRef) { SecItemCopyMatching(query, UnsafeMutablePointer($0)) }
if status == errSecSuccess {
return dataTypeRef as! NSData
}
// No pre-existing key from this application, so generate a new one
let keyData = NSMutableData(length: 64)!
let result = SecRandomCopyBytes(kSecRandomDefault, 64, UnsafeMutablePointer(keyData.mutableBytes))
assert(result == 0, "Failed to get random bytes")
// Store the key in the keychain
query = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keychainIdentifierData,
kSecAttrKeySizeInBits: 512,
kSecValueData: keyData
]
status = SecItemAdd(query, nil)
assert(status == errSecSuccess, "Failed to insert the new key in the keychain")
return keyData
}