CoreData Performance with Relation (RemoteID and internal ID) - ios

I have lots of CoreData Models with relations - for example:
A Task could have Persons, Groups, Images, ..
Normally i would use here the CoreData Relations if a user creates a new object with a relation. like:
let object = NSEntityDescription.insertNewObjectForEntityForName("Images", inManagedObjectContext: context) as! Image
object.relation = whatever (NSManagedObject)
So CoreData created in SQLite an Relation, an numeric ID.
And with a predicate i can easy get all related Objects when ill use the NSManagedObject in a where clause.
But, now i need to sync a lot of Models with a Backend, and it is much better to use the RemoteID for relations. Like:
let object = NSEntityDescription.insertNewObjectForEntityForName("Images", inManagedObjectContext: context) as! Image
object.taskid = 'dhjfdsf-31232-321das321-3232'
if ill now use a predicate (and there are lots of data) - makes in ANY performance difference when ill use as filter my RemoteIDs (GUIDs) ?
So should i create not any relations in CoreData (because i dont need them anymore) and do not run in performance issues? Makes that any difference to get all objects in CoreData with a predicate as (String) here, or with an related NSManagedObject?
Thanks in advance!

You can create local as well as remote ids. With local ones, the performance of the app will be good, and remote ones can be used to filter out the local objects w.r.t server.(i.e. to update any particular object, or to delete all local child/relational objects which are no more require with parent model). In this way, your synchronization process will be handled.

Related

Is there a way to access properties of an x-coredata:// object returned from an NSFetchRequest?

