Core Data Different Object ID For First Time - ios

I am using Entity's Object ID in order to uniquely identify local notifications and modify them. I observed that first time when I save my entity, it has following object ID:
<x-coredata:///Task/tE1C5A230-A419-42D5-AF78-3327A09D13BD2>
If I don't exit my application, and try to modify notification, object ID doesn't change and I can modify my notification.
Now, if I restart my app and try to access that entity again, it has different object ID:
<x-coredata://D6703834-ECB4-487B-84F8-330A215E16B7/Task/p13>
So I can't modify notification, as object ID for entity is different. Interesting thing is whenever I access that entity, Object ID remains same as the last one.
So my Question here is why Core data shows different object ID for the first time entity is created? When I try to access entity after opening app again for many times, the object ID (different than the first one) remains constant. I am curious to know why is it happening so?
Please note:
I know there are many posts on SO pointing out that using Object ID is not a reliable approach. Still I want to know reason that why two IDs are being shown.

the first OID is a temporary OID - a temporary id denotes objects that have not been saved yet. the 2nd id is a permanent one and is assigned to a MO AFTER it has been saved:
so...
var objectID = object.objectID
if objectID.temporaryID {
object.managedObjectContext.save() //try do catch left out
}
objectID = object.objectID
assert(objectID.temporaryID == false)

Related

CoreStore how to fetch or query object from unspecified dynamic object

enter code hereI read this guide which documented pretty good. I need to search through my database and get a record actually execute fetch or query.
Can I search through all records instead of specifying appropriate From clause.
For example
var undefinedObject = CoreStore.fetchAll(
From(GoThroughAllMyDataBaseEntities),
Where("%K == %#", "localId", "some string id")
)
print(undefinedObject.id) // as object will be undefined I need to figure out how to get id property from it.
Side note: all my entities are child objects of parent entity which has id property.
So let's say I have next entities in CoreData:
BaseEntity (which includes id)
Playlist
Song
in the code above I don't care which will be returned to me I just what to see one that matches this condition: Where("%K == %#", "localId", "some string id")
Also my localId property in each objects are very unique strings. They are extracted from NSManagedObjectID.
So there is no way have the same duplicated identifier localy in CoreData
If there is no way to do it, then I will need to loop all my Playlists and Songs records.
managedObjectContext.objectWithID(objectID) may not work thought in some cases as there is no guaranty that CoreData record has not been deleted and app recreated a copy of the same record, so physically a copy of record has another objectID address in CoreData, but still has localID property copied from other record.

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)
}

Access Parse Object ID right after instantiation

I'm creating two PFObjects at the same time that should reference each other's object IDs when they're saved. In the example below, the second object is supposed to save the first object's object ID in an array.
let objectForFirstClass = PFObject(className:"ClassOne")
let objectForSecondClass = PFObject(className: "ClassTwo")
objectForSecondClass.setObject([objectForFirstClass.objectId!], forKey: "classOneObjectArray")
The last line is causing the error because objectForFirstClass.objectId is nil. I'd assume this is because the object hasn't been saved yet. How can I fix this?
You want to save after creating the first object, and in the completion handler, create the second one with a reference to the first one.
You can use saveAllInBackground:block: for this.
Correct, the object id is assigned by the server when saved. I'd be tempted to write some cloud code to do what you want so you can send some details and the cloud code will create and connect the objects, then return both of them to you. You can of course do the same thing locally in your app, there's just more network comms.
You should also consider using pointers or relationships. These are better for querying, though the same save requirements apply before you can set the connections.

Save a Parse Object Causes Another Object Saved Too

