0

I used Realm's example of how to use background notifications and set up notifications on my app, where a Realm notification in the background triggered a local notification to my device.

I had all of this working within the CacheWorker class (see the Realm article) but to have everything actually work the way I want it to (e.g., allow the user to alter the query for what collection is being observed by the token) I think I need to have the token block provided by RequestsTableViewController, the UITableViewController subclass displaying the collection (and picking the filter/sort options).

So I modified the CacheWorker's initializer to take, as its arguments, (1) the collection I want observed, and (2) the block I want executed when a change happens: (note that I've renamed the start method from the article to startWorker)

class CacheWorker: BackgroundWorker {

    private var token: NotificationToken?
    typealias change = RealmCollectionChange<Results<Request>>

    init(observing collection: Results<Request>, block: @escaping (change)->Void) {
        super.init()

        startWorker { [weak self] (_) in
            self?.token = collection.observe(block)
        }
    }       
}

and in the RequestsTableViewController's viewDidLoad() function I provide the block I want the token to execute. The CacheWorker is instantiated once I've connected to the realm (this is the realmSetObserver which calls triggeredBlock):

    var realmSetObserver: NSObjectProtocol?
    let triggeredBlock: (Notification)->Void = { [weak self] (_) in
        self?.realm = YPB.realmSynced
        self?.tableView.reloadData()    // initial loading

        if let requests = self?.realm?.objects(Request.self).filter("played = false") {
            self?.worker = CacheWorker(observing: requests) { [weak self] changes in
                switch changes {
                case .update(_,_,let insertions, _):
                    for index in insertions{
                        self?.tableView.reloadData()
                        self?.notifyOf(requests[index])
                    }
                default: break
                }
            }
            NotificationCenter.default.removeObserver(realmSetObserver!)
        }
    }

    realmSetObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name("realm set"), object: nil, queue: OperationQueue.main, using: triggeredBlock)

The problem is that after startWorker starts the thread, the app crashes, highlighting RunLoop.current.run(mode: .defaultRunLoopMode, before: .distantPast) from BackgroundWorker and giving the report below. (note: in debugging, it appears that the RunLoop is run once, before crashing on its second run.)

Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.'

It took a lot of brainpower to put all these closures into places where they could be passed (and referred to by variable names rather than inline), but even without that, I'm pretty sure I don't know/understand enough about how multithreading works to know what to do here.

How can I fix this?

Jonathan Tuzman
  • 11,568
  • 18
  • 69
  • 129
  • You need to create an exception breakpoint and re-run your app in debug mode so you can see the exact spot where the `RLMException` is being thrown. See this question: https://stackoverflow.com/questions/17802662/exception-breakpoint-in-xcode for help setting an exception breakpoint. – Dave Weston Dec 11 '17 at 06:38
  • Instead of using `self?.realm` and other instance properties, either store or pass around a `Realm.Configuration` object instead, and then open a new instance of the Realm using the configuration inside the block immediately before reading or writing to it. Configuration objects are thread safe. (There is also no real performance hit, because Realm internally caches Realm instances on a per-thread basis.) – AustinZ Dec 11 '17 at 19:06
  • So I tried both of these things – in my BackgroundWorker I created a function (rather than an initializer) that takes a `Realm.Configuration` and then inside the `startWorker` closure I initialize a realm with the passed config, create the query, and then assign the token. And I set an exception breakpoint. Still crashes in the same way. However, see the edit in my post for an update on the concept behind this whole endeavor. – Jonathan Tuzman Dec 13 '17 at 01:27

0 Answers0