Active Filters: Models

In Bite #231, we took a look at Realm's new Fine-grained notifications functionality. Today we'll build upon this by checking out another new Realm feature: Queryable, Live Inverse Collections. Whew! That's a fancy name. This feature deserves one though, let's check it out.

Here's a Realm object with a relationship defined on it:

class Person: Object {
  dynamic var name = ""
  let dogs = List<Dog>()
}

That dogs property can be used in a query, and it will even stay updated to reflect changes to the property's value made elsewhere in our app, automatically.

None of that is new though. What is new is the inverse of this mechanic.

Meet the all-new LinkingObjects:

class Dog: Object {
  dynamic var name = ""
  dynamic var age = 0
  let owners = LinkingObjects(
    fromType: Person.self, 
    property: "dogs"
  )
}

Here's what we get for defining things this way:

LinkingObjects are live and auto-updating. When new relationships are formed or removed, they will update to reflect the new state.

LinkingObjects can be used In queries, natively. (Previously this would need to be done in our code):

// People that have a child that have a parent named Diane.
realm.objects(Person).filter("ANY children.parents.name == 'Diane'")

// People whose parents have an average age of > 65.
realm.objects(Person).filter("parents.@avg.age > 65")

LinkingObjects behave like regular Realm collections:

// Which of my parents are over the age of 56?
self.parents.filter("age > 56")

  // Calculate the age of my parents.
self.parents.average("age")

More info about Realm can be found at realm.io

We first looked at Realm way back in Bite #49. It's a great data storage solution for our mobile apps. Today we'll start looking at some of the latest improvements in Realm and the new capabilities they offer. First up is Fine-grained notifications. Let's dive in:

Realm has offered notifications of write operations for a while, they look like this:

let token = realm.addNotificationBlock { notif, realm in
  // TODO: viewController.updateUI()
}

These are still around and work great, but it might help to know more about what changed. That's where the new Collection Notifications come in.

Collection notifications give us access the changes that just occurred at a fine-grained level, including the specific indices of insertions, deletions, etc

let results = try! Realm().objects(Spaceship).sorted("name")
let token = results.addNotificationBlock { (changes: RealmCollectionChange) in
  // TODO: self.processChanges(changes)
}

changes here is an enum that looks like this:

public enum RealmCollectionChange<T> {
  case Initial(T)
  case Update(T, deletions: [Int], insertions: [Int], modifications: [Int])
  case Error(NSError)
}

.Update's values can be easily mapped to NSIndexPath objects suitable for use in table views and collection views.

Here's a complete example showing all of this in action:

class SpaceshipsViewController: UITableViewController {
  var notificationToken: NotificationToken? = nil

  override func viewDidLoad() {
    super.viewDidLoad()
    let realm = try! Realm()
    let results = realm.objects(Spaceships).filter("maxSpeed > 0")

    // Observe Results Notifications
    notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
      guard let tableView = self?.tableView else { return }
      switch changes {
      case .Initial:
        // Results are now populated and can be accessed without blocking the UI
        tableView.reloadData()
        break
      case .Update(_, let deletions, let insertions, let modifications):
        // Query results have changed, so apply them to the UITableView
        tableView.beginUpdates()
        tableView.insertRowsAtIndexPaths(insertions.map { NSIndexPath(forRow: $0, inSection: 0) }, withRowAnimation: .Automatic)
        tableView.deleteRowsAtIndexPaths(deletions.map { NSIndexPath(forRow: $0, inSection: 0) }, withRowAnimation: .Automatic)
        tableView.reloadRowsAtIndexPaths(modifications.map { NSIndexPath(forRow: $0, inSection: 0) }, withRowAnimation: .Automatic)
        tableView.endUpdates()
        break
      case .Error(let error):
        // An error occurred while opening the Realm file on the background worker thread
        fatalError("\(error)")
        break
      }
    }
  }

  deinit {
    notificationToken?.stop()
  }
}

More info about Realm can be found at realm.io

Topics

#86: Decodable 🔩

Topics

We continue our look at frameworks that map JSON into model types today with Decodable by Johannes Lund. Decodable is another fantastic solution for this task. It takes advantage of Swift 2's new error handling functionality, and unlike ObjectMapper (covered in Bite #84), the properties on your models don't need to be optionals. Let's take a closer look:

To get things wired up we just need to implement the Decodable protocol on our model types, like so:

