Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tim Oliver: Realm: How I learned to love databa...

Realm
October 20, 2016

Tim Oliver: Realm: How I learned to love database again - iOS Conf SG 2016

Abstract/excerpt: iOS provides two system level database frameworks: SQLite and Core Data. While both of these frameworks are great at what they do, they both have a high learning curve, and sometimes require a fair amount of boilerplate code in order to get started. Realm is a new database framework wholly designed from scratch to try and make persisting data easier than that. In this talk, Tim will introduce Realm and contrast it to his experiences with SQLite and Core Data in his own apps. He will also provide a demonstration on how to get started with it, best implementation practices, and how to migrate an existing app to it.

Bio: Tim has been a huge enthusiast of developing apps on iOS ever since the launch of the iPhone 3G. Originally a web designer/developer out of university, he moved to full-time mobile software development in 2013 and has never looked back. Presently, he works for Realm, remotely from Western Australia.

https://twitter.com/timoliverau

Realm

October 20, 2016
Tweet

More Decks by Realm

Other Decks in Technology

Transcript

  1. Overview 1) My history with databases, up to Realm [email protected]

    2) Power users’ guide to Realm Mobile Database 3) Introducing the Realm Mobile Platform
  2. Who is this guy? • From Perth, Australia, currently living

    in San Francisco, USA. [email protected] • Full time iOS Engineer since 2013. • Studied joint CompSci and Multimedia degree at university. • Loves building apps and framework libraries. • Also loves karaoke and video games.
  3. iPokédex - SQLite [email protected] • Custom built SQLite database file

    to store data. • Results of queries were manually mapped to NSObject subclasses. • Stored as a read-only resource in the app bundle. • “Light” and “Heavy” versions of each subclass. • Graphing Pokémon data is not trivial!
  4. SQLite Takeaways [email protected] • As a C library, will always

    be necessary to have some sort of ‘bridge’ to Objective-C / Swift. • A non-trivial amount of effort to map query results to in- app objects. • Requires knowledge of database design and SQL syntax. • Memory usage is a huge concern, prompting the need for ‘paging’ query results. • Schema migration/thread handling is a very manual process.
  5. iComics - Core Data [email protected] • Data persistence needed for

    caching comic book metadata / user settings / page order. • Core Data touted as ‘nicer version of SQLite` with object-mapping, schema migration, native queries being provided for free. • Native framework supported and promoted by Apple, so it must be good. Right?
  6. Core Data Takeaways [email protected] • Steep learning curve. VERY steep

    learning curve. • An Apple ‘black box’™ where it’s impossible to see its internals; hard to debug when errors arise. • A LOT of boilerplate code required to even begin. • Memory usage is still a concern, with fetch results needing to be paged. • Schema migration can corrupt a database without warning.
  7. Converting from Core Data to Realm [email protected] • Rename NSManagedObject

    subclasses to RLMObject. • Re-implement all Core Data fetch/threaded/save operations to Realm equivalents. • Redefine NSNumber properties to Int, Double, etc • Optionally, implement a small helper class to copy data from a Core Data to Realm. • The entire process took less than 1 evening of work.
  8. What is Realm Mobile Database? [email protected] • A completely custom

    C++ database engine. • Proper Objective-C / Swift frameworks for a native feel. • Zero-copy, memory mapped data access to objects. • Free and open source on GitHub. • Uses Objective-C runtime reflection to simplify process. • Purely object driven. No abstraction between disk format.
  9. [email protected] Hello World! import RealmSwift // Define our model object

    class Dog: Object { dynamic var name = “” dynamic var age = 0 } // Create a new instance to save let myDog = Dog() myDog.name = “Rex” myDog.age = 4 // Save it to Realm let realm = try! Realm() try! realm.write { realm.add(myDog) }
  10. [email protected] Realm() Object() • Represents .realm file on disk. •

    Can be configured with Configuration. • Strictly thread-confined. • Provides context for read/writes. • Represents a single entry in the database. • Also strictly thread- confined. • Supports all major data types natively. • Cached on subsequent calls.
  11. [email protected] Getting the data back out again import RealmSwift //

    Create the reference to the Realm on disk let realm = try! Realm() // Fetch all dog objects in Realm let allDogs = realm.objects(Dog.self) // Get just the first dog let rex = allDogs[0] // Access the dog’s properties print(rex.name)
  12. [email protected] Results() / List() • Behaves exactly like an Array.

    • Strictly thread-confined. • Object members are lazy-loaded upon access.
  13. [email protected] Zero-Copy Architecture • Object properties point straight to their

    values on disk. • Memory-mapping is used to lazily page data in on demand. • No need for worrying about memory usage, or pagination!
  14. [email protected] Register for update notifications // Observe Realm Notifications let

    token = realm.addNotificationBlock { notification, realm in viewController.updateUI() } // Observe Results Notifications notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in switch changes { case .initial: // Results are now populated and can be accessed without blocking the UI break case .update(_, let deletions, let insertions, let modifications): // Query results have changed break case .error(let error): fatalError(“\(error)") // An error occurred while opening the Realm file break } } • Notified whenever anything changes inside Realm() • Notified whenever contents of Results() changes dog.addObserver(self, forKeyPath: “age”, options: [.new], context: nil) • Key-Value Observing of specific properties
  15. 1. Take advantage of live objects [email protected] • Don’t worry

    about implementing ‘re-fetch’ code. • Don’t try and optimise for memory consumption (ie paging). • Use Realm Results object; don’t copy to NSArray. • Use fine-grained notifications to specifically control when it’s necessary to trigger a UI update. • This is not SQLite. :)
  16. 2. Write Transactions [email protected] • Minimise/batch write transactions as much

    as possible. for item in results { try! realm.write { item.value = newValue } } try! realm.write { for item in results { item.value = newValue } }
  17. 2. Write Transactions [email protected] • Offload write transactions to background

    threads as much as possible. • For ACID compliance, only one write transaction may be open at once. Background writes can block main threaded ones. DispatchQueue.global(attributes: [.qosDefault]).async { // Background thread let realm = try! Realm() // thread-specific realm instance try! realm.write { { // do Realm updates } }
  18. 3. Working Across Threads [email protected] • Any objects backed by

    a realm property aren’t thread-safe. let dog = try! Realm().objects(Dog.self).first DispatchQueue.global(attributes: [.qosDefault]).async { print(dog.name) // WILL TRIGGER AN EXCEPTION } • Each thread has its own confined version of a Realm instance.
  19. 3. Working Across Threads [email protected] • Re-fetch the object on

    the new thread using a primary key. class Dog: Object { dynamic var id = UUID().uuidString dynamic var name = “” override static func primaryKey() -> String { return “id” } } let dog = try! Realm().objects(Dog.self).first let dogID = dog.id DispatchQueue.global(attributes: [.qosDefault]).async { let backgroundDog = try! Realm().object(ofType: Dog.self, primaryKey: dogID) print(localDog.name) }
  20. [email protected] Hello World! (RMP Edition) import RealmSwift let user =

    … // Authenticate for a valid User object let URL = URL(string: “realm://api.myserver.com:9080/~/user") let configuration = Realm.Configuration() configuration.syncConfiguration = (user: user, realmURL: URL) // Create a new instance to save let myDog = Dog() myDog.name = “Rex” myDog.age = 4 // Save it to Realm let realm = try! Realm(configuration: configuration) try! realm.write { realm.add(myDog) }
  21. [email protected] How do we detect remote changes? // Observe Realm

    Notifications let token = realm.addNotificationBlock { notification, realm in viewController.updateUI() } // Observe Results Notifications notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in switch changes { case .initial: // Results are now populated and can be accessed without blocking the UI break case .update(_, let deletions, let insertions, let modifications): // Query results have changed break case .error(let error): fatalError(“\(error)") // An error occurred while opening the Realm file break } } • Notified whenever anything changes inside Realm() • Notified whenever contents of Results() changes dog.addObserver(self, forKeyPath: “age”, options: [.new], context: nil) • Key-Value Observing of specific properties