RealmSwift Reading Objects

Reading Objects

Fetching all objects of a type

  • Realm.objects(_) 用于从Realm获取对象的API是Realm.objects(_)。它期望的参数是您想要获取的Realm对象的类型。
Example.of("Getting All Objects") {
  let people = realm.objects(Person.self)
  let articles = realm.objects(Article.self)

  print("\(people.count) people and \(articles.count) articles")
}

Fetching an object by its primary key

  • Realm.object(ofType:forPrimaryKey:) 查找主键与提供的键匹配的对象,如果没有找到匹配,则返回nil。
Example.of("Getting an Object by Primary Key") {
  let person = realm.object(ofType: Person.self,
    forPrimaryKey: "test-key")

  if let person = person {
    print("Person with Primary Key 'test-key': \(person.firstName)")
  } else {
    print("Not found")
  }
}

Accessing objects in a result set

  • realm.objects(T.self): return result set
Example.of("Accessing Results") {
  let people = realm.objects(Person.self)
}

print("Realm contains \(people.count) people")
print("First person is: \(people.first!.fullName)")
print("Second person is: \(people[1].fullName)")
print("Last person is: \(people.last!.fullName)")
  • Results还提供了更多访问元素的方法——例如,您可以使用集合方法,如map。在最后一个闭包中附加以下代码以打印每个人的名字:
let firstNames = people.map { $0.firstName }
  .joined(separator: ", ")
print("First names of all people are: \(firstNames)")

您还可以获得一个枚举器来遍历元素。在闭包中添加:

let namesAndIds = people.enumerated()
  .map { "\($0.offset): \($0.element.firstName)" }
  .joined(separator: ", ")
print("People and indexes: \(namesAndIds)")

//First names of all people are: Klark, John, Jane, Boe, Frank
//People and indexes: 0: Klark, 1: John, 2: Jane, 3: Boe, 4: Frank

Results indexes

如果在people集合中找到person, index(of:)返回其在集合中的索引;否则,它将返回nil。

Example.of("Results Indexes") {
    let people = realm.objects(Person.self)
    let person = people[1]
    if let index1 = people.index(of: person) {
        print("\(person.fullName) is at index \(index1)")
    }
}

此外,如果您需要找到匹配某些自定义需求的特定对象,您可以使用index(where:)来查找第一个匹配。添加:

if let index2 = people.index(where: { $0.firstName.starts(with: "J") }) {
  print("Name starts with J at index \(index2)")
}

index(where:) 提供了一个闭包,您可以在其中定义匹配条件。从这个意义上说,index(where:)是filter(_)的特化,不同之处在于它返回一个索引,并且只返回第一个匹配元素的索引。

最后,您可以使用NSPredicate而不是自定义闭包。该方法有两种变体:前者,index(matching:),接受一个NSPpredicate 参数,而后者接受一个 predicate 格式字符串和一个可变参数列表:。

if let index3 = people.index(matching: "hairCount < %d", 10000) {
  print("Person with less than 10,000 hairs at index \(index3)")
}

ℹ️ Results indexes:
—————————————————————
John Smith is at index 1
Name starts with J at index 1
Person with less than 10,000 hairs at index 1

Filtering results

与前面一样,people是保存在Realm中的所有Person对象的集合。

但是,在访问其元素之前,集合不做任何事情。因此,您可以免费筛选集合——在访问数据之前,不会加载任何内容!

Example.of("Filtering") {
  let people = realm.objects(Person.self)
  print("All People: \(people.count)")
  let living = realm.objects(Person.self).filter("deceased = nil")
  print("Living People: \(living.count)")
}

ℹ️ Filtering:
——————————————
All People: 5
Living People: 4

Looks like filter discarded one of the objects according to your predicate!

还有一种filter(_)的变体,它不是格式化字符串,而是接受NSPredicate对象,提供更安全、更健壮的过滤。添加到最后一个例子中:

let predicate = NSPredicate(
  format: "hairCount < %d AND deceased = nil", 1000)
let balding = realm.objects(Person.self).filter(predicate)
print("Likely balding living people: \(balding.count)")