struct Spaceship {
  var captain: User
  var topSpeed: Double
}

extension Spaceship: Decodable {
  static func decode(j: AnyObject) throws -> Spaceship {
    return try Spaceship(
      captain: j => "captain",
      topSpeed: j => "topSpeed"
    )
  }
}

Then we can convert from JSON to one of these structs like this:

do {
  let ship = try Spaceship.decode(json)
} catch let error { print(error) }

We can decode JSON arrays like this:

do {
  let ships = try [Spaceship].decode(array)
} catch let error { print(error) }

Decodable also handles nested types and is quite flexible. For example, we aren't even forced to use Decodable on every type. Here we bypass Decodable a bit and instantiate a value for our rank property manually:

extension User: Decodable {
  static func decode(j: AnyObject) throws -> User {
    return try User(name: j => "name", rank: Rank(name: j => "rank"))
  }
}

Some other noteworth Decodable features are its wonderful printable errors, and how easy it is to add custom decode functions for things like parsing custom date formats, etc.

More info about Decodable can be found at git.io/decodable

Topics

#84: ObjectMapper 📲

Topics

There are plenty (no really, plenty) of options when it comes to parsing JSON into model objects on iOS. We'll be taking a look at some of them from time to time over the coming weeks. First up is ObjectMapper by Hearst. Let's take a look how it works with a fictional set of types:

With ObjectMapper, we implement the Mappable protocol on our types to support converting to and from JSON:

struct Spaceship: Mappable {
  var captain: User?
  var topSpeed: Double?

  init?(_ map: Map) { }

  mutating func mapping(map: Map) {
    captain  <- map["captain"]
    topSpeed <- map["topSpeed"]
  }
}

struct User: Mappable {
  var name: String?
  var rank: Int?

  init?(_ map: Map) { }

  mutating func mapping(map: Map) {
    name <- map["name"]
    rank  <- map["rank"]
  }
}

Then we can convert from JSON to one of these structs like this:

let ship = Mapper<Spaceship>().map(JSONString)

And go the other way (from a struct back to JSON) like this:

let JSONString = Mapper().toJSONString(ship, prettyPrint: true)

ObjectMapper can easily handle nested objects, here on our Spaceship model, we've got an optional User property for the captain of the ship.

It also supports subclasses and custom transforms when serializing/deserializing properties. One of the best things about ObjectMapper are the extensions available for other great iOS libraries like Alamofire and Realm (covered in Bite #49). Here's AlamofireObjectMapper in action:

Alamofire.request(.GET, "https://api.jeditemple.com/ships/31", parameters: nil)
  .responseObject { (response: Spaceship?, error: ErrorType?) in
    print(response?.topSpeed)
  }

ObjectMapper stands out in the simplicity of it's API and the ease at which we can jump in and start using it.

More info about ObjectMapper can be found at git.io/objectmapper

Topics

#49: Realm Basics 🔮

Topics

Realm is a database made specifically for running on mobile devices. It works on Mac and iOS devices. (It even supports Android!) It's a great alternative to Core Data or even raw SQLite.

There's plenty to love about Realm, but the best part is it's ultra-simple API:

Define an Object

import RealmSwift

class Spaceship: Object {
  dynamic var name = ""
  dynamic var topSpeed = 0 // in km
}

Define a Related Object

class Spaceship: Object {
  dynamic var name = ""
  dynamic var topSpeed = 0
  dynamic var owner: User?
}

class User: Object {
  dynamic var firstName = ""
  dynamic var lastName = ""
  let spaceships = List<Spaceship>()
}

Create an Instance of an Object

var ship = Spaceship()

ship.name = "Outrider"
ship.topSpeed = 1150

var dash = User()

dash.firstName = "Dash"
dash.lastName = "Rendar"

ship.owner = dash

// need one Realm per thread
let realm = Realm()

realm.write {
  realm.add(ship)
}

Find Objects

Queries in Realm couldn't be simpler. They are chainable. You can add as many calls to .filter as you'd like. You can sort results using the chainable sorted function.

realm
  .objects(Spaceship)
  .filter("topSpeed > 1000")
  .filter("name BEGINSWITH 'O'")
  .sorted("topSpeed", ascending: false)

Performance

Realm uses a "zero-copy" design that allows it process > 35 queries a second, as well as insert > 95,000 records a second in a single transaction.

More info about Realm can be found at realm.io.