Fetching relations with Core Data - 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.

Related

CoreData with #FetchRequest (SwiftUI) and NSPredicate crashes if there is no data

I have a SwiftUI app where I am using #FetchRequest along with a predicate. Everything works fine as long as there is already some data.
However, when the app is first installed and the user tries to perform a search before entering any data, the app crashes with this error:
error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x60000188c380> , unimplemented SQL generation for predicate : (username CONTAINS[cd] "r") with userInfo of (null)
I believe the cause of the problem is that the column doesn't exist (because there is no data). What I'd like to do is pass nil for the predicate in the case where this is no data.
I understand how to use NSManagedObjectContext and count(for:) but the context isn't really made available via the #EnvironmentObject at the time I need to use it. Does anyone have any suggestions as to how to handle this. I don't see how a try-catch would work either.
Thanks.
Well, I do have a solution. Its not pretty, but it does work. Because the NSManagedObjectContext is being passed around via #Environment it isn't initialized when a SwiftUI View init is being run. So to get the context you can do:
if let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext {
and make the call to get the count in there.
So I'm clueless sometimes. I did not read the error thoroughly since it happened in the context of something I was changing so I thought the problem was that. But no, that is not the problem.
The problem is that earlier in the day, I renamed a column from username to something else. But I forgot to change the NSPredicate.
So for the sake of anyone else using SwiftUI and #FetchRequest, if you do need to count the number of items, you will need to revert to NSFetchRequest to do it. Sorry for wasting anyone's time.

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.

iOS Simulator 6.0 crashes with custom sort descriptor

tAfter creating a Master-Detail Application project, I replaced the following line of code, in MasterViewController.m,
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
with
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"timeStamp"
ascending:YES
comparator:^NSComparisonResult (id obj1, id obj2)
{
return NSOrderedSame;
}];
This is the only change I've made to the Apple code template. When I run it on Simulator 6.0, the code crashes when fetching the data...
if (![self.fetchedResultsController performFetch:&error]) {
with objc_exception_throw. BTW, this code works on the Simulator 5.1. Any idea what's wrong? Thanks in advance.
Objective-C based sort descriptors cannot be used with a fetch request.
From the "Core Data Programming Guide":
... To summarize, though, if you execute a fetch directly, you should
typically not add Objective-C-based predicates or sort descriptors to
the fetch request. Instead you should apply these to the results of
the fetch.
Btw, do you really want a sort descriptor that makes all objects equal?

Bizarre Core Data behavior when fetching unsaved 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.

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];
}

Resources