Group Group Group Group Group Group Group Group Group

raywenderlich.com Forums

Video Tutorial: Beginning Realm on iOS Part 5: Notifications

In this video tutorial you learn how to make use of Realm's notifications to keep your app's UI up to date.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/3479-beginning-realm-on-ios/lessons/6

In this tutorial, you fail to mention that you also need to remove, or comment out the viewWillAppear function on TasksViewController. The app crashes if you don’t when you add a new task. Either that, or just comment out the tableView.reloadData() in that function.
Even your DemoFinished app has that bug.

In addition, you don’t need to remove
tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: .Fade)
from the ToggleFilter function. This can still remain in as the updateUI is not called on a filter, only when you do an insert or delete or update

1 Like

Thanks, I’ll have a look - I might’ve missed that during the recording :+1:

Hi, loved the tutorials, but you forgot something as well.
One issue with the Demo and another with the Challenge.

Demo:
The switch method with .Update is missing a case for when data is being modified to reload the UITableViewCell.
So I modified the case like this:
case .Update(__, let deletions, let insertions, let modifications):
And down in that case i called on tableView .reloadRowsAtIndexPaths passing in NSIndexPath’s for modifications.

Challenge:
There is an issue with the delay function, if the tableView has many cells. If the cell that we call delay in is no longer displayed the delayed completion will be executed on another cell resulting in unpredictable behaviour. In order to remedy that you would need another delay function that takes in the argument taskId, as well as modified completion that accepts a String argument. Something like this:

func delay(seconds:Double, task: String, completion: (id: String)->Void){
_ let time = dispatch_time(DISPATCH_TIME_NOW, Int64(Double(NSEC_PER_SEC)*seconds))_
_ dispatch_after(time, dispatch_get_main_queue(), {completion(id: task)})_
}

1 Like

Looking forward to trying this, but I have a question for those who have experience with both Realm and Core Data.

I can get around Core Data, but for the fun of it I read up on Realm and integrated it with an iPad app I built for work. There are a couple of problems I’m having with it:

  1. The single thread collision thing is giving me problems. So I push a new vc onto the stack and move an object that I created / saved into Realm over to that new vc. Problem is, when I try to access that data within another async thread I get a fatal error. I’ve been reading about some potential workarounds, and what I did was to create a Struct that I populate with the contents of the object so I can easily pass it around. Not the most optimal solution, but it works. I’ve never had thread problems with Core Data.

  2. One thing I like about Core Data is the ability to use fetchedResults and animate changes in your Collection / TableViews. I saw a couple of add-ons to Realm that attempt to do this, but I miss having it available ‘for free’.

For those with experience in both - how much time are you really saving using Realm vs. Core Data - what are the other big wins I’m missing?

Thanks!

On your Question Nr.2: As it’s explained in this series Realm doesn’t provide any fetched results mechanism because it does not copy any data from disc to memory - you are always working with the real time data from the Realm itself, therefore you do not need an intermediate structure like CoreData’s fetched results. The built in Realm mechanism for this is explained literally in the video you left a comment on, but if you need further reading check out this blog post on Realm’s website: https://realm.io/news/marin-todorov-fine-grained-notifications/

I hope that helps

Regarding Question Nr.1: Currently you can’t access Realm objects from different threads - this is a condition, which is part of our multi-threading strategy. (I.e. you can safely read and write from/to your Realm from different threads without the need to merge changes or the possibility to have corrupted data). Check the code demonstrated in this video series to see how you can use objects from different threads - there is a relevant example.

Thanks - just made it through the video about Realm using the data straight from disk. I get it now.

I just wish it had the same animated updating as CD fetchedResults. I think I read somewhere it might be on their roadmap.

Thanks again.

I’ll say it one more time - the video and the link I’ve put up there literally shows what you’re asking for.

Thanks - I didn’t see the withRowAnimation in the URL during my first look.

I appreciate it!

Very nice tutorial @icanzilb :clap:. In case anyone is wanting to run through this using Swift 3 (until it’s updated and RealmSwift for Swift 3 is released), you just need to update your Podfile to this before running pod install:

use_frameworks!

target 'Todoify' do
    pod 'Realm', git: 'https://github.com/realm/realm-cocoa.git', branch: 'master', submodules: true
    pod 'RealmSwift', git: 'https://github.com/realm/realm-cocoa.git', branch: 'master', submodules: true

    post_install do |installer|
        installer.pods_project.targets.each do |target|
            target.build_configurations.each do |config|
                config.build_settings['SWIFT_VERSION'] = '3.0'
            end
        end
    end
end

I’ve published my project for this tutorial here.

Thanks for the tutorial! I can follow along when it comes to updating tableviews using notifications. How might I implement notifications to update/reload annotations in a mapView? I’m trying to get annotations to automatically show up after the initial app launch. Many thanks!

The Realm notifications work the same independently of what UIKit component you use to display the results. If you aren’t sure how to use the map component in UIKit search for a tutorial that explains how to use MapKit. There are a number of tutorials on this very site you just need to do a quick search.

Great! I got it working! Thanks!

Hello, I have a couple questions about notifications, UI updates, and write transactions. According to the Realm documentation:

Q1:

"Notifications in Realm are always delivered asynchronously so your app never stutters or blocks the main UI thread. "

So, when the notifications are delivered are we on the main queue? or do we need to dispatch back to the main queue before updating the table view?

The documentation also provides this sample code for an interface-driven write in which the UI should immediately reflect the change.

func insertItem() throws {
  // Perform an interface-driven write on the main thread:
  collection.realm!.beginWrite()
  collection.insert(Item(), at: 0)
  // And mirror it instantly in the UI
  tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
  // Making sure the change notification doesn't apply the change a second time
  try collection.realm!.commitWrite(withoutNotifying: [token])
}

In this example are we on the main queue, since we haven’t commited the write yet?

Q2:

The documentation also mentions this about write transactions:

“Please note that writes block each other, and will block the thread they are made on if multiple writes are in progress. This is similar to other persistence solutions and we recommend that you use the usual best practice for this situation: offloading your writes to a separate thread.”

Is this the best way to go all the time, and do you provide an example for this in the intermediate course?

Thank you so much for your help and the tutorials and additional articles you provided. You have been a great help!!

Q3

Also I don’t understand why we have to call the subscription method in toggleFilter? Ive read all the documentation but it is unclear. The token is used so that we receive updates whenever tasks changes and so long as we keep the reference to the token we will get the notifications. So why do we have to update the subscription var in toggleFilter? if tasks is changing shouldn’t we get notified anyways? how often to we have to update the token? every time we change tasks? Is it b/c we are reassigning tasks and refetching? rather than just modifying tasks with a write transaction?

Again, thank you so much for clarifying this, I appreciate your time.

Hey Andy, I like these questions - they are really to the point and since you checked the docs first I can answer pretty easily on my part :slight_smile:

Q1: The notifications are delivered on the thread you subscribe on. E.g. viewDidLoad() runs on the main thread, so calling addNotificationBlock() from there guarantees the callback also runs on the main thread. You can subscribe for notifications only threads with installed runloop and by default this is only the main thread.

Q2: You can write from the main and background threads. However writes are serial (as the docs explain) - if you are doing few isolated writes like the demo app for this vide series writing from the main thread should be fine but if you’re doing multiple writes and some concurrently it’s best to do that from background threads to make sure you never happen to block the UI updates on the main thread.

Q3: Once you add a notification block to a collection (e.g. Results, List, LinkingObjects) it runs until you call stop on the token, or the token is released from memory. However in toggleResults we need to change the filter on the collection and that effectively creates a new collection, so we need a new notification block on that new collection.