TL;DR: Is there a way to programmatically read/recall (NOT write!) an instance of a Core Data entity using the p-numbered "serial number" that's tacked on to the instance's x-coredata:// identifier? Is this a good/bad idea?
I'm using a method similar to the following to retrieve the instances of an Entity called from a Core Data data store:
var managedContext: NSManagedObjectContext!
let fetchRequest : NSFetchRequest<TrackInfo> = TrackInfo.fetchRequest()
fetchResults = try! managedContext.fetch(fetchRequest)
for (i, _) in Global.Vars.numberOfTrackButtons! {
let workingTrackInfo = fetchResults.randomElement()!
print("current track is: \(workingTrackInfo)")
The list of tracks comes back in fetchResults as an array, and I can select one of them at random (fetchResults.randomElement()). From there, I can examine the details of that one item by coercing it to a string and displaying it in the console (the print statement). I don't list the code below, but using workingTrackInfo I am able to see that instance, read its properties into other variables, etc.
In the console, iOS/Xcode lists the selected item as follows:
current track is: <MyProjectName.TrackInfo: 0x60000374c2d0> (entity:
TrackInfo; id: 0xa7dc809ab862d89d
<x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22>;
data: <fault>)
The line beginning with x-coredata: got my attention. It's formatted like a URL, consisting of what I assume is a UUID for the specific Core Data store associated with the current build of the app (i.e. not a stable address that you could hardcode; you'd need to programmatically look up the Core Data store, similar to the functions we use for programmatically locating the Documents Folder, App Bundle, etc.) The third item is the name of the Entity in my Core Data model -- easy enough.
But that last number is what I'm curious about. From examining the SQLite database associated with this data store, it appears to be a sort of "instance serial number" associated with the Z_PK field in the data model.
I AM NOT interested in trying to circumvent Core Data's normal mechanisms to modify the contents of a managed object. Apple is very clear about that being a bad idea.
What I AM interested in is whether it's possible to address a particular Core Data instance using this "serial number".**
In my application, where I'm randomly selecting one track out of what might be hundreds or even thousands of tracks, I'd be interested in, among other things, the ability to select a single track on the basis of that p-number serial, where I simply ask for an individual instance by generating a random p-number, tack it on to a x-coredata:// statement formatted like the one listed above, and loading the result (on a read-only basis!) into a variable for further use elsewhere in the app.
For testing purposes, I've tried simply hardcoding x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22 as a URL, but XCode doesn't seem to like it. Is there some other data Type (e.g. an NSManagedObject?) that allows you to set an x-coredata:// "URL" as its contents?
QUESTIONS: Has anyone done anything like this; are there any memory/threading considerations why grabbing instance names in this manner is a bad idea (I'm an iOS/Core Data noob, so I don't know what I don't know; please humor me!); what would the syntax/method for these types of statements be?
Thanks!
You are quite close.
x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22
is the uriRepresentation() of the NSManagedObjectID of the record.
You get this URL from an NSManagedObject with
let workingTrackInfo = fetchResults.randomElement()!
let objectIDURL = workingTrackInfo.objectID.uriRepresentation()
With this URL you can get the managed Object ID from the NSPersistentStoreCoordinator and the coordinator from the managed object context.
Then call object(with: on the context to get the object.
let persistentStoreCoordinator = managedContext.persistentStoreCoordinator!
if let objectID = persistentStoreCoordinator.managedObjectID(forURIRepresentation: objectIDURL) {
let object = managedContext.object(with: objectID) as! TrackInfo
print(object)
}

What is the way to persist a trie data tree with 27000 nodes in iOS using Swift?

I am building a Trie tree that will have about 27000 of the nodes below. Instead of recreating it every time on app start, I would like to persist. Because the child property is a dictionary to another node, I'm having trouble using NSCoding to archive and store it in the core data entity. Is there a way to store this node in Core Data? Or should I be using a different type of persistence?
class TrieNode {
var letter:Character
var fullWord:Bool
var leadingLetters:String
var child = [Character:TrieNode]()
init (letter:Character, leadingLetters:String, fullWord:Bool) {
self.letter = letter
self.fullWord = fullWord
self.leadingLetters = leadingLetters
}
}
The main problem I had in trying to use Core Data is how to convert var child = [Character:TrieNode]() into NSData or another useable type that CD can store in an entity. Examples on how to do that would be appreciated.
It's a little awkward in Core Data. I think what I'd do is:
Create a new entity called something like TrieNodeLink. It has one property, a string called something like childString and one relationship, called node of type TrieNode. Each instance of this entity represents one single sub-node of a trie node.
Add a new to-many relationship from your existing TrieNode to the new TrieNodeLink entity.
Keep your existing child dictionary. At a convenient time, initialize this dictionary by scanning the new to-many relationship from step 2. A convenient time might be in awakeFromFetch, or else you could make it a Swift lazy property. Or if you want to pre-load data for faster performance at the cost of higher memory use, you might write some code to recursively load child nodes a few levels deep before they're needed.
The effect of this would be that you'd load portions of the trie on demand, when needed. Once loaded you'd be able to use your child dictionary to quickly look up child nodes.

Why I don't get the same objects when using objectWithID with CoreData?

I have a NSManagedObjectContext where two NSManagedObject are saved.
I'm calling a method in another thread and I need to access those two NSManagedObject so I created a child context like the following:
let childManagedContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
childManagedContext.parentContext = self.managedContext
When I do:
let myNSManagedObject1 = childManagedContext.objectWithID(self.myNSManagedObject1.objectID) as! MyNSManagedObject
let myNSManagedObject2 = childManagedContext.objectWithID(self.myNSManagedObject2.objectID) as! MyNSManagedObject
myNSManagedObject1 and myNSManagedObject2 are not the same objects as self.myNSManagedObject1 and self.myNSManagedObject2. Can someone explain me why?
Plus if I use existingObjectWithID instead of objectWithID, it seems I still have a fault object for my relationship in myNSManagedObject1 and myNSManagedObject2:
relationShipObject = "<relationship fault: 0x170468a40 'relationShipObject'>"
Understand that they are the "same" in the sense that they refer to the same object in your object graph. If you compare all attributes, you will find that they are equal.
However, because they are in different contexts, they will be two separate instances of this object. So the machine address you see will be different. I hope that clears up the confusion.
As for the "fault", that only means that the underlying object (or attribute) has not yet been fetched into memory. This is simply an optimization mechanism to minimize memory footprint. If you were to log the object or attribute explicitly, it would be fetched from the store and displayed as expected. See "Faulting and Uniquing" in the Core Data Programming Guide.
You have one object, that is the version that's in Core Data. When you use objectWithID: you create an instance of that object. So, if you do it twice you get two instances of the same object. (Much in the same way that you can create two objects of the same class.)
Of course, if you try to save your context, having changed one but not the other, weird things might happen.
A common pattern is where you create a new "editing" managed object context and create a new instance there. Then if the user pressed Cancel, you can just delete the context and not have to worry about rolling back any changes. I can't think where having two instances on the same context would be useful.

How to get list of existing entities(tables) in core data

How to get list of existing entities (tables) for a particular schema (Managed Object Model) in core data. I just started implementing core data concept and stuck with these points.
Something like:
SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'dbName';
Thanks
You should read through Apple's Core Data Programming Guide. To get the entities for a particular NSManagedObjectModel, you would use one of the following (this assumes you have an NSManagedObjectModel named objectModel):
NSArray *myEntities = [objectModel entities];
// Array of all entities in the model
or
NSDictionary *myEntities = [objectModel entitiesByName];
// Dictionary of entities in the model, with the entity names as keys
You can read more in the NSManagedObjectModel Class Reference.
It appears you're coming from a SQL background (as I was). There are a number of concepts in Core Data that are different - sometimes for the better, once you understand them, sometimes requiring more work than a simple SQL statement you may be used to. I think it's important to approach Core Data without SQL "baggage" and treat it as if you're learning how to use a database for the first time - this will help avoid frustration.
In Swift it would be:
let model: NSManagedObjectModel
let entities: [NSEntityDescription] = model.entities
// or
let entitiesByName: [String: NSEntityDescription] = model.entitiesByName
and to get a list of names
let enititesNames: [String] = entities.compactMap(\.name)

Cross-Store weak relationship with Fetched Properties?

I would like to separate my reference data from my user data in my Core Data model to simplify future updates of my app (and because, I plan to store the database on the cloud and there is no need to store reference data on the cloud as this is part of my application). Therefore, I've been looking for a while for a way to code a cross-store relationship using fetched properties. I have not found any example implementations of this.
I have a Core Data model using 2 configurations :
data model config 1 : UserData (entities relative to user)
data model config 2 : ReferenceData (entities relative to application itself)
I set up 2 different SQLite persistent stores for both config.
UserData config (and store) contains entity "User"
ReferenceData config (and store) contains entities "Type" and "Item".
I would like to create two single-way weak relationships as below :
A "User" has a unique "Type"
A "User" has many "Items"
Here are my questions :
How do I set up my properties?
Do I need 2 properties for each relation (one for storing Unique ID and another to access my fetched results)?
Could this weak relationship be ordered?
Could someone give me an example implementation of this?
As a follow-on to Marcus' answer:
Looking through the forums and docs, I read that I should use the URI Representation of my entity instance instead of objectID. What is the reason behind this?
// Get the URI of my object to reference
NSURL * uriObjectB [[myObjectB objectID] URIRepresentation];
Next, I wonder, how do I store my object B URI (NSURL) in my parent object A as a weak relationship? What attribute type should I use? How do I convert this? I heard about archive... ?
Then, later I should retrieve the managed object the same way (by unconvert/unarchive the URIRepresentation) and get Object from URI
// Get the Object ID from the URI
NSManagedObjectID* idObjectB = [storeCoordinator managedObjectIDForURIRepresentation:[[myManagedObject objectID] URIRepresentation]];
// Get the Managed Object for the idOjectB ...
And last but not least, shouId I declare two properties in my entity A, one for persisting of URI needs and another for retrieving direclty object B?
NSURL * uriObjectB [objectA uriObjectB];
ObjectB * myObjectB = [objectA objectB];
As you can read, I really miss some simple example to implement thes weak relationships ! I would really appreciate some help.
Splitting the data is the right answer by far. Reference data should not be synced with the cloud, especially since iCloud has soft caps on what it will allow an application to sync and store in documents.
To create soft references across to stores (they do not need to be SQLite but it is a good idea for general app performance) you will need to have some kind of unique key that can be referenced from the other side; a good old fashioned foreign key.
From there you can create a fetched property in the model to reference the entity.
While this relationship cannot be ordered directly you can create order via a sort index or if it has a logical sort then you can sort it once you retrieve the data (I use convenience methods for this that return a sorted array instead of a set).
I can build up an example but you really are on the right track. The only fun part is migration. When you detect a migration situation you will need to migrate each store independently before you build up your core data stack. It sounds tricky but it really is not that hard to accomplish.
Example
Imagine you have a UserBar entity in the user store and a RefBar entity in the reference store. The RefBar will then have a fetchedProperty "relationship" with a UserBar thereby creating a ToOne relationship.
UserBar
----------
refBarID : NSInteger
RefBar
--------
identifier : NSInteger
You can then create a fetched property on the RefBar entity in the modeler with a predicate of:
$FETCHED_PROPERTY.refBarID == identifier
Lets name that predicate "userBarFetched"
Now that will return an array so we want to add a convenience method to the RefBar
#class UserBar;
#interface RefBar : NSManagedObject
- (UserBar*)userBar;
#end
#implementation RefBar
- (UserBar*)userBar
{
NSArray *fetched = [self valueForKey:#"userBarFetched"];
return [fetched lastObject];
}
#end
To create a ToMany is the same except your convenience method would return an array and you would sort the array before returning it.
As Heath Borders mentioned, it is possible to add a sort to the NSFetchedProperty if you want but you must do it in code. Personally I have always found it wasteful and don't use that feature. It might be more useful if I could set the sort in the modeler.
Using the ObjectID
I do not recommend using the ObjectID or the URIRepresentation. The ObjectID (and therefore the URIRepresentation of that ObjectID) can and will change. Whenever you migrate a database that value will change. You are far better off creating a non-changing GUID.
The weak relationship
You only need a single value on the M side of the relationship and that stores the foreign identifier. In your object subclass you only need to implement accessors that retrieve the object (or objects).
I would go with just one store.
For storing stuff in the cloud, you will anyway have to serialize the data, either as JSON or SQL statements, or whatever scheme you prefer.
You will need a local copy of the data on the user's device, so he can access it quickly and offline. The cloud store can have only the user entity, while the local store (part of the app) can also have the reference entity.
I have a similar project with a huge reference store (20000 records) with geographic information, and user generated content ("posts"). I use a single store. When I ship the app, the "posts" entity is also defined but empty. When I update the data model I simply re-generate the whole reference store before shipping.
I see absolutely no reason to go for a cross store solution here.

Resources