您可以使用您所知道的大多数NSPredicate语法,但不幸的是,它并不支持所有语法。不用担心,下一节将提供一个方便的NSPredicate备忘单,您可以使用它作为快速参考。

如果您以前使用过NSPredicate,或者您只是很自然地意识到代码的安全性,那么您可能已经感觉到使用字符串查询数据库是容易出错的。 您可能会输入错误的属性名称,或者无意中使用了错误的查询。

您可以使用一个伟大的开放源码库,以一种语义的、类型安全的方式构建NSPredicate。然而,在本节中,您将把查询包装在一个方法中,以使代码更安全、更可读。

就在当前示例代码之前,在Person对象上添加一个新的扩展:

extension Person {
  static let fieldHairCount = "hairCount"
  static let fieldDeceased = "deceased"

  static func allAliveLikelyBalding(
    `in` realm: Realm, hairThreshold: Int = 1000) -> Results<Person> {

    let predicate = NSPredicate(format: "%K < %d AND %K = nil",
      Person.fieldHairCount, hairThreshold, Person.fieldDeceased)
    return realm.objects(Person.self).filter(predicate)
  }
}

allAliveLikelyBalding(in:hairthreshold:)构建一个谓词并使用包含属性名的预定义常量。在后面的章节中,你会看到这些代码的一些变化;在任何情况下,您都应该尝试最适合您自己项目的安全样式。

现在,您可以将此代码添加到示例中,以尝试新的方法:

let baldingStatic = Person.allAliveLikelyBalding(in: realm)
print("Likely balding people (via static method): \(baldingStatic.count)")

More advanced predicates

In this section, you’ll try some more complex predicates with filter(_). Add this code to try a predicate matching a list of values:

Example.of("More Predicates") {
  let janesAndFranks = realm.objects(Person.self)
    .filter("firstName IN %@", ["Jane", "Frank"])
  print("There are \(janesAndFranks.count) people named Jane or Frank")
}

You just used the IN keyword to match firstName from a given list of values to find all people named Jane or Frank.

Next, you’ll try BETWEEN to match values in a given range. Add:

let balding = realm.objects(Person.self)
  .filter("hairCount BETWEEN {%d, %d}", 10, 10000)
print("There are \(balding.count) people likely balding")

Now try some string matching. Add inside your example closure:

let search = realm.objects(Person.self)
  .filter("""
          firstName BEGINSWITH %@ OR
          (lastName CONTAINS %@ AND hairCount > %d)
          """,
          "J", "er", 10000)
print("There are \(search.count) people matching our search")

