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?
Related
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.
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.
I am facing same problem as here
How to achieve relationships between entity in one store with other?
Kindly guide me through this.
It is indeed possible and makes total sense in some cases to split objects across stores
lets assume obj A in Store #1 and obj B in Store #2.
Each A has N Bs
easiest solution would maybe be a TRANSIENT property on A ... named 'allMyBs'
then implement the fetch in code:
- (NSArray*)allMyBs {
NSFetchRequest *r == ... //setup to fetch Entity B
[CoreDataHelper managedObjectContextForStore2] executeFetchRequest:r];
}
this isnt the fetched property yet but easy
see Cross-Store weak relationship with Fetched Properties? for a complicated way ^^
The short answer is that you can't. You can't have a relationship between managed objects that crosses persistent stores.
You can, however, implement a key or identifier in your model that YOU maintain across stores, and use that find 'related' objects in different stores.
If you have user records in one store, and application data in another, the application records might have a "belongsTo" property whose value is a user ID corresponding to a user record in the user store.
Apple's documentation is very specific about this:
From the Core Data Programming Guide:
"Core Data does not let you create relationships that cross stores. If you need to create a relationship from objects in one store to objects in another, you should consider using fetched properties."
Again, from the Core Data Programming Guide:
"You must be careful not to create relationships from instances in one persistent store to instances in another persistent store, as this is not supported by Core Data. If you need to create a relationship between entities in different stores, you typically use fetched properties (see “Fetched Properties”)."
This is exactly what I am talking about above. If you implement fetched properties that way, it is up to you to maintain the integrity of the graph. The same document walks you through creating and using fetched properties. Perhaps you can be more specific in what you need answered.
How to group methods belong to one entity in one class file in Core Data like Entity Framework?
In Linq, we can put all methods in the domain object class and reuse them, in Core Data, is there any way to create different classes for different entities? Or we can only use predicate to retrieve?
It seems that I can't define the class for each entity I configured in the data model. And it is not like hibernate that I can control the physical database schema via ORM. Correct me if I am wrong, I don't believe Core Data can do this as far I know.
So what is the advantage to use Core Data? And is there any mechanism on Cocoa that I can define my domain object classes including primary key, foreign key, for instance, and then create the database schema then?
It seems like the Core Data can only support configuring the objects but there is even no way to configure the physical database via the Core Data.
In your Core Data model you can define which class to instantiate your entities as:
alt text http://img404.imageshack.us/img404/3368/setclass.png
When Core Data fetches objects from the store, it will attempt to create them as instances of this class (although it's not quite that simple, Core Data does some tricks).
You can auto-generate class files for your entities by opening your core data model in a new window, and choosing File->New File:
alt text http://img241.imageshack.us/img241/8238/newfile.png
You should see a new "Managed Object Class" item:
alt text http://img46.imageshack.us/img46/348/managedobjectclass.png
Choose this and select the entities to generate classes for:
alt text http://img241.imageshack.us/img241/5030/selectentity.png
A class will be generated with various properties for getting/setting the attributes on your entity:
alt text http://img10.imageshack.us/img10/9868/generatedproperties.png
If you want to add your own methods, I would recommend you add them to a separate Category. This is because you will often want to regenerate these core data classes when you add new properties or change the entities. If you make additions directly to these files you will lose your changes the next time you generate them.
So create a category called Person+Additions or whatever name you think is appropriate:
alt text http://img294.imageshack.us/img294/8871/addcategory.png
And add any new methods or properties to this class:
alt text http://img717.imageshack.us/img717/3884/addmethods.png
Note that you cannot add new instance variables, but NSManagedObject has facilities to get/set values in an internal dictionary. See the NSManagedObject documentation
So once you've done all this, all you do is cast the NSManagedObject you get from a fetch request (or wherever) to the appropriate type:
#import "Person+Additions.h"
//...
Person *p = (Person *) [fetchResult objectAtIndex:0];
[p myCustomMethod];
//...
As for your other questions about primary keys, and interacting with the physical database, this isn't really what Core Data is designed to provide you. You should not think too much about the underlying database, Core Data is meant to be more high level than that. You define your model, and fetch objects through the API. Primary keys and othe "database-y" details like this are not exposed.
If you want full control over the database you should use the sqlite APIs. But I would say for the large majority of applications Core Data is the better choice.
Look at the docs for KVC ("key-value-coding"). You use key paths to obtain the values you want. You can also sort the data in an array using a key path. For that, take a look at NSSortDescriptors. Otherwise, yes, just change your NSPredicate and re-fetch.
Consider an iPhone application that is a catalogue of animals. The application should allow the user to add custom information for each animal -- let's say a rating (on a scale of 1 to 5), as well as some notes they can enter in about the animal. However, the user won't be able to modify the animal data itself. Assume that when the application gets updated, it should be easy for the (static) catalogue part to change, but we'd like the (dynamic) custom user information part to be retained between updates, so the user doesn't lose any of their custom information.
We'd probably want to use Core Data to build this app. Let's also say that we have a previous process already in place to read in animal data to pre-populate the backing (SQLite) store that Core Data uses. We can embed this database file into the application bundle itself, since it doesn't get modified. When a user downloads an update to the application, the new version will include the latest (static) animal catalogue database, so we don't ever have to worry about it being out of date.
But, now the tricky part: how do we store the (dynamic) user custom data in a sound manner?
My first thought is that the (dynamic) database should be stored in the Documents directory for the app, so application updates don't clobber the existing data. Am I correct?
My second thought is that since the (dynamic) user custom data database is not in the same store as the (static) animal catalogue, we can't naively make a relationship between the Rating and the Notes entities (in one database) and the Animal entity (in the other database). In this case, I would imagine one solution would be to have an "animalName" string property in the Rating/Notes entity, and match it up at runtime. Is this the best way to do it, or is there a way to "sync" two different databases in Core Data?
Here's basically how I ended up solving this.
While Amorya's and MHarrison's answers were valid, they had one assumption: that once created, not only the tables but each row in each table would always be the same.
The problem is that my process to pre-populate the "Animals" database, using existing data (that is updated periodically), creates a new database file each time. In other words, I can't rely on creating a relationship between the (static) Animal entity and a (dynamic) Rating entity in Core Data, since that entity may not exist the next time I regenerate the application. Why not? Because I have no control how Core Data is storing that relationship behind the scenes. Since it's an SQLite backing store, it's likely that it's using a table with foreign key relations. But when you regenerate the database, you can't assume anything about what values each row gets for a key. The primary key for Lion may be different the second time around, if I've added a Lemur to the list.
The only way to avoid this problem would require pre-populating the database only once, and then manually updating rows each time there's an update. However, that kind of process isn't really possible in my case.
So, what's the solution? Well, since I can't rely on the foreign key relations that Core Data makes, I have to make up my own. What I do is introduce an intermediate step in my database generation process: instead of taking my raw data (which happens to be UTF-8 text but is actually MS Word files) and creating the SQLite database with Core Data directly, I introduce an intermediary step: I convert the .txt to .xml. Why XML? Well, not because it's a silver bullet, but simply because it's a data format I can parse very easily. So what does this XML file have different? A hash value that I generate for each Animal, using MD5, that I'll assume is unique. What is the hash value for? Well, now I can create two databases: one for the "static" Animal data (for which I have a process already), and one for the "dynamic" Ratings database, which the iPhone app creates and which lives in the application's Documents directory. For each Rating, I create a pseudo-relationship with the Animal by saving the Animal entity's hash value. So every time the user brings up an Animal detail view on the iPhone, I query the "dynamic" database to find if a Rating entity exists that matches the Animal.md5Hash value.
Since I'm saving this intermediate XML data file, the next time there's an update, I can diff it against the last XML file I used to see what's changed. Now, if the name of an animal was changed -- let's say a typo was corrected -- I revert the hash value for that Animal in situ. This means that even if an Animal name is changed, I'll still be able to find a matching Rating, if it exists, in the "dynamic" database.
This solution has another nice side effect: I don't need to handle any migration issues. The "static" Animal database that ships with the app can stay embedded as an app resource. It can change all it wants. The "dynamic" Ratings database may need migration at some point, if I modify its data model to add more entities, but in effect the two data models stay totally independent.
The way I'm doing this is: ship a database of the static stuff as part of your app bundle. On app launch, check if there is a database file in Documents. If not, copy the one from the app bundle to Documents. Then open the database from Documents: this is the only one you read from and edit.
When an upgrade has happened, the new static content will need to be merged with the user's editable database. Each static item (Animal, in your case) has a field called factoryID, which is a unique identifier. On the first launch after an update, load the database from the app bundle, and iterate through each Animal. For each one, find the appropriate record in the working database, and update any fields as necessary.
There may be a quicker solution, but since the upgrade process doesn't happen too often then the time taken shouldn't be too problematic.
Storing your SQLite database in the Documents directory (NSDocumentDirectory) is certainly the way to go.
In general, you should avoid application changes that modify or delete SQL tables as much as possible (adding is ok). However, when you absolutely have to make a change in an update, something like what Amorya said would work - open up the old DB, import whatever you need into the new DB, and delete the old one.
Since it sounds like you want a static database with an "Animal" table that can't be modified, then simply replacing this table with upgrades shouldn't be an issue - as long as the ID of the entries doesn't change. The way you should store user data about animals is to create a relation with a foreign key to an animal ID for each entry the user creates. This is what you would need to migrate when an upgrade changes it.