Slide 1

Slide 1 text

Mastering Realm Notifications JP Simard, @simjp, realm.io 1

Slide 2

Slide 2 text

2

Slide 3

Slide 3 text

Cocoa Messaging 3

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

Reactive Programming 5

Slide 6

Slide 6 text

Reactive != Functional 6

Slide 7

Slide 7 text

7

Slide 8

Slide 8 text

Reactive Programming just means reacting to changes & minimizing the explicit messaging about changes. 8

Slide 9

Slide 9 text

It's about avoiding stale data 9

Slide 10

Slide 10 text

It's about avoiding duplicate code paths 10

Slide 11

Slide 11 text

Avoid different code paths —network —database —user action —push notification —system alerts —other processes 11

Slide 12

Slide 12 text

12

Slide 13

Slide 13 text

13

Slide 14

Slide 14 text

Intro to Realm Notifications 14

Slide 15

Slide 15 text

RealmTasks 15

Slide 16

Slide 16 text

Types of Realm Notifications —Realm Notifications —Collection Notifications —KVO —Upcoming APIs... 16

Slide 17

Slide 17 text

Realm Notifications 17

Slide 18

Slide 18 text

Realm Notifications let realm = try Realm() let token = realm.addNotificationBlock { notification, realm in viewController.updateUI() } // Later... token.stop() 18

Slide 19

Slide 19 text

Realm Notifications —Use when you want to react to everything that happens —Asynchronously delivered —Realm.refresh() waits for these notifications to be delivered —There's usually a better way 19

Slide 20

Slide 20 text

Notification Token —How Realm knows how long to deliver notifications for. —Must hold a strong reference. —Best to call stop() when you no longer want to be notified. —Logs "released without unregistering a notification" if deallocated without calling stop(). Harmless but the best way we know to inform 20

Slide 21

Slide 21 text

Collection Notifications 21

Slide 22

Slide 22 text

Collection Notifications let realm = try Realm() let results = realm.objects(Item.self) // Auto-Updating Results let token = results.addNotificationBlock(tableView.applyChanges) 22

Slide 23

Slide 23 text

Collection Notifications extension UITableView { func applyChanges(changes: RealmCollectionChange) { switch changes { case .initial: reloadData() case .update(let results, let deletions, let insertions, let updates): let fromRow = { (row: Int) in return IndexPath(row: row, section: 0) } beginUpdates() insertRows(at: insertions.map(fromRow), with: .automatic) reloadRows(at: updates.map(fromRow), with: .automatic) deleteRows(at: deletions.map(fromRow), with: .automatic) endUpdates() case .error(let error): fatalError("\(error)") } } } 23

Slide 24

Slide 24 text

Notifications may be coalesced! (grouped) 24

Slide 25

Slide 25 text

Notifications may be coalesced (grouped) switch changes { case .initial: reloadData() case .update(let results, let deletions, let insertions, let updates): let fromRow = { (row: Int) in return IndexPath(row: row, section: 0) } beginUpdates() insertRows(at: insertions.map(fromRow), with: .automatic) reloadRows(at: updates.map(fromRow), with: .automatic) deleteRows(at: deletions.map(fromRow), with: .automatic) endUpdates() case .error(let error): fatalError("\(error)") } 25

Slide 26

Slide 26 text

Synchronous vs Asynchronous Queries Results are queried synchronously if requested, asynchronously otherwise. 26

Slide 27

Slide 27 text

Asynchronous Query let realm = try Realm() // Query never performed on the current thread let results = realm.objects(Item.self) let token = results.addNotificationBlock { _ in /* results available asynchronously here */ } 27

Slide 28

Slide 28 text

Synchronous Query let realm = try Realm() // Query performed synchronously to return first result _ = results.first // <- query performed here let token = results.addNotificationBlock { _ in /* results available asynchronously here */ } 28

Slide 29

Slide 29 text

