Restkit truncate db before core data mapping - core-data

i'm using restkit to map a json with core data
i need to call a routine every time the user launch the app.
if the server has updated data to send, i need to download them, truncate my table and insert the data in the table, if the server sends me nothing i don't have to do nothing.
obviously i want to truncate my current data only when i'm sure that new data has been downloaded, not before
how can I achieve this?
this is my code:
NSURL *endpoint = [NSURL URLWithString:kBaseURL];
RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:endpoint];
[objectManager.HTTPClient setAuthorizationHeaderWithToken:#"username:my-token-12345"];
objectManager.managedObjectStore = [RKManagedObjectStore defaultStore];
[RKObjectManager setSharedManager:objectManager];
RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:#"Medic" inManagedObjectStore:objectManager.managedObjectStore];
[entityMapping addAttributeMappingsFromDictionary:#{
#"identifier": #"identifier",
#"name": #"name",
#"surname": #"surname",
#"personalAddress": #"personalAddress",
#"hospital": #"hospital",
#"hospitalAddress": #"hospitalAddress",
#"oldDigitalAgreement": #"oldDigitalAgreement",
#"oldPaperAgreement": #"oldPaperAgreement"}];
entityMapping.identificationAttributes = #[#"identifier"];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor
responseDescriptorWithMapping:entityMapping
method:RKRequestMethodAny
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:responseDescriptor];
[[RKObjectManager sharedManager] getObjectsAtPath:kregistryURL
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// done
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:NSLocalizedString(#"attention", nil) message:NSLocalizedString(#"communication.genericerror.title", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:#"Ok", nil];
[alertView show];
}
];
solution after Wain answer
[objectManager addFetchRequestBlock:^NSFetchRequest *(NSURL *URL) {
RKPathMatcher* pathMatcher = [RKPathMatcher pathMatcherWithPattern:kregistryURL];
NSDictionary *dic = nil;
if ([pathMatcher matchesPath:[URL relativePath] tokenizeQueryStrings:YES parsedArguments:&dic]) {
NSFetchRequest *fetchRequest = [NSFetchRequest new];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Medic"
inManagedObjectContext: [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext];
fetchRequest.entity = entity;
return fetchRequest;
} else
return nil;
}];

Your problem is
if the server sends me nothing, i don't have to do _any_thing
and I'm guessing the server still sends a 200 response rather than a special status code (it should be 304 ‘Not Modified’). Because if you didn't have that requirement you could use a deletion fetch request block (which is the correct solution).
As it stands, you will likely have to do the deletion yourself by fetching the existing items from the data store and iterating over them, checking if the item is in the mapping results provided to you in the success block and, if not, deleting it from the context...

Related

FetchRequest does not return NSManagedObjects fetched via RestKit 0.2

I am having the following problem: I use RestKit to get objects form a REST Api. The object mapping works, which I can see from the RK Debugger Output. However, when I perform a fetch request afterwards, the result is empty. I am talking about NSManagedObjects. I have the following setup.
1: Restkit and Coredata stack initialization:
NSError *error;
NSURL *baseURL = [NSURL URLWithString:#"https://mfihost.us/gocoffee/api/V1/"];
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:baseURL];
[objectManager.HTTPClient setDefaultHeader:#"Token" value:[NSString stringWithFormat:#"%#",[FBSDKAccessToken currentAccessToken].tokenString]];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
objectManager.managedObjectStore = managedObjectStore;
//[RKObjectManager setSharedManager:objectManager];
[FetchRequests registerFetchRequests:objectManager];
[Descriptors registerDescriptors:objectManager];
[managedObjectStore createPersistentStoreCoordinator];
NSPersistentStore *persistentStore = [managedObjectStore addInMemoryPersistentStore:&error];
NSAssert(persistentStore, #"Failed to add inmemory store with error: %#", error);
[managedObjectStore createManagedObjectContexts];
managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
2: call to get objects from server and performing a fetch request afterwards:
[[RKObjectManager sharedManager]
postObject:nil path:#"/gocoffee/api/V1/login/IOS"
parameters:nil
success: ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Objects have been saved in core data.");
NSManagedObjectContext *managedObjCtx = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
// Shout* sh=[managedObjCtx insertNewObjectForEntityForName:#"Shout"];
// sh.inviterUserId=#"22223";
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Shout" inManagedObjectContext:managedObjCtx];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *result = [managedObjCtx executeFetchRequest:fetchRequest error:&error];
if (error) {
NSLog(#"Unable to execute fetch request.");
NSLog(#"%#, %#", error, error.localizedDescription);
} else {
NSLog(#"%#", result);
}
completionBlock();
}
failure: ^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Load failed with error: %#", error);
}];
The fetch result is empty, although the server returns objects and these objects are properly mapped by using RKEntityMappings and the corresponding response descriptors. Confusingly, if I uncomment the two lines //Shout * .... (i.e. manually insert a managed oject into the context), then this object is fetched by the fetch request. Consequently, the fetch request should be working fine.
I am searching for ages now what the problem might be. Could it be that I am calling on the wrong context or something ? By the way: core-data multi-threading debugging is enabled and does not show any error, i.e. no "AllThatIsLeftToUsIsHonor" error.
The corresponding route from the above example is:
[objectManager.router.routeSet
addRoute:[RKRoute routeWithName:#"loginAndOrSignup"
pathPattern:#"login/IOS"
method:RKRequestMethodPOST]
];
The descriptors look like (example):
[objectManager addResponseDescriptor:
[RKResponseDescriptor responseDescriptorWithMapping:[shoutMapping inverseMapping]
method:RKRequestMethodPOST
pathPattern: #"login/IOS"
keyPath:#"response.incomingshoutapplications"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)
]
];
Shout mapping is as follows:
RKEntityMapping *shoutMapping = [RKEntityMapping mappingForEntityForName:#"Shout" inManagedObjectStore:managedObjectStore];
shoutMapping.identificationAttributes = #[ #"id" ];
[shoutMapping addAttributeMappingsFromDictionary:#{
#"id" : #"id",
#"inviterUserId" : #"inviterUserId",
#"eventType" : #"eventType",
#"eventTime" : #"eventTime",
#"locationLat" : #"locationLat",
#"locationLng" : #"locationLng",
#"radius" : #"radius",
#"targetGender" : #"targetGender",
#"state" : #"state",
#"buddyOnly" : #"buddyOnly",
#"timeCreated" : #"timeCreated"
}
];
The "manager" ist the one from above, the managedObjectStore is manager.managedObjectStore
All the mappings and descriptors are setup in another method that is calles by [Descriptors registerDescriptors:objectManager]; (see first block of code)
The problem is probably that you're using inverseMapping. That is used mainly for request mapping, because it's a mapping that creates instances of NSMutableDictionary, which isn't what you want.
So, what I expect is happening is that the response is mapped successfully, but to plain objects, not managed objects, and then you're throwing the result away.
Just change to
... responseDescriptorWithMapping:shoutMapping ...

Loading Core Data relationships from existing database ID relationships

I have an application that has lots of data in tables, and I'm writing a mobile app that gets a small portion of the data. The data in tables uses IDs for relations, and I'm trying to load it into a Core Data model and preserve the relationships.
But it doesn't seem like there's an easy way to tell Core Data, "For this relationship, I want a foreign key to this other table." Instead, I have this monstrosity (this is a simplified version of only one of six asynchronous RestKit queries that are coming back to fill the database):
[manager postObject:nil path:#"api/shiftservice/get" parameters:#{#"workerIds": #[#1]} success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Loaded shifts: %d", [[mappingResult array] count]);
NSManagedObjectContext *context = [AppDelegate managedObjectContext];
NSSet *positions = [context fetchObjectsForEntityName:#"NWPosition"];
NSDictionary *positionDict = [NSMutableDictionary new];
for (NWPosition *position in positions) [positionDict setValue:position forKey:position.positionId.stringValue];
NSSet *workers = [context fetchObjectsForEntityName:#"NWWorker"];
NSDictionary *workerDict = [NSMutableDictionary new];
for (NWWorker *worker in workers) [workerDict setValue:worker forKey:worker.workerId.stringValue];
NSSet *shifts = [context fetchObjectsForEntityName:#"NWShift"];
for (NWShift *shift in shifts)
{
NWPosition *position = [positionDict valueForKey:shift.positionId.stringValue];
position.shifts = [context fetchObjectsForEntityName:#"NWShift" withPredicateFormat:#"positionId = %d", position.positionId];
shift.position = position;
NSSet *tradesAsGetShift = [context fetchObjectsForEntityName:#"NWTrade" withPredicateFormat:#"getShiftId = %#", shift.shiftId];
for (NWTrade *trade in tradesAsGetShift) trade.getShift = shift;
shift.tradesAsGetShift = tradesAsGetShift;
NSSet *tradesAsGiveShift = [context fetchObjectsForEntityName:#"NWTrade" withPredicateFormat:#"giveShiftId = %#", shift.shiftId];
for (NWTrade *trade in tradesAsGiveShift) trade.giveShift = shift;
shift.tradesAsGiveShift = tradesAsGiveShift;
NWWorker *worker = [workerDict valueForKey:shift.workerId.stringValue];
worker.shifts = [context fetchObjectsForEntityName:#"NWShift" withPredicateFormat:#"workerId = %d", worker.workerId];
shift.worker = worker;
}
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed to load shifts with error: %#", [error localizedDescription]);
}];
I'm using a modified version of Matt Gallagher's One-Line Fetch at http://www.cocoawithlove.com/2008/03/core-data-one-line-fetch.html for the fetchObjectsForEntityName.
Anyway, this seems pretty horrible to me, like I'm missing something obvious. Is there some way to just tell Core Data about database-style foreign keys? Is there an easy way to populate them, if there isn't? Because doing this many fetches for every single entity sure doesn't seem like the right way to do it. And if RestKit helps out here, so much the better.
Nope, but there should be no need to set the inverse relationships.
Thanks to Wain and his insightful comment, I managed to get RestKit to do this for me in a reasonably-elegant fashion. Here is how I set up the foreign key relationships. I set up both ends of each relationship so that either set of data could come in first and the relationship would still get populated properly.
First, I set up the manager, telling it to use JSON serialization:
RKManagedObjectStore *store = [AppDelegate managedObjectStore];
RKObjectManager *manager = [[RKObjectManager alloc] initWithHTTPClient:oauthClient];
[RKObjectManager setSharedManager:manager];
[RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class] forMIMEType:RKMIMETypeJSON];
[manager.HTTPClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
[manager registerRequestOperationClass:[AFJSONRequestOperation class]];
[manager setAcceptHeaderWithMIMEType:RKMIMETypeJSON];
[manager setRequestSerializationMIMEType:RKMIMETypeJSON];
manager.managedObjectStore = store;
Next, I set up connections and response descriptors:
NSManagedObjectContext *context = [AppDelegate managedObjectContext];
NSDictionary *orgRels = [[NSEntityDescription entityForName:#"NWOrg" inManagedObjectContext:context] relationshipsByName];
NSDictionary *positionRels = [[NSEntityDescription entityForName:#"NWPosition" inManagedObjectContext:context] relationshipsByName];
// ...
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
NSArray *orgConnections =
#[[self connectionForRelation:#"positions" localKey:#"orgId" foreignKey:#"orgId" withRels:orgRels],
[self connectionForRelation:#"workers" localKey:#"orgId" foreignKey:#"orgId" withRels:orgRels]];
[manager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:
[self entityMappingForName:#"NWOrg" fromDictionary:#{#"orgId": #"orgId", #"orgName": #"orgName"} withKey:#"orgId" andConnections:orgConnections]
method:RKRequestMethodAny pathPattern:#"api/orgservice/get" keyPath:nil statusCodes:statusCodes]];
NSArray *positionConnections =
#[[self connectionForRelation:#"org" localKey:#"orgId" foreignKey:#"orgId" withRels:positionRels],
[self connectionForRelation:#"shifts" localKey:#"positionId" foreignKey:#"positionId" withRels:positionRels]];
[manager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:
[self entityMappingForName:#"NWPosition" fromDictionary:#{#"positionId": #"positionId", #"orgId": #"orgId", #"name": #"positionName"} withKey:#"positionId" andConnections:positionConnections]
method:RKRequestMethodAny pathPattern:#"api/positionservice/get" keyPath:nil statusCodes:statusCodes]];
// ...
Then, I was able to do the simple request, and it worked great:
[manager postObject:nil path:#"api/shiftservice/get" parameters:#{#"workerIds": #[#1]}
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Loaded shifts: %d", [[mappingResult array] count]);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed to load shifts with error: %#", [error localizedDescription]);
}];
Note: This used a few helper methods, which I will reproduce here:
- (RKConnectionDescription *)connectionForRelation:(NSString *)relation
localKey:(NSString *)localKey foreignKey:(NSString *)foreignKey
withRels:(NSDictionary *)rels
{
return [[RKConnectionDescription alloc] initWithRelationship:rels[relation]
attributes:#{localKey: foreignKey}];
}
- (RKEntityMapping *)entityMappingForName:(NSString *)name
fromDictionary:(NSDictionary *)dict withKey:(NSString *)key
{
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:name
inManagedObjectStore:[AppDelegate managedObjectStore]];
[mapping addAttributeMappingsFromDictionary:dict];
mapping.assignsDefaultValueForMissingAttributes = YES;
mapping.assignsNilForMissingRelationships = YES;
mapping.identificationAttributes = #[key];
return mapping;
}
- (RKEntityMapping *)entityMappingForName:(NSString *)name
fromDictionary:(NSDictionary *)dict withKey:(NSString *)key
andConnections:(NSArray *)connections
{
RKEntityMapping *mapping = [self entityMappingForName:name
fromDictionary:dict withKey:key];
for (RKConnectionDescription *connection in connections)
{
[mapping addConnection:connection];
}
return mapping;
}

Core Data Fetch Save relationship while inserting entity (simultaneously)

First question here and I've tried a bunch of stuff and can't figure it out.
Core Data with 2 entities with to-many relationship both ways
A<<---->>B
A entity has name as an attribute, and a relationship Bs
First Controller lists all A entities and i have a second controller to add A entities and I want to have it save a default B in its relationship.
In the prepare for segue I have this code:
if ([[segue identifier] isEqualToString:#"addAEntitySegue"]) {
AddAEntityViewController *addAEntityViewController = [segue destinationViewController];
addAEntityViewController.delegate = self;
addAEntityViewController.managedObjectContext = self.managedObjectContext;
NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.addingManagedObjectContext = addingContext;
[addingManagedObjectContext setPersistentStoreCoordinator:[[fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
addAEntityViewController.A = [NSEntityDescription insertNewObjectForEntityForName:#"A" inManagedObjectContext:addingContext];
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:addAEntityViewController
action:#selector(save:)];
addAEntityViewController.navigationItem.rightBarButtonItem = saveButton;
}
In addAEntityViewController i have this to save
-(IBAction)save:(id)sender
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"B" inManagedObjectContext:self.managedObjectContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#",[defaults objectForKey:#"BDefault"]];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
//Set the batch size to a suitable number
[fetchRequest setFetchBatchSize:20];
NSError *error;
self.A.name = textFieldVal;
[self.A addBObject:[[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] objectAtIndex:0]];
NSLog(#"A = %#", self.A.Bs);
[self.delegate addAEntityViewController:self didFinishWithSave:YES];
}
In the addAEntityViewController everything saves correctly even the NSLog(#"A = %#", self.A.Bs); statement shows the B. But when the delegate saves in the First Controller (AEntityViewController) it only saves the A.name but not the relationship A.Bs, I can't figure out what's wrong.
Here's the delegate method:
-(void) addAEntityViewController:self didFinishWithSave:YES{
if (save) {
NSLog(#"saveworkouts");
NSError *error;
if (![addingManagedObjectContext save:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
// release the adding managed object context
self.addingManagedObjectContext = nil;
}
Like I said it saves the A entity but not the relationship to B even though the relationship to B saved correctly in the addAEntityViewController (the second View). An NSLOg of A.Bs is null.
First I believe that this line:
addAEntityViewController = self.managedObjectContext;
should be:
addAEntityViewController.managedObjectContext = self.managedObjectContext;
but that would also be wrong.
it should be getting the addingContext you created afterwards:
addAEntityViewController.managedObjectContext = addingContext;
I'm a bit surprised that your app didn't crash, as you are mixing managed objects from 2 different contexts.

How to delete data from Core Data after a Lightweight Migration

In upgrading my application from v1 to v2, I've made a few small changes to the Core Data Model. The changes are just adding new attributes to the models.
I have versioned the data model with the before and after changes and implemented the following code in my App Delegate:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"ISDEmployees.sqlite"];
NSLog(#"storeURL:%#",storeURL);
NSError *error = nil;
// Create a dictionary for automatic lightweight core data migration
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
// Set up the persistent store and migrate if needed
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
Basically the standard persistentStoreCoordinator with the addition of the migration options. This code works great and my database is successfully updated. The problem that I'm having is that after the database update, I need to refresh all of the data in the database so that the new columns are populated. I was thinking that I would delete the data from the relevant entities/tables and force the application to re-download a new dataset with the added columns/attributes.
I'm not sure how/where to perform the delete/updates. The general application flow is this:
Log in with validation against an web API
On successful login, call the API and get latest added/updated records.
Display the updated data
I know I can check to see if a migration is needed by adding this code to persistentStoreCoordinator:
// Get the current data store meta data
BOOL migrationNeeded = NO;
NSDictionary *existingStoreData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeURL error:&error];
if (existingStoreData)
{
// Check to see if new model is not the same as the existing mode, meaning a migration is required
if (![self.managedObjectModel isConfiguration:nil compatibleWithStoreMetadata:existingStoreData])
{
migrationNeeded = YES;
}
}
Any help would be greatly appreciated!!
Update #1:
Based on the feedback below, I've made the following changes:
Changed the migrationNeeded from a local to a public class variable on the AppDelegate.
On the Login View, I've added the following method:
- (void)checkForDatabaseMigration
{
// Get a copy of the managed object context. If a migration is needed, it will kick it off
NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
if ([(AppDelegate *)[[UIApplication sharedApplication] delegate] migrationNeeded])
{
// Delete all data from the table
}
managedObjectContext = nil;
}
Does that seem right? The code works and the data is removed after migration and a fresh copy is inserted. I just hate to check for migration each time the application starts.
If you know how to determine when to delete old data, all you need is to fetch all the enteties you need and delete them. Here is how you do that(for example, if you want to delete all Man enteties):
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Man" inManagedObjectContext:myContext]];
[request setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * men = [myContext executeFetchRequest:request error:&error];
//error handling goes here
for (NSManagedObject * man in men) {
[myContext deleteObject:man];
}
NSError *saveError = nil;
[myContext save:&saveError];
//more error handling here

Retrieving data while multi threading

I am using multithreading while loading data from the database.
I am doing the following
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
// Get data
NSDate *pastDate = [CommonHelper getSelectedDateYearBackComponents:[NSDate date]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"users == %# && startDate >= %#", objUser,pastDate];
NSMutableArray *fetchArray = [DataAccess searchObjectsInUserContext:#"userIngo" :predicate :#"startDate" :NO];
if ([fetchArray count] > 0)
{
dispatch_async(dispatch_get_main_queue(),
^{
// Reload table
[self.tableView reloadData]; });
}
else
{
dispatch_async(dispatch_get_main_queue(),
^{ // calling Webservice
});
}
});
where users is the entity from which I am trying to fetch data and objUser is the user object for whom I am retrieving data from the users entity
and my searchObjectsInUserContext code is like this
+(NSMutableArray *) searchObjectsInLabContext: (NSString*) entityName : (NSPredicate *) predicate : (NSString*) sortKey : (BOOL) sortAscending
{
i3EAppDelegate *appDelegate = (i3EAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setUndoManager:nil];
[context setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
// Register context with the notification center
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
[request setEntity:entity];
[request setFetchBatchSize:10];
[request setReturnsObjectsAsFaults:NO];
// If a predicate was passed, pass it to the query
if(predicate != nil)
{
[request setPredicate:predicate];
}
// If a sort key was passed, use it for sorting.
if(sortKey != nil)
{
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:sortAscending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
}
NSError *error;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
// NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[request release];
appDelegate = nil;
return [mutableFetchResults autorelease];
}
So in my searchObjectInUserContext I use multiple managedObjectContext so that it does not create problems for me while multithreading as explained by Fred McCann in his blog post.
However, my problem is at NSMutableArray *mutableFetchResults in my searchObjectsInUserContext because it returns 0 at times even though there is data in the database.
Can someone help me with what I am doing wrong?
You are leaking the context you create; you never release it.
There is no need to register as an observer because you are never saving with that context.
Why are you making a mutable copy of the context? That rarely serves any useful purpose.
How do you know there is data in the database?
Is this method being run on a background thread?
Update
There is nothing wrong with your fetch so I suspect the issue might be one of timing. Is this fetch being run before another thread saves? That would explain the behavior you are seeing.
Also, why are you running this on a background thread? Are you seeing a performance issue that requires a background search like this?
Update 2
First, I still question the need for the background fetching. That is normally reserved for when you have performance issues as fetching is very fast.
Second, you fetch on a background queue but then you don't do anything with the data you fetched. You do not hand it off to the table (which would be bad anyway) you just fetch and throw it away.
If you are fetching just to count then don't fetch, do a -[NSManagedObjectContext -countForFetchRequest: error:]. It will be even faster and removes the need for the background queue call.
If you are expecting to do something with the results you have an issue. Instances of NSManagedObject cannot cross a thread/queue boundary. So fetching on the background queue does very little for the UI. You need to fetch those objects on the main queue/main thread. If you don't you will crash because Core Data does not allow it.
In the end, your background queues are doing you more harm than good and they are not solving the problem you are trying to solve.

Resources