Bizarre Core Data behavior when fetching unsaved data - core-data

I have a Core Data importer that loops through data, and ignores duplicate records during the import process.
But I have discovered that my NSFetchRequest is not matching against recently stored records that have not been saved. And I am seeing seemingly identical queries delivering different and unexpected results.
For example, in my testing, I discovered that this query matches and returns results:
fetchTest.predicate = [NSPredicate predicateWithFormat:#"%K = 3882", #"intEmployee_id"];
But this seemingly identical one does not:
fetchTest.predicate = [NSPredicate predicateWithFormat:#"%K = %#", #"intEmployee_id", #"3882"];
But - they both match identically after the context is saved to the persistent store.
Apple's documentation says that fetches should work against pending changes by default, and indeed I have conformed that [fetchTest includesPendingChanges] = YES.
Any ideas what on earth is going on here? How is it possible that those two fetches return different results?

Maybe the employee id is not a string but a number? Then the predicate should be:
[NSPredicate predicateWithFormat:#"%K = %#", #"intEmployee_id",
[NSNumber numberWithInt:3882]];
This would imply that the erratic behavior comes from mixing up the types. It still works somehow, even if erratically, because in the SQLite docs it says that SQLite actually does not really distinguish by type when storing the data physically.
See Distinctive Features of SQLite from the SQLite web site, under the heading Manifest Typing.

These actually don't evaluate to the same value.
[NSPredicate predicateWithFormat:#"%K = 3882", #"intEmployee_id"]
evaluates to intEmployee_id = 3882 while
[NSPredicate predicateWithFormat:#"%K = %#", #"intEmployee_id", #"3882"]
evaluates to intEmployee_id = "3882"
Try using a number instead of a string for the id.

Related

Making multiple NSUserActivity instances searchable

I'm planning to make some of my app content publicly indexable, and for that I am using NSUserActivity. From my experiments so far, I've discovered that apparently the only activity that appears in the search results is the last one to get becomeCurrent called on. Is there a way to make all my activities searchable?
The following code is on my appDelegate:
for (Shop* shop in shopManager)
{
NSUserActivity* activity = [[NSUserActivity alloc] initWithActivityType:ACTIVITY_OPEN_SHOP];
activity.userInfo = #{#"additional1": shop.name};
activity.eligibleForPublicIndexing = YES;
activity.eligibleForSearch = YES;
activity.keywords = shop.indexableKeywords;
CSSearchableItemAttributeSet* attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString*)kUTTypeText];
attributeSet.title = shop.name;
attributeSet.contentDescription = shop.indexableDescription;
attributeSet.keywords = [shop.indexableKeywords allObjects];
[activity setContentAttributeSet:attributeSet];
[activity becomeCurrent];
[activities addObject:activity];
}
self.userActivities = [[NSSet alloc] initWithArray:activities];
Hey I have code that is very similar to yours and spotlight is able to index all of my NSUserActivity objects. My guess is that your NSUserActivity objects go out of reference as soon as the next iteration of the loop occurs. Try adding a strong property.
From this source on Apple Forums:
https://forums.developer.apple.com/message/13640#13640
In my case, I had code that was allocating the NSUA, setting some
properties on it, calling becomeCurrent, but then the object would go
out of scope and deallocated. If you're doing this, try tossing the
activity into a strong property to see if you can then see the results
when you search.
Let me know if it still doesn't work.
RequiredUserInfoKeys is the property of NSUserActivity that you have to set in order to work properly in search results.
activity.requiredUserInfoKeys = [NSSet setWithArray:#[#"additional1"]];
I have met the same problem. I think the reason of this is that previous user activity has not enough time for indexing its metadata by system, the next user activity have became current user activity, so only last one is searchable.
My solution is put latter one into a dispatch_after block and delay 1.5 second, making each of them has time to be indexed.
If someone has a better solution, I would be grateful.

How do you correctly update a model in Xcode4 without corrupting it?

I never had any problems with Xcode3, but with Xcode4 I'm getting Apple's code failing approx 1 time in 3 when I update a core data model, with the dreaded "Persistent store migration failed, missing source managed object model." error.
Here's my setup (how I configured the project to auto-migrate):
NSPersistentDocument, from Apple's template
Override Apple's model-loading method, and the ONLY thing I do is to provide the two flags in the storeOptions Dictionary, which turn on auto-migration
-(BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url ofType:(NSString *)fileType modelConfiguration:(NSString *)configuration storeOptions:(NSDictionary *)storeOptions error:(NSError **)error
{
NSMutableDictionary *newOptions = nil;
if( storeOptions != nil )
newOptions = [NSMutableDictionary dictionaryWithDictionary:storeOptions];
else
newOptions = [NSMutableDictionary dictionary];
[newOptions setValue:#"YES" forKey:NSMigratePersistentStoresAutomaticallyOption];
[newOptions setValue:#"TRUE" forKey:NSInferMappingModelAutomaticallyOption];
BOOL success = FALSE;
success = [super configurePersistentStoreCoordinatorForURL:url ofType:fileType modelConfiguration:configuration storeOptions:newOptions error:error];
return success;
}
Here's the process I've been using (which is already working around 1 bug in Xcode4!)
Select the model (named "something.xcdatamodel" in Xcode4, with a twisty on the left)
Go to Editor menu, select "Add new model version..."
Name the new version 1 integer higher than last - e.g. if previous was "4" name the new one "5"
In the right-hand pane, change the current model version to the newly-created one
workaround for XCode4 bug: select any file, then select the newly-created model. If you do not, Xcode shows the selection on the newly-created model, but will edit the previous model instead, which definitely corrupts everything in CoreData
Edit your model; in this case, I'm adding a new attribute to an existing entity
Save. Build. Run. ... CRASH.
Except, as I said, approx 2 times in 3 this works correctly. Once it works once, it's (obviously) fine - the lightweight migration is complete, the next save saves in the new model version.
So I'm guessing there's something I'm doing wrong in the above steps, but I've been through the docs 5 or 6 times and can't see anything obvious. Doesn't help that NSPersistentDocument docs are all out of date - but I've done lightweight migration on iPhone lots of times too, so I'm reasonably confident with doing this, and it seems right to me.
Other things I've tried/checked:
- iPhone Core Data Lightweight Migration Cocoa error 134130: Can't find model for source store (nope; only the root xcdatamodel was being included)
Use [NSNumber numberWithBool:YES] not #"YES" or #"TRUE".
Since you have eliminated a corrupt development store as a source of the problem, I suspect the problem lays in Xcode 4.x which is buggy to say the least. A lot of people are reporting similar issues but no two problems seem exactly the same. It is probably a bug/s that only occur with specific data model setups so the problem will be very hard to track down.
You may simply have to abandon automatic migration and create an explicit migration map. It takes longer and introduces complexity into your code but it will always work.
If you have a shipping app and will be dealing with end user data in the wild, you do have a moral and business obligation to take the extra step to protect end user data.
I was getting super-confused but this, and it WASN'T working.. because I was assuming that the method would already HAVE a "store options" dictionary.. I just needed to check for it's existence before i set the aforementioned options…
-(BOOL)configurePersistentStoreCoordinatorForURL: (NSURL*)u
ofType: (NSString*)t
modelConfiguration: (NSString*)c
storeOptions:(NSDictionary*)o
error: (NSError**)e
{
return [super configurePersistentStoreCoordinatorForURL:u
ofType:t
modelConfiguration:c
storeOptions:
o ? [o dictionaryWithValuesForKeys:
#[ NSMigratePersistentStoresAutomaticallyOption, #YES,
NSInferMappingModelAutomaticallyOption, #YES]]
: #{ NSMigratePersistentStoresAutomaticallyOption :#YES,
NSInferMappingModelAutomaticallyOption :#YES}
error:e];
}

Core Data NSFetchedResultController not updating on object change?

I have a NSFetchedResultController with this predicate set:
NSPredicate* pred = [NSPredicate predicateWithFormat:#"author != %# && deleted != %#", [NSNumber numberWithLongLong:0],[NSNumber numberWithBool:YES]];
It filters fine on startup, and I get a delegate callback and list updates fine if objects are added or deleted.
But if I change the "deleted" field, the NSFetchedResultController set is NOT updated, nor do I get the callback.
Though, the actual object in the NSFetchedResultController is updated, if I do a "reloadData" on my table and check the value of "deleted", it is actually set to YES.
Why is it not disappearing from the NSFetchedResultController?
Is this expected behavior?
Or what could I be doing wrong?
I guess I solved it...
Turns out I actually do get the callback ("didChangeObject") and I now check the value of "deleted" here and manually remove it from the NSFetchedResultController if the value is YES.
Seems to work, but I still did expect it to be automatic.

NSPredicate fetch Core Data objects with dates attribute within NSDate date range

I'm using an NSPredicate with a couple of <= constructs to fetch objects from a Core Data store with a startDate attribute between 2 dates but I'm getting 0 objects fetched.
Here's my NSPredicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"( ( %# <= %K ) && ( %K <= %# ) )", fromDate, #"startTime", #"startTime", toDate];
Here's the console output:
predicate: CAST(336261600.000000, "NSDate") <= startTime AND startTime <= CAST(339285599.000000, "NSDate")
[sectionInfo numberOfObjects]: 0
Here are the dates that are in Core Data (the Attribute Type is set to Date):
startTime
---------
337249800
337309200
337318200
fromDate and toDate are both NSDate objects. I've tried substituting these with distantPast and distantFuture to make completely sure than my Core Data startTime attributes are within the date range and I still get 0 object fetched.
I have no idea why 0 objects are returned. Can anyone see where I'm going wrong?
I'd start by adding the following executable argument:
-com.apple.CoreData.SQLDebug 1
This is going to spew a gigajazillion lines of debugging information to the console when you run your app, but it should also include information about the predicate its attempting to translate to SQL, and perhaps even why it's not working.
You'll obviously want to turn remove this before shipping your app :)
After looking at the generated SQL queries it appeared like the TIMESTAMP was being evaluated as a string and not an integer. This explained why my predicate wasn't working. I confirmed this by running the following SQL query on the SQLite db that I'm using to pre-populate Core Data.
SELECT DISTINCT typeof(ZSTARTTIME) FROM ZMATCH;
This showed that the TIMESTAMP field was of type BLOB!
Adding '+0' to my predicate so the startTime attribute was evaluated as an integer fixed the issue. However this seemed like a nasty solution. Also keeping the dates stored as blobs would most likely cause issues further down the road. So I went back and re-populated the the SQLite db from scratch making sure the TIMESTAMPs were inserted as integers and not strings. Now everything is working fine.
However this does mean that I'm going to have to do some more work with the next app update because I'll have to make sure the update overwrite the old SQLite db in the Documents folder with the new db. Is there a easy way to do this? Should I look at Core Data store migration??

Fetching relations with Core Data

I have two managed objects which have a bidirectional relationship.
It is a 'segmentWithDetails' which contains a 'segment' object.
I use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"SegmentWithDetails"
inManagedObjectContext:connectionDetailsService.connectionDetailsContext];
[fetchRequest setEntity:entity];
to get my segmentWith Details. This works ok.
BUT, when I try to retrieve the contained segment, I get nil.
I've tried to get it with
Segment *segment = [segmentWithDetails valueForKeyPath:#"segment"];
and with
Segment *segment = segmentWithDetails.segment;
But this does not work (=nil). I've also tried modifying the NSFetchedResultsController.
I added the following:
[fetchRequest setRelationshipKeyPathsForPrefetching:[NSArray arrayWithObject:#"segment"]];
[fetchRequest setIncludesSubentities:YES];
But both of these do not seem to make any difference.
From what I understand out of Apple fine documentation, the relationship should just work using faults.
What am I missing here?
How do I get to the relationship object 'segment' from 'segmentWithDetails' ??
It should just work like you describe. Are you sure the "segment" object/relation actually exists and contains a real object? Check the data model that this relation isn't optional and check your code that you have actually stored something.
Problem solved.
It was not how I read the data, but how I put it in CoreData.
The posted code above is indeed correct. Thanx for the response Jaanus.

Resources