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?