Whoah, that one looks a bit scary. No worries, it’s pretty simple! You use a multi-line string literal to format your complex predicate format neatly. Everything between the opening and closing """ is considered a single piece of text.

Sub-query predicates

对于更复杂的谓词,您可以使用SUBQUERY谓词函数,该函数允许您在每个匹配原始谓词的对象上运行单独的谓词。 这有点让人抓狂,但坚持住!

Example.of("Subqueries") {
  let articlesAboutFrank = realm.objects(Article.self).filter("""
    title != nil AND
    people.@count > 0 AND
    SUBQUERY(people, $person,
      $person.firstName BEGINSWITH %@ AND
      $person.born > %@).@count > 0
    """, "Frank", Date.distantPast)
  print("There are \(articlesAboutFrank.count) articles about frank")
}

这个相当复杂的谓词的详细说明是:

  1. 标题不应该是nil。
  2. 人员列表上的聚合属性@count应该返回大于0的值,例如,人员列表不应该是空的。
  3. 最后,对到目前为止匹配条件的每个对象运行子谓词。

要执行子查询,可以使用SUBQUERY(people, person<predicate>)peopleperson, <predicate>)。第一个参数指定您在上面运行谓词的people集合,第二个参数指定一个变量名person,以便在该集合上循环时使用,第三个参数是一个谓词。下面是子查询的分解:

  1. 它匹配名字以弗兰克开头的人。
  2. 它适用于出生于Date.distantPast之后的人。
  3. 最后,通过对SUBQUERY(…)结果本身使用@count,它过滤掉了所有在子查询中没有任何匹配的对象。
ℹ️ Subqueries:
———————————————
There are 1 articles about frank

Predicates cheat-sheet

  1. Predicate replacements
  • [property == %d] filter("age == %d", 30) replaces %d with 30 and matches if column called 'property' is equal to 30.
  • [%K == %d] filter("%K == %d", "age", 30) replaces %K with 'age' and %d with 30 and matches if column 'age' equals 30.
  1. Comparison operators (abbrev.)
  • [==] filter("firstName == 'John'") matches values equal to.
  • [!=] filter("firstName != 'John'") matches values not equal to.
  • [>, >=] filter("age > 30"), filter("age >= 30") matches values greater than (or equal) to .
  • [<, <=] filter("age < 30"), filter("age <= 30") matches values less than (or equal) to.
  • [IN] filter("id IN [1, 2, 3]") matches value from a list of values.
  • [BETWEEN] filter("age BETWEEN {18, 68}") matches value in a range of values.
  1. Logic operators (abbrev.)
  • [AND] filter("age == 26 AND firstName == 'John'") matches only if both conditions are fulfilled.
  • [OR] filter("age == 26 OR firstName == 'John'") matches if any of the conditions are fulfilled.
  • [NOT] filter("NOT firstName == 'John'") matches if the conditions is not fulfilled.
  1. String operators
  • [BEGINSWITH] filter("firstName BEGINSWITH 'J'") matches if the firstName value starts with J.
  • [CONTAINS] filter("firstName CONTAINS 'J'") matches if the firstName value contains anywhere J.
  • [ENDSWITH] filter("lastName ENDSWITH 'er'") matches if the lastName value ends with er.
  • [LIKE] filter("lastName LIKE 'J*on'") matches if value starts with 'J', continues with any kind of sequence of symbols, and ends on 'on', e.g., Johnson and Johansson. In the search pattern a ? matches one symbol of any kind and * matches zero or more symbols.

以上所有操作符都可以用[c]和[d]进行修改,方法如下:CONTAINS[c]不区分大小写,CONTAINS[d]忽略变号(即e, è, ê, ë都被认为是同一个字符)。

  1. Aggregate operators and key-paths (abbrev.)
  • [ANY] filter("ANY people.firstName == 'Frank'") matches if at least one of the objects in the people list has a property firstName equal to 'Frank'.
  • [NONE] filter("NONE people.firstName == 'Frank'") matches if none of the objects in the people list has a property firstName equal to 'Frank'.
  • [@count] filter("people.@count == 2") matches objects whose people list contains exactly two elements.

Sorting results

唷!NSPredicate的高级语法确实让事情变得有些复杂。是时候回到Realm自己的api了,使用起来很有趣。

在本节中,您将研究sorted()方法的一些变体,它允许您对查询的结果进行排序。添加到你的操场:

Example.of("Sorting") {
  let sortedPeople = realm.objects(Person.self)
    .filter("firstName BEGINSWITH %@", "J")
    .sorted(byKeyPath: "firstName")

  let names = sortedPeople.map { $0.firstName }.joined(separator: ", ")
  print("Sorted people: \(names)")
}

Providing a property name to sorted(byKeyPath:) sorts the result set by the values for the given property. Realm sorts string properties alphabetically, number properties numerically and dates chronologically.

ℹ️ Sorting:
————————————
Sorted people: Jane, John

sorted() 接受第二个名为ascending的参数,该参数默认设置为true。这个订单的数量从小到大,日期从老到新等等。通过将ascending设置为false,可以将顺序颠倒为降序。在你现有的闭包中添加另一个例子来尝试一下:

let sortedPeopleDesc = realm.objects(Person.self)
  .filter("firstName BEGINSWITH %@", "J")
  .sorted(byKeyPath: "firstName", ascending: false)

let revNames = sortedPeopleDesc.map { $0.firstName }.joined(separator: ", ")
print("Reverse-sorted People: \(revNames)")

//Reverse-sorted People: John, Jane

注意,第一个参数,而不是一个property名,可以是一个keypath-也就是说,它可以引用对象链接上的一个属性。要尝试这个方法,请添加到你当前的例子中:

let sortedArticles = realm.objects(Article.self)
  .sorted(byKeyPath: "author.firstName")

print("Sorted articles by author: \n\(sortedArticles.map { "- \($0.author!.fullName): \($0.title!)" }.joined(separator: "\n"))")

这段代码对所有Article对象进行排序,不是根据它们自己的任何属性,而是根据链接作者的名字。

The list of articles sorted by the author’s first name is:

Sorted articles by author: 
- John Smith: Musem of Modern Art opens a Boe Carter retrospective curated by Jane Doe
- Klark Kent: Jane Doe launches a successful new product

最后,sorted(by:)允许您按多个keypaths对结果集进行排序。添加到你的例子:

let sortedPeopleMultiple = realm.objects(Person.self)
  .sorted(by: [
    SortDescriptor(keyPath: "firstName", ascending: true),
    SortDescriptor(keyPath: "born", ascending: false)
  ])

  print(sortedPeopleMultiple.map { "\($0.firstName) @ \($0.born)" }.joined(separator: ", "))

sorted(by:) 接受单个参数,该参数是SortDescriptors的集合。SortDescriptor是一种简单的包装器类型,它设置要排序的属性名以及顺序是升序还是降序。

The code above sorts the list of all Person objects:

  • 首先,他们的名字升序排列。
  • 然后,按他们的出生日期,同时保留按名字排序(例如,第二种排序规则只影响有相同名字的人)。
Boe @ 1970-01-01 13:53:20 +0000, Frank @ 1970-01-01 08:20:00 +0000, Jane @ 1970-01-01 13:53:20 +0000, John @ 2027-04-03 11:00:00 +0000, Klark @ 1970-01-01 00:00:00 +0000

Live results

在继续编写事务和向Realm添加对象之前,还需要研究Results类的最后一个方面。Realm result sets 总是返回最新的数据。结果中的数据永远不会过时。

这意味着您永远不必从磁盘重新加载结果或以某种方式手动刷新内存数据。这个概念对您来说可能不是很明显,所以让我们尝试一些示例,以很好地理解实时结果对您的代码意味着什么。

Append to the bottom of your playground:

Example.of("Live Results") {
  let people = realm.objects(Person.self).filter("key == 'key'")
  print("Found \(people.count) people for key \"key\"")
}

测试数据集不包括一个键等于“key”的人。因此,people不会包含任何结果,代码的输出将是:

ℹ️ Live Results:
——————————————————
Found 0 people for key "key"

创建一个新的Person实例并设置其键值,然后将其持久化到Realm实例中:

let newPerson1 = Person()
newPerson1.key = "key"

try! realm.write {
  realm.add(newPerson1)
}

此时,您知道people匹配了一个新对象,并且由于结果集总是最新的,您可以简单地检查people。数属性了。附加:

print("Found \(people.count) people for key \"key\"")

//Found 1 people for key "key”

您不需要刷新结果或要求Realm再次获取结果。每次你问people.count,您将获得与定义的结果集匹配的对象的计数

Ready to experiment a bit further? Append:

let newPerson2 = Person()
newPerson2.key = "key"
newPerson2.firstName = "Sher"
print("Found \(people.count) people for key \"key\"")

这段代码仍然显示只有一个人被找到。由于您没有向Realm添加newPerson2,因此该对象不是持久的Person集合的一部分,因此不在people结果集中。

实际上,您甚至不能将newPerson2添加到相同的Realm,因为它的惟一主键与newPerson1具有相同的值,而newPerson1已经被持久化了。如果尝试添加newPerson2,将得到一个运行时异常,表明具有给定主键值的对象已经存在。

实时结果是一个方便的特性,您将经常在本书的其余部分和您的“真实生活”代码中使用它。 所有努力显示实时数据的应用程序都可以利用实时的Realm结果,特别是当与内置的Realm更改通知相结合时,它不仅会让你知道什么时候,还会让你知道什么数据发生了更改。