I have a core-data app that runs without crashing if I perform a fetch inside viewDidLoad like this:
- (void) performCoreDataFetch {
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
exit(-1); // Fail
}
}
- (void)viewDidLoad {
[super viewDidLoad];
[self performCoreDataFetch];
}
The only problem with the above way of performing fetch is if the data to be returned is big, it freezes the app for a few seconds (but does return correct result without crashing every single time), so to avoid that I decided to use dispatch_async (code shown below) and call [self performCoreDataFetch] inside it.
But if I run this same [self performCoreDataFetch] inside dispatch_sync within viewDidLoad, like shown below,:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performCoreDataFetch];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
Calling [self performCoreDataFetch] within dispatch_async crashes the app randomly saying "-[NSFetchRequest fetchLimit]: message sent to deallocated instance"
My fetchedResultsController method looks like this:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create and configure a fetch request with the Organization entity
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Organization" inManagedObjectContext:managedObjectContext];
request.fetchBatchSize = 20;
// create sortDescriptor array
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptorArray = [NSArray arrayWithObjects:nameDescriptor, nil];
request.sortDescriptors = sortDescriptorArray;
NSPredicate *predicate = nil;
predicate = [NSPredicate predicateWithFormat:#"state LIKE %#", filterByState];
[request setPredicate:predicate];
// Create and initialize the fetchedResultsController
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc ] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:#"uppercaseFirstLetterOfName" cacheName:nil];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
// Memory management
filterByState = nil;
// [sortDescriptorArray release];
[nameDescriptor release];
// [predicate release];
[request release];
[aFetchedResultsController release];
return fetchedResultsController;
}
Core data is not thread safe if you perform a fetch for a fetchedResultsController. It makes sense, as the fetchedResultsController is the datasource of your UI. Rather than perform a fetch, set your fetchedResultsController to nil and reload your tableView.
Core data is not thread save. To be more exact, the NSManagedObjectContext is not save. All NSManagedObject belong to a particular NSManagedObjectContext and they are not interchangeable.
Pre IOS 5 you need to put really really complicated method. Basically each thread require it's own NSManagedContext
After IOS5, you can do:
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
Then you can do
[__managedObjectContext performBlock ^{
//Some really long operation
}]
on any thread that is not main thread.
That will do it on a different thread however in a thread save way. Basically core data will put your operation into queues and it will execute that one by one locking the managedObjectContext for each operation.
Related
I setup a background thread with the Parent/Child model. Essentially the context save is failing.
Here is my setup. In the AppDelegate i've setup the _managedObjectContext with the NSMainQueueConcurrencyType:
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];//[[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
In my data loading class I setup the parent/child mocs here to perform the work on the background thread:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSManagedObjectContext *mainMOC = self.managedObjectContext;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[moc setParentContext:mainMOC];
[moc setUndoManager:nil];
When the json data has completed I attempt to peform a save operation with the following macro:
#define SAVE_MOC { NSError *error; \
if (![moc save:&error]) { NSLog(#"Sub MOC Error"); } \
[mainMOC performBlock:^{ NSError *e = nil; if (![mainMOC save:&e]) {
NSLog(#"Main MOC Error %#",error.localizedDescription);}}];}
Also when i've completed the data load I jump back on the main thread like this:
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"<---- complete CS sub moc! ---->");
//this fires ok
});
So, from my SAVE_MOC macro i just get a simple error:
Main MOC Error (null)
Let me know if I can provide more info. I'm very new to multi-threading and trying to get a better handle on this approach.
Thanks,
Josh
In my data loading class I setup the parent/child mocs here to perform
the work on the background thread:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSManagedObjectContext *mainMOC = self.managedObjectContext;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
You should not do that. Do this instead.
NSManagedObjectContext *mainMOC = self.managedObjectContext;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
Make sure you access the MOC in a performBlock. For example,
[moc performBlock:^{
// Anything at all involving this MOC or any of its objects
}];
When the json data has completed I attempt to peform a save operation
with the following macro:
Consider saving with something like this. Your completion block will be called when the save has finished.
- (void)saveMOC:(NSManagedObjectContext*)moc
completion:(void(^)(NSError *error))completion {
[moc performBlock:^{
NSError *error = nil;
if ([moc save:&error]) {
if (moc.parentContext) {
return [self saveMOC:moc.parentContext completion:completion];
}
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
}
}];
}
[self saveMOC:moc completion:^(NSError *error) {
// Completion handler is called from main-thread, after save has finished
if (error) {
// Handle error
} else {
}
}];
EDIT
This code will crash if moc.parentContext is main concurrency type. –
Mundi
There is no inherent reason that the code I posted should cause a crash with a parent MOC of NSMainQueueConcurrencyType. It has supported being a parent context ever since parent/child was added to Core Data.
Maybe I was missing a typo, so I copy/paste saveMOC:completion: straight from this answer, and wrote the following test helper.
- (void)testWithChildConcurrencyType:(NSManagedObjectContextConcurrencyType)childConcurrencyType
parentConcurrencyType:(NSManagedObjectContextConcurrencyType)parentConcurrencyType {
NSAttributeDescription *attr = [[NSAttributeDescription alloc] init];
attr.name = #"attribute";
attr.attributeType = NSStringAttributeType;
NSEntityDescription *entity = [[NSEntityDescription alloc] init];
entity.name = #"Entity";
entity.properties = #[attr];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init];
model.entities = #[entity];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
[psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL];
NSManagedObjectContext *parent = [[NSManagedObjectContext alloc] initWithConcurrencyType:parentConcurrencyType];
parent.persistentStoreCoordinator = psc;
NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:childConcurrencyType];
child.parentContext = parent;
NSManagedObject *obj = [NSEntityDescription insertNewObjectForEntityForName:#"Entity" inManagedObjectContext:child];
[obj setValue:#"value" forKey:#"attribute"];
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:#"save from %# to %# finished", concurrencyTypeString(childConcurrencyType), concurrencyTypeString(parentConcurrencyType)]];
[self saveMOC:child completion:^(NSError *error) {
// Verify data saved all the way to the PSC
NSManagedObjectContext *localMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
localMoc.persistentStoreCoordinator = psc;
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
XCTAssertEqualObjects(#"value", [[[localMoc executeFetchRequest:fr error:NULL] firstObject] valueForKey:#"attribute"]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:nil];
}
And then, I wrote a test for each possible parent/child relationship.
- (void)testThatDoingRecursiveSaveFromPrivateToPrivateWorks {
[self testWithChildConcurrencyType:NSPrivateQueueConcurrencyType
parentConcurrencyType:NSPrivateQueueConcurrencyType];
}
- (void)testThatDoingRecursiveSaveFromPrivateToMainWorks {
[self testWithChildConcurrencyType:NSPrivateQueueConcurrencyType
parentConcurrencyType:NSMainQueueConcurrencyType];
}
- (void)testThatDoingRecursiveSaveFromMainToPrivateWorks {
[self testWithChildConcurrencyType:NSMainQueueConcurrencyType
parentConcurrencyType:NSPrivateQueueConcurrencyType];
}
- (void)testThatDoingRecursiveSaveFromMainToMainWorks {
[self testWithChildConcurrencyType:NSMainQueueConcurrencyType
parentConcurrencyType:NSMainQueueConcurrencyType];
}
So, what am I missing?
As I write this, I am reminded of a 360iDev presentation where the presenter said that you can't call performBlock on a NSMainQueueConcurrencyType context. At the time, I thought he just misspoke, meaning confinement, but maybe there is some confusion in the community about this.
You can't call performBlock on a NSConfinementConcurrencyType MOC, but performBlock is fully supported for NSMainQueueConcurrencyType.
I want to add my imageArray into coredata as transformable but this is not storing properly.
My save button coding.
- (IBAction)saveButton:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"FoodInfo" inManagedObjectContext:context];
NSAttributeDescription *messageType = [[NSAttributeDescription alloc] init];
[messageType setName:#"photos"];
[messageType setAttributeType:NSTransformableAttributeType];
[imagesForShow addObject:messageType];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unable to save context for class" );
} else {
NSLog(#"saved all records!");
[context save:nil];
}
//[newEntry setValue:imagesForShow forKey:#"images"];
}
Here 'imagesForShow' is my array of images.
When iam going to fetch this image array , this showing nil
- (void)viewDidLoad {
[super viewDidLoad];
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:#"FoodInfo"];
[request setReturnsObjectsAsFaults:NO];
arrayForPhotos = [[NSMutableArray alloc]initWithArray:[context executeFetchRequest:request error:nil]];
// Do any additional setup after loading the view.
}
What I am doing wrong with this code. Thanks.
In your save code:
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *newEntry = [NSEntityDescription
insertNewObjectForEntityForName:#"FoodInfo"
inManagedObjectContext:context];
NSAttributeDescription *messageType = [[NSAttributeDescription alloc] init];
[messageType setName:#"photos"];
[messageType setAttributeType:NSTransformableAttributeType];
[imagesForShow addObject:messageType];
I can't even figure out what this is supposed to do. It's completely wrong. You should never be allocating an instance of NSAttributeDescription unless you are constructing a Core Data model on the fly-- which you are not doing and which almost nobody ever does. Creating the new entry is OK. The rest, I don't know. You said that imagesForShow is your array of images, so I don't know why you're also adding an attribute description to the array.
In a more general sense, if newEntry has a transformable attribute named photos and imagesForShow is an NSArray of UIImage objects, then you could do this:
[newEntry setValue:imagesForShow forKey:#"photos"];
This is similar to a line that you have commented out, though it's not clear why it's commented out.
But whatever you do get rid of the code creating the NSAttributeDescription.
In an iOS6 app I fetch NSManagedObjects from DB with CoreData and display them in a tableViewCell. My problem is, that all objects that correspond to cells outside of the initial scroll position are in fault state and do not come back to life. I can't see my mistake.
fetchRequest.returnsObjectsAsFaults = NO; helps, but I want a clean solution.
Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ContactsCell";
ContactsCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Contact *contact = [self.contacts objectAtIndex:indexPath.row];
//here some contacts are faulted. contact.name is null if I fetch it
[cell setContactData:contact];
return cell;
}
here is how I fetch (with Restkit 0.10.3):
NSFetchRequest *fetchRequest = [Contact fetchRequest];
fetchRequest.sortDescriptors = [self sortDescriptors];
fetchRequest.returnsObjectsAsFaults = NO;
return [Contact objectsWithFetchRequest:fetchRequest];
Ok I never really used your approach so I won't say that it is wrong, but I'll say that it is strange. I guess that Contact is a subclass of NSManagedObject, and I can believe that it knows of the fetch request which which he was originally fetched, and that he knows of the context from which he was fetched, but only after he was already fetched. I really don't see how could he know of those things if he never before was fetched from the persistent store. So I recommend U use classic executeFetch or fetchedResultsController to populate your tableView.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:persistentStoreCoordinator];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Contact" inManagedObjectContext:context];
fetchRequest.entity = entity;
fetchRequest.sortDescriptors = [self sortDescriptors];
NSArray *array = [context executeFetchRequest:fetchRequest error:&error];
return array;
Try it, hope it helps
i'm working on an app with core data with storyboard. the app has uitabbarcontroller as rootview. i have created entity and generated the classes. each tab has it own uinavigation controller. the view in the tab 1 just saves some data in the database from uilabels. and it works fine and data is in the database.
the view in tab 2 displays the data from the database in uitableview. the data is only shown when i kill the app and restart it. so the ui table doesnt get refreshed.
first method: i have passed the managedobject context from the app delegate to the both views. so ui table doesnt get refreshed till kill and restart.
second method: i (mis)used the app delegate, but still the same result.
MyApplicationDelegate *appDelegate = (MyApplicationDelegate *)[[UIApplication sharedApplication] delegate];
how can one achieve that one view only adds data to core data(which it does right now) and the second view get notified of changes and display it in uitableview?
edit
-(NSFetchedResultsController *) fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Favis" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"chapterid" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[NSFetchedResultsController deleteCacheWithName:#"Master"];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
it is the code when you use the core data template. only tweaked to work with my app. i have it in my both viewcontroller.
edit 2
i have implemented nsfetchedresultcontroller in my uitableview controller.
the manged object returns the exact number of data in the database, but ui table doesnt get refreshed. i also did [self.tableview reloaddata] but no luck
In the viewController with the UITableVIew implement the methods for the NSFetchedResultsControllerDelegate. The documentation has the full implementation of those methods.
And then make your viewController the delegate of the NSFetchedResultsController fetchedResultsController.delegate = self;
There should be some thing in 2 tab as to notify as data changed in database update the new data, Is there any? If
NSManagedObjectContext, NSFetchedResultsController in 2 tab by saying
Implement NSFetchedResultsController delegation methods.
in appdelegate
secTab.managedObjectContext = self.managedObjectContext;
Surely it works now
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.