Realm notification token on background thread

偶尔善良 提交于 2019-12-03 13:10:19

问题


I was trying to fetch realm data on the background thread and add a notification block (iOS, Swift).

Basic example:

    func initNotificationToken() {

        DispatchQueue.global(qos: .background).async {
          let realm = try! Realm()
          results = self.getRealmResults()

        notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in

          switch changes {
           case .initial:
            self?.initializeDataSource()
            break
          case .update(_, let deletions, let insertions, let modifications):
           self?.updateDataSource(deletions: deletions, insertions: insertions, modifications: modifications)
           break
          case .error(let error):
            fatalError("\(error)")
            break
          }
        }
      }
    }

    func initializeDataSource() {
          // process the result set data 

         DispatchQueue.main.async(execute: { () -> Void in
            // update UI
         })
    }

    func updateDataSource(deletions: [Int], insertions: [Int], modifications: [Int]) {
          // process the changes in the result set data 

         DispatchQueue.main.async(execute: { () -> Void in
            // update UI
         })
    }

When doing this I get

'Can only add notification blocks from within runloops'

I have to do some more extensive processing with the returned data and would like to only go back to the main thread when updating the UI after the processing is done.

Another way would probably to re-fetch the data after any update on the background thread and then do the processing, but it feels like avoidable overhead.

Any suggestions on the best practice to solve this?


回答1:


To add a notification on a background thread you have to manually run a run loop on that thread and add the notification from within a callout from that run loop:

class Stuff {
    var token: NotificationToken? = nil
    var notificationRunLoop: CFRunLoop? = nil

    func initNotificationToken() {
        DispatchQueue.global(qos: .background).async {
            // Capture a reference to the runloop so that we can stop running it later
            notificationRunLoop = CFRunLoopGetCurrent()

            CFRunLoopPerformBlock(notificationRunLoop, CFRunLoopMode.defaultMode.rawValue) {
                let realm = try! Realm()
                results = self.getRealmResults()

                // Add the notification from within a block executed by the
                // runloop so that Realm can verify that there is actually a
                // runloop running on the current thread
                token = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
                    // ...
                }
            }

            // Run the runloop on this thread until we tell it to stop
            CFRunLoopRun()
        }
    }

    deinit {
        token?.stop()
        if let runloop = notificationRunLoop {
            CFRunLoopStop(runloop)
        }
    }
}

GCD does not use a run loop on its worker threads, so anything based on dispatching blocks to the current thread's run loop (such as Realm's notifications) will never get called. To avoid having notifications silently fail to do anything Realm tries to check for this, which unfortunately requires the awakward PerformBlock dance.



来源:https://stackoverflow.com/questions/41819435/realm-notification-token-on-background-thread

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!