CoreData: Checking for Null - iphone

I have a CoreData entity (waypoint) with a foreign key to another entity (track). When the foreign key is set, or if it is not set, this if statement works fine:
if ([wp track]) {
thirdLabel.text = [NSString stringWithFormat:#"Part of Track: %#", [[wp track] title]];
}
However, if the track that the waypoint is keyed to has been deleted, then [wp track] still evaluates to true, and the program crashes when I set the label text.
So, how do I properly check for this "has been deleted" null value in CoreData?

It sounds like you have an issue with a one-way relationship. The problem you are describing is given in more detail here under "Unidirectional Relationships"
Basically, your waypoint has no way to know that the track was deleted out from under it. The recommended solution is to model the relationship to be bi-directional, which allows Core Data to maintain consistency in your model.
In your specific example, if your 'track' object has an inverse relationship back to the 'waypoint', then when you delete the 'track' object, Core Data will know to update the waypoint to get rid of any dangling relationships. More about this can also be seen at the above link.

It is valid Objective-C to send messages to nil. You'll need to modify your if predicate to read:
if (wp != nil && [wp track]) {
//...
}
You can also check the retainCount of an object, which might return 0 or crash in the case when the object has been deallocated.
In either case, it might be best to send out some kind of notification from whomever destroyed wp to those interested in using it, or at least retain wp until after you're done with it in this code.

Related

When deleting a CoreData Object, how to also delete all of its related Objects in Swift5?

I am currently working with CoreData.
Problem: I have a CoreData Entity User with a one-to-many-relationship to another CoreData Entity Badges. I am now trying to delete a User and, obviously, also would like to delete all of his Badges.
Deleting the User itself is pretty straight forward:
context.delete(selectedUser)
However I have to first delete all of the User's Badges. This is the complicated Part for me:
for badge in selectedUser.badges {
context.delete(badge)
}
When doing so, this Error occurs: Cannot convert value of type 'NSSet.Element' (aka 'Any') to expected argument type 'NSManagedObject'
My Possible Solution: I was thinking of simple downcasting: context.delete(badge as! NSManagedObject). However I am not sure whether this is possible.
Question: What is the best Practice for achieving the Goal I described above? Or is there maybe a CoreData way to recursively delete all related Objects?
Thanks for your help.
selectedUser.badges is an NSSet of Badges, therefore you can cast its elements to Badge or to NSManagedObject:
for badge in selectedUser.badges {
context.delete(badge as! NSManagedObject)
}
You can also cast the NSSet to its Swift counterpart Set:
for badge in selectedUser.badges as! Set<Badge> {
context.delete(badge)
}
But in order to delete all related objects if a user is deleted, the simple solution is to set the “Deletion Rule” for the relationship to “Cascade”.

Check if CoreData attribute is empty

I am hoping to find a way to check, if a CoreData attribute is empty. The attribute itself is of type binary data. If the attribute is empty then I could tell my class to download and save some data into this attribute.
According to CoreData Documentation, you should not keep fetching to see if objects exists. I am wondering if there is even a way to possibly do this? without breaking this 'law'?
This is my first attempt at using CoreData. I am adding it to my code afterwards, which is slightly more painful, but as a whole so far everything seems to be going okay. I just need to figure out a logical way of checking if attribute has values. If it doesn't then I need to download and save the new data, if it does then I just use what's in the attribute.
Update :
I just found this method in the CoreData framework that I have been reading though trying to catch a break on this. Not sure if it would help.. what do you guys think?
willAccessValueForKey: Provides support for key-value observing access
notification.
(void)willAccessValueForKey:(NSString *)key Parameters key The name of one of the receiver's properties. Discussion See
didAccessValueForKey: for more details. You can invoke this method
with the key value of nil to ensure that a fault has been fired, as
illustrated by the following example.
[aManagedObject willAccessValueForKey:nil];
Not sure really.. the things that I dont understand is Provides support for key-value observing access notification. ???
That notification is for when the value is going to be accessed.
If I understand you correctly, you are not wanting to see if an entity exists, but an attribute within the entity. So, I assume you have it marked as an optional attribute.
Let's say you have a binary data attribute called rawData. If you want to find all the #"MyEntity" objects in the database that do not have any data set for this attribute, you cn issue this fetch request.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"MyEntity"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"rawData = nil"];
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:0];

How to validate the values in the context before saving to core data