I am quite new with Parse, I am confused on how parse works.
Here I have a PFUser, PFGroupObject, PFUserGroupObject. Where PFUserGroupObject is a "Table" that relate user an group.
PFUserGroupObject and PFGroupObject is my custom PFObject
What confuses me is that, once I save the PFUserGroupObject, the PFGroupObject is also saved to Parse.
I have 2 questions:
Is this behaviour normal? I know that PFUserGroupObject has a variable that stores PFGroupObject. But, I am wondering if this is the expected behaviour of Parse.
//MARK - Create a New Group
var group: PFGroupObject = PFGroupObject();
group.name = "Friends Group";
//MARK - Create a Many to Many Join Table to Relate User and Group
var userGroup: PFUserGroupObject = PFUserGroupObject();
userGroup.user = PFUser.currentUser();
userGroup.group = group;
//THIS LINE CAUSES MY GROUP OBJECT TO BE SAVED TO PARSE AS WELL
PFObject.save(userGroup);
Let's say I called
PFObject.saveAll([group, userGroup]);
Would the group object saved twice into Parse?
I am trying to understand how Parse works.
Thank you!!!!!
When you save a "Parent" object that has one or more "child" objects, it will save the children as well. I have never worked with PFGroupObjects, but I have a pointer to a custom object attached to each of my users. Rather than having to save my custom object, then save my user (or vice versa), saving just my user will also save my custom object.
There are some nuances with this, such as trying to add a pointer to an unsaved object which you have just created to another object, then saving the parent. While you generated an object id when you created the child object, you haven't actually saved it, so it's a dangling pointer.
Hope this helps!
edit - If you want to see exactly what is getting saved when, add a before save trigger to your cloud code for the object you want to see whether or not is being saved multiple times, with a console print line inside. Do some testing, and check your logs to see how many times the beforeSave trigger is getting called, and that's how many times you are attempting to save an object of that type.

CloudKit: Preventing Duplicate Records

I am working through an app that pulls data from an external web service into a private CloudKit database. The app is a single user app, however I am running into a race condition that I am not sure how to avoid.
Every record in my external data has a unique identifier that I map to my CKRecord instances. The general app startup flow is:
Fetch current CKRecords for the relevant record type.
Fetch external records.
For every external record, if it doesn't exist in CloudKit, create it via batch create (modification operation).
Now, the issue is, if this process is kicked off on two of a user's devices simultaneously, since both the CK and external fetch is async, there is a strong possibility that I'll get duplicate records.
I know I can use zones to atomically commit all of my CKRecord instances, but I don't think that solves my issue because if all of these fetches happen at essential the same time, the save is not really the issue.
My questions are:
Does anyone know of a way to "lock" the private database for writes across all of a user's devices?
Alternatively, is there a way to enforce uniqueness on any CKRecord field?
Or, is there a way to use a custom value as the primary key, in that case I could use my external ID as the CK ID and allow the system to prevent duplicates itself.
Thanks for the help in advance!
Answers:
No, you cannot lock the private database
Cloudkit already enforces and assumes uniqueness of your record ID
You can make the record ID anything you like (in the non zone part of it).
Explanation:
Regarding your issue of duplication. If you are the one creating the record IDs (from the external records you mentioned for example) then at worst you should have one record over write the other with the same data if you have a race condition. I do not think that is an issue for the extreme case two devices kick off this process at the same time. Basically you logic of first fetching existing records and then modifying them seems sound to me.
Code:
//employeeID is a unique ID to identify an employee
let employeeID = "001"
//Remember the recordID needs to be unique within the same database.
//Assuming you have different record types, it is better to prefix the record name with the record type so that it is unique
let recordName = "Employee-\(employeeID)"
//If you are using a custom zone
let customZoneID = CKRecordZoneID(zoneName: "SomeCustomZone", ownerName: CKCurrentUserDefaultName)
let recordIDInCustomZone = CKRecordID(recordName: recordName, zoneID: customZoneID)
//If you are using the default zone
let recordIDInDefaultZone = CKRecordID(recordName: recordName)
I had similar issue of duplicates downloaded when I tried to read in a database of more than 100 records; the solution is found in the Apple's Atlas example which uses a Boolean to check if the last process finished before it launches the next. You find a block much like this...
#synchronized (self)
{
// Quickly returns if another loadNextBatch is running or we have the oldest post
if(self.isLoadingBatch || self.haveOldestPost) return;
else self.isLoadingBatch = YES;
}
Incidentally here the code to create your own record key.
CKRecordID *customID = [[CKRecordID alloc] initWithRecordName: [globalEOConfirmed returnEOKey:i]];
newrecord = [[CKRecord alloc] initWithRecordType:#"Blah" recordID:customID];

Resources