Realm Swift
version 0.98.7
Realm支持类型
- String,NSString
- Int
- Int8,Int16,Int32,Int64
- Float
- Double
- Bool
- NSData
- NSDate
- RealmOptional
- Object
- List
Realm Object
Model Properties
属性声明方式
Class => T:Object
Type | Non-optional | Optional |
---|---|---|
声明类型 | 非可选类型声明方式 | 可选类型声明方式 |
Bool | dynamic var value = false | let value = RealmOptional() |
Int | dynamic var value = 0 | let value = RealmOptional() |
Float | dynamic var value:Float = 0.0 | let value = RealmOptional() |
Double | dynamic var value:Double = 0 | let value = RealmOptional() |
String | dynamic var value = “” | dynamic var value: String? = nil |
Data | dynamic var value = Data() | dynamic var value: NSData? = nil |
Date | dynamic var value = Date() | dynamic var value: Date? = nil |
Object | n/a: must be optional | dynamic var value: Class? |
List | let value = List | n/a: must be non-optional |
注意事项:
- Object只能声明成可选类型.
- List和RealmOptional只能声明成非可选类型.
- 使用var修饰变量,必须要使用dynamic.具体原因请查照)
Relationships
一对一(To-One Relationships)
class Dog: Object {
// ... other property declarations
dynamic var owner: Person? // to-one relationships must be optional
}
一对多(To-Many Relationships)
class Person: Object {
// ... other property declarations
let dogs = List<Dog>()
}
反向链接(Inverse Relationships / backlink)
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
var owners: [Person] {
// Realm 并不会存储这个属性,因为这个属性只定义了 getter
// 定义“owners”,和 Person.dogs 建立反向关系
return linkingObjects(Person.self, forProperty: "dogs")
}
}
Indexed Properties
添加索引属性,加快查询速度.
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.
使用索引增加查询速度的代价是插入数据时速度会降低
Auto-Updating Objects
顾名思义当一个数据的内容改变时,它会自动更新该数据的所有实例化对象
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个功能
Primary Keys
声明主键之后,对象将被允许查询,更新速度更加高效,并且要求每个对象保持唯一性。
一旦带有主键的对象被添加到 Realm 之后,该对象的主键将不可修改。
class Person: Object {
dynamic var id = 0
dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
Ignored Properties
重写 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"]
}
}
Writes
所有写入操作(添加,修改,删除)都必须依托一个write transaction.
由于write transaction会占用一定的资源,所以尽量精简write transaction的个数.当队列写入时,只需要一个就write transaction
Creating Objects
数据赋值方式:
// (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])
Nested Objects
嵌套赋值方式:
// 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]]])
Adding Objects
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)
}
Updating Objects
Typed Updates
// Update an object with a transaction
try! realm.write {
author.name = "Thomas Pynchon"
}
Creating and Updating Objects With Primary Keys
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.
}
Key-Value Coding
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")
}
Deleting Objects
// let cheeseBook = ... Book stored in Realm
try! realm.write {
// Delete an object with a transaction
realm.delete(cheeseBook)
realm.delete(List<T:Object>)
realm.delete(Results<T:Object>)
// Delete all objects from the realm
realm.deleteAll()
}
Queries
通过查询操作,Realm 将会返回包含 Object 集合的Results实例。Results 的表现和 Array 十分相似,并且包含在 Results 中的对象能够通过索引下标进行访问。
所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。也就是说当没有使用数据前,进行多次排序或者过滤都是不需要额外cpu时间的
查询结构不是Copy对象,而是引用对象.所以在Write操作中修改查询数据,是直接修改数据库中的数据.
基本查询语句
let dogs = realm.objects(Dog) // retrieves all Dogs from the default Realm
Filtering
条件查询
类似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‘")
支持的断言类型
- 比较操作数(comparison operand)可以是属性名称或者某个常量,但至少有一个操作数必须是属性名称;
- 比较操作符 ==、<=、<、>=、>、!=, 以及 BETWEEN 支持 int、long、long long、float、double 以及 NSDate 属性类型的比较,比如说 age == 45
- 相等比较 ==以及!=,比如说Results().filter(“company == %@”, company)
- 比较操作符 == and != 支持布尔属性
- 对于 NSString 和 NSData 属性来说,我们支持 ==、!=、BEGINSWITH、CONTAINS 以及 ENDSWITH 操作符,比如说 name CONTAINS ‘Ja’
- 字符串支持忽略大小写的比较方式,比如说 name CONTAINS[c] ‘Ja’ ,注意到其中字符的大小写将被忽略
- **Realm 支持以下复合操作符:“AND”、“OR” 以及 “NOT”。比如说 name BEGINSWITH ‘J’ AND age >= 32;
包含操作符 IN,比如说 name IN {‘Lisa’, ‘Spike’, ‘Hachi’}**
- ==、!=支持与 nil 比较,比如说 Results().filter(“ceo == nil”)。注意到这只适用于有关系的对象,这里 ceo 是 Company 模型的一个属性
- ANY 比较,比如说 ANY student.age < 21
- 支持复合表达式类型@count, @min, @max, @sum and @avg.例如realm.objects(Company).filter(“[email protected] > 5”)
- 子查询有如下限制
-
- @count is the only operator that may be applied to the SUBQUERY expression
-
- The SUBQUERY(…)[email protected] expression must be compared with a constant
-
- Correlated subqueries are not yet supported
Sorting
// 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)
Auto-Updating Results
结果会自动更新
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
Limiting Results
// 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]
// ...
}
Realms
Realm Configuration
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
}
Other Realms
指定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,指定的路径必须是可写的
In-Memory Realms
内存中的Realms,没有保存在磁盘上.
优点:可以快速的访问数据,而不需要考虑数据持久化的性能开销.内存Realms只会在temp路径里存放几个文件,用来进行线程间数据同步,不会将Realms中任何数据写入磁盘中
注意
由于ARC的原因,内存Realms创建的数据必须要有一个强引用,否则会被回收
Error Handling
错误只会发生在第一次创建Realms.如果Realms已经创建,以后不会发生错误.
do {
let realm = try Realm()
} catch let error as NSError {
// handle error
}
Copying Objects Between Realms
数据拷贝只能是不同Realms都在同一线程中创建的,否则无法实现数据拷贝
realm.create(MyObjectSubclass.self, value: originalObjectInstance)
Auxiliary Realm Files
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.
Class Subsets
Realms可以配置只保存特定的Class,除指定的Class外,其他Class一律不存储.
let config = Realm.Configuration(objectTypes: [MyClass.self, MyOtherClass.self])
let realm = try! Realm(configuration: config)
Deleting Realm Files
删除本地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
}
}
Using Realm with Background App Refresh
这章主要是说如何使用IOS本地加密,详情查看官方文档.
Threading
这一章主要是讲多线程开发,大量写入事务最好是放在其他线程中,以防止UI线程被阻塞
只在一个线程中处理所有事情,不需要担心并发和多线程.(然并卵的话)
Realm在多线程处理上不需要使用线程锁,只需要注意写入操作需要在Write事件中.
Realm为了更好的支持多线程处理,它为每个线程都创建了一个视图(SQL中的视图概念??).由于每个线程都有自己的snapshots,导致线程之间同步问题.
唯一需要记住的是:你不能在多个线程之间共享同一个Realm对象.如果这样的话,就会导致一个线程上修改了数据,其他线程无法同步数据.
Seeing Changes From Other Threads
在UI线程或者其他添加Runloop的线程上,Realm都会自动更新其他线程Runloop的操作结果.(这里是说其他线程有更新,UI线程或Runloop线程都不会更新数据)
在其他类型的线程上操作,都是基于Snapshots.
所以最好的处理方法是,保存唯一的一个视图,这样就不用担心多线程并发的问题.
UI线程或者其他添加Runloop的线程上,数据都会自动刷新,除非将Realm.autorefresh设置为NO
其他类型的线程,都是以最后一次修改成功的Realm为snapshot,除非是手动refresh
Realm.commitWrite后,Realm会刷新一次
最好是不要经常性的手动调用refresh(),当你正在刷新,其他线程有其他事务进行处理时,会导致数据”pinned”,进而增大Realm在磁盘上的空间
```
###Passing Instances Across Threads
继承于NSObject的类,是可以在线程之间传递的.<BR>
继承于Realm, Object, Results, or List的类,是无法在线程之间传递的.否则会引起崩溃.<BR>
多线程之间传递数据的解决方案:<BR>
* 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().**<BR>
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:)转换的导入.<BR>
// 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.<BR>
**需要注意的是,addNotificationBlock返回的Token是必须要被强引用的,否则无法回调**<BR>
支持的调用方式:
* 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,所以先不学习这章**<BR>
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
**数据迁移,版本迭代时,数据库常用**<BR>
###为什么要进行数据库迁移
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**<BR>
Realm的加密方式为:**key为64字节,AES-256+SHA2**<BR>
**加密过的 Realm 只会带来很少的额外资源占用(通常最多只会比平常慢10%)**<BR>
**注:如果数据库加密后,由于不知道加密方式,即使有原始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<UInt8>(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
}
“`