iPhone core data can I cache NSManagedObjects? - iphone

I'm running a data model in one of my apps, where an event has an "eventType" relationship defined. This allows me to modify the look and feel of multiple events by changing their "eventType" relationship object.
The problem that I'm running into is that before I insert an event, I check if a typeRelationship for this object is present with the code below. This takes some time if I need to insert a large number of objects.
Can I cache the results of this fetch request (for example in NSMutableDictionary) and check that dictionary (local memory) to see if there is an NSManagedObject with the given EventIDEnum? Can I keep the cache alive forever, or will the underlying objects get "out of date" after a while?
-(Event*)insertAndReturnNewObjectWithTypeID:(EventIDEnum)eventTypeID date:(NSDate*)date
{
NSFetchRequest *eventTypesArray = [NSFetchRequest fetchRequestWithEntityName:#"EventType"];
eventTypesArray.predicate = [NSPredicate predicateWithFormat:#"SELF.id == %d", eventTypeID];
NSArray *eventTypes = [[DataManager sharedInstance].managedObjectContext executeFetchRequest:eventTypesArray error:nil];
if(eventTypes.count==0)
{
DLog(#"ERROR inserting event with type: %i NOT FOUND",(int)eventTypeID);
return nil;
}
else {
if(eventTypes.count !=1)
{
DLog(#"ERROR found %i events with type %i",eventTypes.count,(int)eventTypeID);
}
EventType* eventType = [eventTypes lastObject];
if(date)
{
// DLog(#"Returning object");
return [self insertAndReturnNewObjectWithEventType:eventType date:date];
}else {
// DLog(#"Returning object");
return [self insertAndReturnNewObjectWithEventType:eventType];
}
}
}
Thank you for taking a look at my question!

The array of objects returned by a fetch request cannot be cached. They are only valid as long as the NSManagedObjectContext that was used to query them has not been released. The NsManagedObject.objectID and the data you retrieve from the query can be cached and kept for as long as you like. You are probably better off copying the pertinent data and objectIDs into another object you cache and maintain separately from CoreData objects; and releasing the core data array that was returned by the fetch request.

The pattern you're using is often referred to as "find or create": look for an object whose uniquing characteristic matches, return it if it exists, create/populate/return it if it didn't exist.
One thing you can do to speed this up is to do the uniquing outside of Core Data. If it's possible based on your data, perhaps you can iterate over your EventIDEnum values, find the unique values you need to have available, and thus reduce the number of fetches you perform. You'll only search once for each EventIDEnum. As long as you're working within one thread/context, you can cache those.
When I'm writing this kind of code, I find it helpful to pass in the NSManagedObjectContext as a parameter. That allows me to use the find-or-create or bulk insert methods anywhere, either on the main thread or within a private queue/context. That would take the place of your [[DataManager sharedInstance] managedObjecContext] call.

Related

CoreData is it possible to safely use NSManagedObject from one context/thread in another context/background task

Is it possible to use NSManagedObjects between different background Task?
I have such code that when executed loads NSManagedObjects using load() method on one context/background task using inside .performBackgroundTask { context
Then I want to execute another .performBackgroundTask { context but the problem is that NSManagedObject seems to be there unavailable as all properties starting from .performBackgroundTask { stop returning fields
self.load(predicate: NSPredicate(format: "company.id = %#", companyId))
.flatMap { contacts in
Future { promise in
self.storage.persistentContainer.performBackgroundTask { context in
let request : NSFetchRequest<Company> = Company.fetchRequest()
request.predicate = NSPredicate(format: "id = %#", companyId)
let result = try? context.fetch(request)
I recommend you to read Core Data concurrency overview to understand the basics.
Regardless of your problem...
You can't directly pass Managed Objects between contexts/threads.
What you can do is to pass objectIDs off objects from one context and use them to fetch the objects from another context/thread.
Performance-wise it might not be a good idea if there are many objects and I recommend you to fetch both contracts and companies in one performBackgroundTask, using a single context. You may pass the context as a parameter to your load function if you don't want to expose its logic.

KVC Collection Accessor + index 0 beyond bounds for empty array error

I have a mutable array property declared and synthesized:
#property (nonatomic, strong) NSMutableArray *arrayOfTasks;
I am using KVC collection Accessors for the same property and also I have other methods which will internally call this KVC Collection accessor method like this:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
I had to do some modifications and add the object into the array only when a particular condition is satisfied, so to make sure that this check goes into the designated method, I included the following in the -insertObject KVC Collection accessor method:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
if ([inTask isOperatable])
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
Now each time when I trigger -addObjectINArrayOfTasks method and if the -isOperatable condition returns boolean NO, the app crashes with no stack trace at all! (Stack trace is at main() of the application). All it says is "index 0 beyond bounds for empty array error".
I am not understanding the reason for this, I am not trying to access the array yet, so I am not giving a chance for framework to complain me that there is no element at index 0. Moreover, I am doing the count of array items check everywhere before accessing the objects out of array. For, if I was trying to access and element out of the bounds index, the app would crash at the same point and let me know exactly where I was trying to access the index out of bounds. That would have been a simple straightforward fix.
Now, to just cross verify, I made a small change in the code like this, which seems to work:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
if ([inTask isOperatable])
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
}
I can go ahead with this approach which is working and does not crash, but my concerns are the following:
Adding the same check in designated method would be an added advantage in future if some other programmer would want to invoke the designated method from somewhere else.
Why would the app crash in first case when I wont insert the object into the array in KVC collection accessors based on some condition check?
Thanks for any inputs,
Raj
I think the crash you are seeing is more likely related to internal KVC behavior than your array. That might be the reason you don't see a usable stack trace. Have you enabled the exception breakpoint in Xcode?
KVC basically expects that -insertObject:in<Key>AtIndex: will insert a new object at the given index (presumably 0 in your case). Since it assumes that the object was inserted it should now be accessible by queuing the data structure (NSMutableArray) for the object at the given index. When the condition evolves to NO, you fail to insert this object, which means that an index out of bounds exception is possible when KVO tries to query using the provided index.
The second code snipped you posted avoids this error by not calling the KVC collection accessor when an insertion is not needed.
If you want to minimize the chance of someone incorrectly using those methods, expose just -addObjectInArrayOfTasks: in your public header. In addition you can document this. If you want to make it absolutely certain that -insertObject:in<Key>AtIndex: can't be accessed on int's own, you can add an NSAssert, that checks if the method was called from -addObjectInArrayOfTasks:.

NSMutableArray Comparison

I have two NSMutableArrays. One array consists of records from the database and the other array consists of records from webservices.
I want to compare each record from the database array to each record in the web services array using a unique key like barcodeID. Also, if the barcodeID key is same then I want to remove the item from the array. It's like I'm updating my database records. If we get the same records from the webservice then I don't want to insert them.
Please help me I'm unable to break the logic for this.
if Product.barcodeID uniquely identifies your objects, then you can use that member to implement -[Product hash] and -[Product isEqual:].
then you can easily use Product in NSSets. NSSet and NSMutableSet contain several methods to combine and remove sets.
The brute force method of doing such comparison is for every record in one array is checked with every record in another. If you find it then stop and discard the object. if you do not find it, then you add it to the array. This of course will have a very high time complexity with a worse case scenario is O(n^2). you could shorten this down by using certain data structures inside your database and web service. Maybe storing them in sorted order or through some algorithm.
You should do some research yourself before asking this question. I shall leave you the option to find a way to optimize your code.
Good luck!
This is kind of the idea of the brute force method. As mentioned above this is incredibly slow compared to alternatives.
- (void)myUpdateFunction
{
NSMutableArray *baseDatabaseArray;
NSMutableArray *baseWebServiceArray;
for (int i = 0; i < baseWebServiceArray.count; i++) {
id value = [[baseWebServiceArray objectAtIndex:i] valueForKey:#"barcodeID"];
NSArray *array = [baseDatabaseArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"barcodeID = %#", value]];
if (array.count > 0)
{
id obj = [array objectAtIndex:0];
[baseDatabaseArray removeObject:obj];
}
[baseDatabaseArray addObject:[baseWebServiceArray objectAtIndex:i]];
}
}
I have been using Magical Record and love it.
You have to be using Core Data for this though. Here is what my update code looks like with Magical Record.
- (void)updateDatabase
{
Class class = NSClassFromString(self.managedObjectClassName);
if ([class MR_countOfEntities] > 0) {
for (NSArray *array in self.totalBatches) {
[class MR_updateFromArray:array];
}
} else {
for (NSArray *array in self.totalBatches) {
[class MR_importFromArray:array];
}
}
[self.totalBatches removeAllObjects];
}
If you have any questions about Core Data feel or if you need me to walk through the algorithms feel free to ask.

Core Data Wont Persist Property Updates to Database

I'm working with a subclass of NSManagedObject. Actually, it inherits from a class that inherits from a class that itself inherits from NSManagedObject (that shouldn't be a problem, right?).
The problem
After I make changes to the properties of the object, the object remembers the changes for its lifetime, but the changes are never saved to the database.
How Do I Know This?
I know this because:
when I restart the app, the changes I've made are lost.
telling the context to refresh the object – AFTER I've made changes to the object and told the context to save – sets the object's values back to their original state before I made the changes.
when running the app in the simulator, I can look at the sqlite database file in the Finder, and it's modified date isn't updated when I attempt to save the context.
Nothing is being written to the database!
Context
I'm using the auto-generated delegate methods to create the store coordinator and the context. Then I'm passing the context to the view controllers in their init methods, as recommended in the docs. The store is SQLite.
I am able to successfully insert objects into the database and read them. I can even make property changes to the newly inserted object and save it successfully. I simply don't seem to be able to update object properties when the object is pulled back out of the database.
The object is fetched from the store via a relationship from another object. After making changes to its properties, I call the context's save method. However, before doing so, I call the object's isUpdated method and the context's hasChanges method, and both return false. Shouldn't they return true since I've just made changes to the object's properties but haven't saved the context?
More
If I call the object's committedChanges method before saving the context, however, passing in the names of the properties that I've changed, I get back the correct values of the properties. I'm not sure what this means. I would have thought that this means that the object's new property values have been successfully saved, but clearly they are not saved.
I know that the result objects is registered with a context. If I call
[[result managedObjectContext] refreshObject:result mergeChanges:YES];
the object reverts back to the original property values. This means that the context is there and that it is the same context from which the record was fetched. And it means that the new property values are never written tot he database.
Some Code
Here's the code where I'm poking around with all of these things. There are other places in my code where I'm making property changes, but the changes are never saved.
- (IBAction)statusControlChanged:(UISegmentedControl *)control {
WCAAssessmentResult *result = [self currentResult];
/* printing the existing property values */
if (![result.complete boolValue]) NSLog(#"result is in progress!");
else if ([result.passed boolValue]) NSLog(#"result is passed!");
else NSLog(#"result is not passed!");
/* changing the property values */
switch (control.selectedSegmentIndex) {
case 0:
NSLog(#"setting incomplete");
result.complete = [NSNumber numberWithBool:NO];
break;
case 1:
NSLog(#"setting passed");
result.passed = [NSNumber numberWithBool:YES];
result.complete = [NSNumber numberWithBool:YES];
break;
case 2:
NSLog(#"setting failed");
result.passed = [NSNumber numberWithBool:NO];
result.complete = [NSNumber numberWithBool:YES];
break;
default:
break;
}
/* this method always returns an empty dictionary */
NSLog(#"%#", [result changedValues]);
/* this method returns the values that I just set */
NSLog(#"%#", [result committedValuesForKeys:[NSArray arrayWithObjects:#"complete", #"passed", nil]]);
/* isUpdated returns false */
if (![result isUpdated]) {
NSLog(#"result is not updated?! WTF!?!?");
}
/* hasChanges returns false */
if (![[result managedObjectContext] hasChanges]) {
NSLog(#"context has no changes!? WTF!?!?");
}
/* saving the context produces no error */
NSError *error = nil;
if (![[result managedObjectContext] save:&error]) {
NSLog(#"save failed");
NSLog(#"%#",[error description]);
}
}
A Twist
If I create a new result object by inserting a new record into the context, I can set that object's properties and they are saved successfully. In the above code, I'm fetching the object as a member of a to-many association from another object. Is that a clue?
I'm tearing my hair out over this. What the hell could be going wrong here?
What's NOT The Problem
I've logged the object's class, and it is indeed the correct class
I've made sure that the managedObjectContext I'm saving is the same as the object's context
I haven't made any changes to the auto-generated setter/getter methods of my managed object classes
I've tried using the setValue:forKey: method instead of object's properties
I've used the -com.apple.CoreData.SQLDebug 1 argument to log Core Data SQL, and no SQL is logged when I update and save the object's properties
I do not really understand your statement
WCAAssessmentResult *result = [self currentResult];
Indeed, if you are accessing a to-many relationship from an object, you should get back a set, not an object. Anyway, without seeing the code it's hard to tell. The problem you are experiencing may or may not lie there.
I would rather expect in your code something like the following snippet to access objects belonging to a to-many relationship. I assume that yourObject is the object you use to access the WCAAssessmentResult objects in the to-many relationship, which I call results.
NSMutableSet *resultObjects = [yourObject mutableSetValueForKey:#"results"];
NSPredicate *predicate = ...
[resultObjects filterUsingPredicate:predicate];
for(WCAAssessmentResult *result in resultObjects){
// modify as needed the current result object
}
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"save failed");
NSLog(#"%#",[error description]);
}
Did you verify that the managedObjectContext you are using to save the object is valid (not nil) ?
Some ideas in no particular order:
I would log the class of the result object and make sure it is the class you think it is. Some confusion with super/sub classes could result in certain values not being saved.
If you made any alterations in the setter/getter methods in any class in the hierarchy, look closely at those methods especially if you used a primativeValue method. Simply leaving out willChangeValue and didChangeValue can cause changes to be invisible to the context and sometimes to the object itself.
I would log the context you are saving as well as managedObjectContext property of the result object. Confirm they are indeed the same context.
Skip using the property accessors (the dot notation) and use setValue:forKey and see if that makes any difference. If so, you have an accessor problem. Likewise, you could try setPrimativeValue:forKey to also check for an accessor problem.
If I had to bet, I would put my money on you assigning the objects returned by a fetch to the wrong class.

Arbitrary Attributes in Core Data

In short, I want to associate arbitrary key/value pairs with the objects of a Core Data entity, on an iPad app.
My current solution is to have a to-many relationship with another entity that represents a single pair. In my application, I have:
Entry <--->> ExtraAttribute
where ExtraAttribute has properties key and value, and the key is unique to the ExtraAttribute's Entry.
Although the code to deal with this is slightly complicated, it is acceptable. The real problem comes with sorting.
I need to sort those Entries that have a given ExtraAttribute by that attribute. Using the SQL store, it is apparently impossible for Core Data itself to sort the Entries by the value of the associated ExtraAttribute with a given key. (Frustrating, since this is possible with the other stores, and trivial in SQL itself.)
The only technique I can find is to sort the entries myself, then write a displayOrder attribute back to the store, and have Core Data sort by the displayOrder. I do that with the following class method on Entry. (This uses a some methods and global functions not shown, but hopefully you can get the gist. If not, ask and I will clarify.)
NSInteger entryComparator(id entry1, id entry2, void *key) {
NSString *v1 = [[entry1 valueForPropertyName:key] description];
NSString *v2 = [[entry2 valueForPropertyName:key] description];
return [v1 localizedCompare:v2];
}
#implementation Entry
...
// Unified builtin property and extraAttribute accessor;
// expects human-readable name (since that's all ExtraAttributes have).
- (id)valueForPropertyName:(NSString *)name {
if([[Entry humanReadablePropertyNames] containsObject:name]) {
return [self valueForKey:
[Entry propertyKeyForHumanReadableName:name]];
} else {
NSPredicate *p = [NSPredicate predicateWithFormat:
#"key = %#", name];
return [[[self.extraAttributes filteredSetUsingPredicate:p]
anyObject] value];
}
}
+ (void)sortByPropertyName:(NSString *)name
inManagedObjectContext:(NSManagedObjectContext *)moc {
BOOL ascending = [Entry propertyIsNaturallyAscending:name];
[Entry sortWithFunction:entryComparator
context:name ascending:ascending moc:moc];
[[NSUserDefaults standardUserDefaults]
setObject:name
forKey:#"entrySortPropertyName"];
}
// Private method.
+ (void)sortWithFunction:(NSInteger (*)(id, id, void *))sortFunction
context:(void *)context
ascending:(BOOL)ascending
moc:(NSManagedObjectContext *)moc {
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Entry" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSError *error;
NSArray *allEntries = [moc executeFetchRequest:request error:&error];
[request release];
if (allEntries == nil) {
showFatalErrorAlert(error);
}
NSArray *sortedEntries = [allEntries
sortedArrayUsingFunction:sortFunction context:context];
int i, di;
if(ascending) {
i = 0; di = 1;
} else {
i = [sortedEntries count]; di = -1;
}
for(Entry *e in sortedEntries) {
e.displayOrder = [NSNumber numberWithInteger:i];
i += di;
}
saveMOC(moc);
}
#end
This has two major problems:
It's slow, even with small data sets.
It can take an arbitrarily large amount of memory and hence crash with large data sets.
I'm open to any suggestions that are easier than ripping out Core Data and using SQL directly. Thanks so much.
EDIT Thank you for your answers. Hopefully this will clarify the question.
Here is a typical data set: There are n Entry objects, and each one has a distinct set of key/value pairs associated with it. Here I am listing the key/value pairs under each entry:
Entry 1:
Foo => Hello world
Bar => Lorem ipsum
Entry 2:
Bar => La dee da
Baz => Goodbye cruel world
Here I want to sort the entries by any of the keys "Foo", "Bar", or "Baz". If a given entry doesn't have a value for the key, it should sort like an empty string.
The SQLite store cannot sort by an unknown key using -valueForUndefinedKey:; attempting to do so results in an NSInvalidArgumentException, reason keypath Foo not found in entity <NSSQLEntity Entry id=2>.
As noted in the documentation, only a fixed set of selectors will work with sort descriptors using the SQL store.
EDIT 2
Suppose there are three instances E1, E2, and E3 of my entity, and the user attaches the custom properties 'Name' and 'Year' to each of these instances. Then we might have:
E1 Bob 2010
E2 Alice 2009
E3 Charles 2007
But we wish to present these instances to the user, sorted by any of these custom properties. For example, the user might sort by Name:
E2 Alice 2009
E1 Bob 2010
E3 Charles 2007
or by Date:
E3 Charles 2007
E2 Alice 2009
E1 Bob 2010
and so on.
First question is, why do you need to store the sort in the database? If you are alway sorting in the key property, just use a sort descriptor whenever you need to access them in a sorted order.
Second question, why are you writing your own sort routine?
This design seems rather complicated. I understand the need for arbitratary storage of key value pairs, I designed a similar system in my book. However I am unclear as to the need for sorting those values nor the need for a custom sort routine such as this one.
If you could explain the need behind the sorting I could probably suggest a better strategy.
Also, I would highly recommend looking into the two methods -valueForUndefinedKey: and -setValue: forUndefinedKey: as a cleaner solution to your issue. That would allow you to write code like:
[myObject valueForKey:#"anythingInTheWorld"];
[myObject setValue:someValue forKey:#"anythingInTheWorld"];
and follow proper Key-Value Coding rules.
Update
The -valueForUndefinedKey: design is only for use in code, it is not for use accessing the store. I am still a little unclear with your goals.
Given the following model:
Entity <-->> Property
In this design, Property has two attributes:
Key
Value
From here you can access any property on Entity via -valueForUndefinedKey: because under the covers, Entity will go out and fetch the associated Property for that key. Thus you get dynamic values on your Entity.
Now the question of sorting. With this design, you can sort directly on SQLite because you are really sorting on the Property entity. Although I am still unclear as to the final goal of the sorting. What value does it have? How will it be used?
Update: Design reconsidered
The last design I proposed was wrong. On deeper reflection, it is simpler than I proposed. Your goal can be accomplished with the original Entity <-->> Property design. However there is a bit more work to be done in the -setValue: forKey: method. The logic is as follows:
External code called -setValue: forKey: on an Entity.
The -setValue: forKey: method attempts to retrieve the Property.
If the Property exists then the value is updated.
If the Property does not exist then a Property is created for each Entity with a default value set (assumed to be an empty string).
The only performance hit is when a new key is introduced. Other than that it should work without any performance penalties.