简体   繁体   中英

Periodically save an object in Realm

I have a Realm object called Trip . It stores data of a user's movement.

class Trip: Object {
    dynamic var id: Int = 0
    dynamic var startTimestamp: Int64 = 0
    dynamic var endTimestamp: Int64 = 0
    dynamic var distance: Double = 0.0
    dynamic var calories: Double = 0.0
    dynamic var averageSpeed: Double = 0.0
}

In the view controller, I keep a class-level variable called trip .

fileprivate var trip: Trip?

Whenever a user starts a trip, I initialize a Trip object and assigns it to this variable.

trip = Trip()

And throughout the user's movements, I keep updating this trip object with the data.

I need to save this data to the Realm database every 1 minute. So I run a timer.

Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(save()), userInfo: nil, repeats: true)

Which executes a function to save this object in a background thread.

fileprivate func save() {
    do {
        DispatchQueue(label: "RealmBackgroundThread").async {
            autoreleasepool {
                let realm = try! Realm()
                try! realm.write {
                    realm.add(self.trip!, update: true)
                }
            }
        }

    } catch {

    }
}

Up to here, it works fine. The problem is after the first save, when I try to access that trip object again, it crashes with the following error.

libc++abi.dylib: terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread.

I think this happens because I open a new Realm to save this object in a background thread. I know that Realm isn't thread-safe.

But I'm not sure how to resolve this. How do I keep using the same trip object after saving it?

To quote from Realm's documentation about Passing Instances Across Threads :

Instances of Realm , Results , or List , or managed instances of Object are thread-confined, meaning that they can only be used on the thread on which they were created, otherwise an exception is thrown.

In your case, self.trip is a managed instance of an Object subclass, and you appear to be accessing it from both the main thread, and repeatedly from the serial dispatch queue you create within your save() method. It's important to keep in mind that the call to DispatchQueue.async will result in your code being executed on different threads depending on the whims of Grand Central Dispatch. That is to say that two consecutive calls like …

DispatchQueue(label: "RealmBackgroundThread").async {
    // work
}

… will often result in the work being performed on two different threads.

Realm's documentation on Passing Instances Across Threads contains an example of how to pass a thread-safe reference to an object across threads and resolve it in the Realm instance on the target thread.

It's hard to provide a more specific suggestion in your case as I'm not clear what you're trying to do with your save() method and why you're calling it on a timer. Managed Object instances (instances retrieved from a Realm, or that have already been added to a Realm) can only be modified within a write transaction, so any modifications to self.trip would need to be performed within the write transaction opened by your save() method for it to serve any purpose. For the pattern of using a timer to make sense you'd need to leave self.trip as an unmanaged object, at which point save() would update the Trip instance with the same ID in the Realm file. To do this you'd want to use Realm.create(_:value:update:) instead of Realm.add(_:update:) , as create does not convert unmanaged instances to managed instances like add does.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM