NSManagedObjectID vs custom UUID identifier attribute - fetch performance
Asked Answered
C

2

7

I would really like to avoid using NSManagedObjectID as a way to connect my model structs to their CoreData objects. I mean something like this:

Say I have a Book entity in CoreData and then I have a model struct like this representing it for my model layer:

struct BookModel {
    let name: String
    ...

    let objectID: NSManagedObjectID // I need this to refer back to the entry in the database
}

I don't like this approach. It makes working with the structs tedious and, for instance, testing is annoying because I always have to generate dummy objectIds or make BookModel.objectID optional.

What I would love to have is an id property of type UUID inside the Book entity. This would be so easy to connect to structs and also allows the structs to properly exist without a database:

struct BookModel {
    let name: String
    ...
    let id: UUID
    ...

    func object() -> Book {
        // Retrieve managed object using a fetch request with a predicate.
    }
}

I've noticed that you can actually have UUID properties in an entity. However, the performance difference seems to be enormous. I've created an example that tries to fetch individual objects 10000 times.

First, I fetched them using the contexts object(with: NSManagedObjectID). I hard-coded all the possible objectIds in an array and passed a random one each time.

Then, I used a simple fetch request with a NSPredicate that got passed a random UUID.

The difference in execution time is significant:

With ObjectID: 0.015282376s

With UUID: 1.093346287s

However, the strange thing is that the first method didn't actually produce any SQL queries (I logged them using the launch argument -com.apple.CoreData.SQLDebug 4). This would explain the speed but not why it doesn't need to communicate with the database at all.

I researched a bit but can't really figure out what object(with: NSManagedObjectID) actually does behind the scenes.

Does this mean, using a "custom" UUID property is not a good idea? I would really appreciate any insights on this!

Curtsy answered 2/4, 2020 at 11:33 Comment(0)
R
4

I would not rely on the NSManagedObjectID in your code. It makes your code dependent on Apple's database implementation, which may change at any time, and it would not make your app resilient against future changes.

By way of example, you would not be able to use the new NSPersistentCloudKitContainer. It does not support NSManagedObjectID: see https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit/creating_a_core_data_model_for_cloudkit

Instead of hardcoding NSManagedObjectID you are better off giving your entities unique UUIDs, as you have suggested. This may or may not affect performance, but you are better off in the long run, as the underlying core database technologies will shift.

Ringnecked answered 4/4, 2020 at 15:29 Comment(2)
Thanks for the input. I did not know this. I always thought NSManagedObjectID was one of the fundamental things about Core Data.Curtsy
Regarding NSPersistentCloudKitContainer it would just mean that you'd have to serialize your NSManagedObjectID before storing them I thinkLiminal
B
1

You should just use a String to represent the NSManagedObjectID. To convert from NSManagedObjectID to string is easy:

objectID.uriRepresentation().absoluteString

To convert from String to NSManagedObjectID is slightly more complicated:

if let url = URL(string: viewModel.id),
   let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: url)

This will make your model objects cleaner.

NSManagedObjectID is good to be used within one application on one device, but it should never be stored and referenced across different applications on different device. I think it is not true that NSManagedObjectID is not supported for CloudKit.

As per why object(with: NSManagedObjectID) is fast. The document says it returns:

The identified object, if its known to the context; otherwise, a fault with its objectID property set to objectID.

This means that if the object has been loaded before, it will return it immediately, if it has not been loaded before, it will return a fault. If you want to trigger a SQL to happen for a good comparison, you need to access one of the attributes after you call object(with: NSManagedObjectID). I would assume the performance should be very similar to the one using UUID.

Brenn answered 17/9, 2022 at 16:36 Comment(1)
I think object ids are maintained between devices: "A managed object ID uniquely identifies the same managed object both between managed object contexts in a single application, and in multiple applications (as in distributed systems). " From: developer.apple.com/documentation/coredata/nsmanagedobjectidLiminal

© 2022 - 2024 — McMap. All rights reserved.