Collection Notifications —May be coalesced —Generally delivered on runloop iterations —Query upkeep always performed on background thread —Notifications delivered on the source thread —Can avoid all source thread overhead if used correctly 29

Slide 30

Slide 30 text

Interface-Driven Writes 30

Slide 31

Slide 31 text

Interface-Driven Writes —Changeset notifications are all about applying state diffs. —State must be up to date for the diff to make sense. —Some changes should be applied to the UI synchronously. 31

Slide 32

Slide 32 text

Like reordering rows. 32

Slide 33

Slide 33 text

Interface-Driven Writes func viewDidLoad() { messages = realm.objects(Message.self) self.token = messages.addNotificationBlock(tableView.applyChanges) } func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { guard editingStyle == .delete else { return } realm.beginWrite() messages.removeAtIndex(indexPath.row) tableView.deleteRows(at: [indexPath], with: .automatic) realm.commitWrite(withoutNotifying: [self.token]) } 33

Slide 34

Slide 34 text

34

Slide 35

Slide 35 text

Key-Value Observation 35

Slide 36

Slide 36 text

Key-Value Observation —Enables Realm to work well with Cocoa APIs —ReactiveCocoa —Synchronously delivered —Single property at a time 36

Slide 37

Slide 37 text

Objects as Messages 37

Slide 38

Slide 38 text

Inter-process data sharing/message passing —Realm already has robust interprocess data sharing —Could use to share data or notify main iOS app from app extension 38

Slide 39

Slide 39 text

Not limited to single-device —Also for Realm Object Server communication —And massively distributed apps 39

Slide 40

Slide 40 text

Objects as Messages let managementRealm = try user.managementRealm() try managementRealm.write { let permissionChange = SyncPermissionChange(realmURL: realmURL, userID: anotherUserID, mayRead: true, mayWrite: true, mayManage: false) managementRealm.add(permissionChange) } 40

Slide 41

Slide 41 text

Objects as Messages let collectionOfOne = managementRealm.objects(SyncPermissionChange.self) .filter("id = %@", permissionChange.id) token = collectionOfOne.addNotificationBlock { notification in if case .update(let changes, _, _, _) = notification, let change = changes.first { // Object Server processed the permission change operation switch change.status { case .notProcessed: break // handle case case .success: break // handle case case .error: break // handle case } print(change.statusMessage) // contains error or message } } 41

Slide 42

Slide 42 text

Object-Level Notifications 42

Slide 43

Slide 43 text

Object-Level Notifications let realm = try Realm() let jane = realm.objects(Person.self).filter("name == 'Jane'").first! let token = jane.addNotificationBlock { change in switch change { case .change(let propertyChanges): for propChange in propertyChanges { print("'\(propChange.name)': \(propChange.oldValue) -> \(propChange.newValue)") } case .deleted: print("object was deleted") case .error(let error): print("notification couldn't be delivered: \(error)") } } // Later... token.stop() 43

Slide 44

Slide 44 text

Object-Level Notifications let realm = try Realm() let state = realm.objects(AppState.self).first! let token1 = state.addNotificationBlock(observingProperties: ["unreadCount", "status"], block: { change in // handle change and restrict to properties we care about }) let token2 = state.addNotificationBlock(ignoringProperties: ["noisyProp"], block: { change in // handle change and avoid being notified for properties we don't care about }) 44

Slide 45

Slide 45 text

Object-Level Notifications —Coming early 2017 —Asynchronously delivered —Great for Objects-as-APIs/Messages and event handling 45

Slide 46

Slide 46 text

Resources —Realm's docs on Notifications —realm.io/docs/swift#notifications —Live Objects and Fine-Grained Notifications —realm.io/news —RealmTasks —github.com/realm/RealmTasks —Removing workaround PR: RealmTasks#352 —RxRealm: RxSwiftCommunity/RxRealm 46

Slide 47

Slide 47 text

Thank You! Questions? JP Simard, @simjp, realm.io 47