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!
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.
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.
Related
I have a simple data structure for storing a User:
struct User{
let id: String
var name: String
}
I'm confused as to whether I should be using a Struct or a Class. I've consulted Apple's documentation:
In an app that consults a remote database, for example, an instance's identity may be fully owned by an external entity and communicated by an identifier. If the consistency of an app's models is stored on a server, you can model records as structures with identifiers.
Local changes to model types like PenPalRecord are useful. For example, an app might recommend multiple different penpals in response to user feedback. Because the PenPalRecord structure doesn't control the identity of the underlying database records, there's no risk that the changes made to local PenPalRecord instances accidentally change values in the database.
This would suggest using a struct, since I wouldn't want to accidentally modify the model in the database, the YapDatabase being a a key/value store. YapDatabase is clear that you can store both Structs and Classes. Yet most of their documentation seems to suggest using a Class.
Any thoughts?
I hope this question is not too generalized or broad. But I've been trying to grasp this concept for the past week and I don't make any progress. I've read dozens of articles and answers on StackOverflow but nobody seems to address this properly. Everyone is always using the most basic models that don't intertwine at all (see example). It might just be me being stupid.
I'm really struggling with understanding how a good data model works and how to implement it into an app. I want a method that is consistent and convenient throughout the app. I have an example.
Let's say I want to create a data model like this:
struct Person {
var name: String
}
struct Car {
var name: String
var owner: Person // Link to a car struct
}
struct Garage {
cars: [Car] // Array of car structs. Usage: Maybe a list view that shows all cars
}
This covers some of my big problems using structs as data models:
References between models - Structs are value types, referencing another model simply creates a copy of that model. Thus, I end up with multiple copies of the same data. What if that data changes? Or what if the reference itself changes (i.e. the owner of a car)? Observing a Car struct using, for instance, Apple's Combine framework doesn't really work because of this owner reference and the fact that it just is a copy of the original Person.
Arrays of models - This seems to be even more tricky. I have to decide whether I want to use the array as a whole (i.e. for a list view) or if I need to use individual elements (i.e. passing a car to a detail view). When trying to keep everything synced it seems like I have to choose one of the two. Doing both at the same time looks impossible. I could create two separate models, each for its own use case but again, I end up having multiple copies of the same data. It just doesn't work.
(To make it more clear: What I mean is I always have to decide between Observe([cars]) and [Observe(car)]. Doing both doesn't seem to work.)
How would I use this data model inside a project with multiple views like a "CarListView", "CarDetailView", "PersonDetailView", etc. without ending up with dozens different copies of my data that are all different?
I really want to use structs because the code becomes so simple and beautiful. But it just seems to be really annoying and partially impossible. However, wherever I go people advice using structs over classes in most cases. The example looks like a fairly basic and normal use case. Is it not suitable for struct usage? Am I just stupid?
I'd be extremely grateful for any insight on this topic. This is driving me crazy.
In this simple class:
class Simple: Object{
#objc var name: String = ""
func doSomething(){}
}
When I save this into Realm, what does get saved? The variable only or the function as well? The reason I am asking this, is because when I got a lot of Simple objects, I do not want to save the functions ofcourse. The objects would get bigger causing a negative influence on performance.
The variable. It creates a 'column' named "name". Check the realm docs.
Also if you have a lot of data and you would like to browse it you could do it with this Realm Browser where you can see clearly your realm database structure.
You should read through the official documentation and especially the part about supported model properties, which clearly mentions what you can persist in Realm objects.
You can only save properties of certain supported types (Int, String, etc.) or references to other Realm objects (as one-to-to, one-to-many or inverse relations), but you cannot save function references and it wouldn't make sense anyways.
You can add ignored properties and functions to your Realm model classes, but they will only exist in memory, they won't be saved to Realm. For functions this is all you actually need, it wouldn't make any sense to save a function to local storage.
Also, your current model is flawed as your name property is missing the dynamic keyword in its declaration and hence it cannot be treated as a Realm property.
To ease load on my MongoDB server, I want to cache some objects from Waterline in Redis. To achieve this, I have to serialize the object to JSON.
My question is, how can I construct my JSON back to an instance of the Waterline model, with datatype handling, member functions etc, making this transparent to the consuming code?
I have also wanted this whenever I run native MongoDB queries, giving me objects with native ObjectIDs, mismatching date types etc.
User.findOne(id, function (err, user) {
// to string and back again, could be stored in cache in the meantime
var object = JSON.parse(JSON.stringify(user));
var user = new User(object); //doesn't work
var user = User.toObject(object); // doesn't work
}
According to this issue, this should work
var user = new User._model(object);
Pay attention about the values that you insert. The new object should really fit what you had before the stringification.
What kind of cache do you want to approach?
var object = JSON.parse(JSON.stringify(user)); won't store all of the information. JSON is different from JavaScript object. Which user is JavaScript object, that is created by Sails's Waterline engine. It has methods like save, but when you turn it into JSON, the method will be gone.
Either var user = new User(object); or var user = User.toObject(object); won't work, because User is not a constructor, it just a shorthand to sails.models.user which represent the User model at /api/models/User.js.
If you want to query fast, you must store your data in your Redis, and retrieve from it later.
By that mean, you should save your user from the main database to your redis by insert it manually to your redis, then retrieve it later. But all of your main database model characteristic is will be gone, except you copy your main database model (User in this case) to your redis User model (or whatever you named it).
The CoreData documentation says "You can sometimes benefit from creating your own unique ID (UUID) property which can be defined and set for newly inserted objects. This allows you to efficiently locate specific objects using predicates (though before a save operation new objects can be found only in their original context)."
What should be used for this type?
A managed object's objectID is usually 'NSManagedObjectID' type but the DataModel wizard tool via XCode that allows you to set the type for a given attribute only has the basic allowed types in addition to 'Undefined', Binary Data, & Transformable.
If I wanted to have an attribute that serves as a secondary id for an object (in addition to it's standard ObjectID), do you store it as an NSString or would you custom modify the object model to hold NSManagedObjectID?
(for iPhone app/CoreData development)
An NSString or integer attribute are logical choices, though you could use a transformable attribute to store anything you wanted (that could be appropriately serialized, of course). An incrementing integer is probably good enough for many uses, but each use case is different. Many algorithms exist on the net for generating string or byte-array UUIDs (start with Google). An NSString UUID is quite easy:
+(NSString*)UUIDString {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
return [NSMakeCollectable(string) autorelease];
}
for an array of bytes, look at CFUUIDGetUUIDBytes().
Before you go this route, think long and hard about whether it is necessary. Folks coming from a SQL point of view "want" their ids, but Core Data is not about relational databases. It's an object graph management framework that just happens to use SQLite as one backing implementation. If you're trying to do SQL-like things in Core Data, you're going to be fighting the framework. There's often a way around needing a separate id property in proper usage of the Core Data framework.