Realm Swift

Realm Swift

当前这个翻译,主要是方便我自己查阅api,有非常多地方写的比較晦涩或者没有翻译,敬请谅解
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>()
}
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(“employees.@count > 5”)
  • 子查询有例如以下限制
    • @count is the only operator that may be applied to the SUBQUERY expression
    • The SUBQUERY(…).@count 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
}

“`

posted @ 2018-04-16 15:56  zhchoutai  阅读(738)  评论(0编辑  收藏  举报