How can we validate the context before saving it to core data?. My idea is i should have some validation before saving it into core data, if the values doesn't satisfy the validation the the coredata should not save the values. Say for example i have attributes like name, class, age, etc for entity candidate. I should have a validation that the values shouldn't be nil. If it is nil then the other values should not be saved.
Can anybody help me in this regard
EDITED:
I need to check them only at the time of saving and that should be done with core data
I like to do catchall validation in the save routine. Before you actually do the call to save the context, loop through its insertedObjects array and make sure they are as you require. If they aren't, you can either delete them or alert the user that they need to complete something (if the latter, return out of the method; give the user a chance to fix the problem).
Other validation should be at the point of entry, when you are getting values from, say, a textfield or checkbox to assign to your managed objects. Let the user know right away if there's a problem.
Also check out NSNumberFormatter, which can be applied to fields, preventing user from making incorrect entries to begin with.
Coredata validate itself when inserting its values. In managedObject class we can write our custom validation so that coredata will check that validation before saving the values. If the value is not valid then those values in the context will not be saved to coredata.
Here i added
#interface
-(BOOL) validateForInsert:(NSError **)error;
#implementation
-(BOOL) validateForInsert:(NSError **)error {
// check the value of the field with validation
if(condition == true) {
return Yes;
}
return NO;
}
(NSError **) is a special parameter that makes the coredata to call this method as if like a delegate method
Sorry, I hadn’t read your question carefully enough when I made that first answer. You’re not validating that individual entries for individual attrs are correct, rather, that no changes should be saved unless all attrs are filled for that object.
Looking at Apple doc “Model Object Validation”, you are concerned with inter-property validation, not property validation, and you are on the right track to be thinking of using validateForInsert: for this purpose.
That doc also supplies examples. Here’s a possible implementation for the particular entity you describe:
- (BOOL)validateForInsert:(NSError **)error {
if (self.name && self.class && self.age)
return [super validateForInsert:error];
else
return NO;
}
However, this method happens at the insertion stage, not at the save stage.
If you are gathering entries for a new entity all at once, validating at the insertion stage would make sense — don’t add a new object to the context if that object is doomed to be discarded as incomplete.
If you are gathering entries for changes to an existing object, and you want to make sure that all those changes work together before accepting any of them, validateForUpdate: would make sense — but there would be no way to restore the object to its original state other than by reopening the context without saving, unless you had cached its original values elsewhere.
If you want to gather attrs individually and wait to check that they are all complete before saving the object, I think you would do as I first suggested: Loop through the context’s insertedObjects and take care of validation before actually saving the context. There’s no existing validateForSave: method to override, but you could add one.
You could also combine these techniques: Gather entries and make new objects without inserting them, but cache all these objects in an array. When it comes time to save, loop through the cache and insert the objects into the context only if they pass validateForInsert:; then save the context.
Obviously I’m learning along with you, so the above might not be quite the cookie. Hopefully that Apple doc will be enough to get you started.

How do I set default values on new properties for existing entities after light weight core data migration?

I've successfully completed light weight migration on my core data model.
My custom entity Vehicle received a new property 'tirePressure' which is an optional property of type double with the default value 0.00.
When 'old' Vehicles are fetched from the store (Vehicles that were created before the migration took place) the value for their 'tirePressure' property is nil. (Is that expected behavior?)
So I thought: "No problem, I'll just do this in the Vehicle class:"
- (void)awakeFromFetch {
[super awakeFromFetch];
if (nil == self.tirePressure) {
[self willChangeValueForKey:#"tirePressure"];
self.tirePressure = [NSNumber numberWithDouble:0.0];
[self didChangeValueForKey:#"tirePressure"];
}
}
Since "change processing is explicitly disabled around" awakeFromFetch I thought the calls to willChangeValueForKey and didChangeValueForKey would mark 'tirePresure' as dirty.
But they don't.
Every time these Vehicles are fetched from the store 'tirePressure' continues to be nil despite having saved the context.
I finally figured it out after 6 months.
The attributes that are added to a core data entity have to be marked as non-optional.
Only then will the default values be set automatically during lightweight migration for the entities that were created with the old data model.
You need to use setPrimativeValueForKey in awakeFromFetch because the dynamic accessor used by self.propertyName is not yet active.
However, since the default value is not appearing in the first place, that suggest your migration failed in detail. You may need to create a migration map to ensure the migration is completely successful.

setPropertiesToFetch method appears to always make request return faulted objects

Whenever I use setPropertiesToFetch on an NSFetchRequest, the objects that are returned appear to all be faulted. They return YES to isFaulted and when displaying them in the console they appear like this:
< MyEntity: 0x5884850> (entity: MyEntity; id: 0x5886180 <x-coredata://4D96A1CB-187C-4D92-A50C-D639F7E69114/MyEntity/p197> ; data: <fault>),
Whereas if I don't set properties to fetch, every object's properties are visible in the console. Any idea why this is happening?
I've got an answer from the Apple Developer Forums:
https://devforums.apple.com/message/152330#152330
"The object being a fault means it might require a trip to the database, or it may mean that the object is pending lazy initialization and all the necessary data is cached. Tracking your app with the Core Data template in Instruments is the best way to empirically tell the difference.
For setPropertiesToFetch, you are intentionally not fetching all the properties, so you end up with a partial fault. Accessing some properties (the ones you told it to fetch) will work, and accessing others will require a round trip to the database. -isFault is a boolean, so most of the framework treats partial faults like they are just faults."
By default, Core Data will fetch objects as faults the first time they're loaded into the managed object context. It won't populate the attributes until you ask for them. If you need a fetch request to return non-faulted objects, you can use setReturnsObjectsAsFaults:YES
The documentation says that the value from -setPropertiesToFetch: is only used when resultType is set to NSDictionaryResultType.