I have a moc (self.managedObjectContext) which was created with NSMainQueueConcurrencyType.
Now, for a method invoked this way -
ManagedObjectType1 *obj1 = [self createAnObject];
With the implementation for createAnObject being -
- (ManagedObjectType1 *) createAnObject {
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
childContext.parentContext = self.managedObjectContext;
ManagedObjectType1 *obj1 = //..initialize in childContext
return obj1
}
obj1 is nil after the method returns (at the place where it was invoked) and yet obj1 has data in the method implementation at the time of being returned.
What could be going wrong here. I have tried assigning childContext with NSPrivateQueueConcurrencyType but that hasn't helped either.
This worked. But is this a good way to do it.
- (ManagedObjectType1 *) createAnObject {
__block ManagedObjectType1 *obj1;
[self.managedObjectContext performBlockAndWait:^{
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
childContext.parentContext = self.managedObjectContext;
obj1 = //..initialize in childContext
}];
return obj1
}
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 am having issues trying to get my CoreData to save, I have used CoreData before and don't normally run in to these issues. Can someone tell me what is wrong with the following code:
MapEntity *mapEntity = [_fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
NSMutableSet *set = (NSMutableSet *)mapEntity.points;
[set removeObject:self.selectedPoint];
self.selectedPoint = nil;
[[Singleton sharedSingleton] saveContext];
To initialise my fetched results controlled I use the following:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MapEntity" inManagedObjectContext:[[Singleton sharedSingleton] managedObjectContext]];
So the entity returned should definitely be in the singletons manabged object context. The code for saveContext is below:
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
NSLog(#"Unable to save context");
}
}
}
If I check the mapEntity.points after the removal of the object I can see the object has been removed. The object doesn't re-appear until I relaunch the app, so it must be a persistance issue. Can anyone figure out what's wrong with my code as I am completely baffled.
I have managed to fix the issue, seems it lied within the way I was deleting objects. Instead I am now using the set deletion functions created by CoreData, code below for anyone else with the same issues:
MapEntity *mapEntity = [_fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
NSSet *removeSet = [NSSet setWithObject:self.selectedPoint];
[mapEntity removePoints:removeSet];
self.selectedPoint = nil;
[[Singleton sharedSingleton] saveContext];
I need to use some core data managed objects in an NSOperation. The problem is that core data is not thread safe and apparently the object can't be loaded from the new thread. Does anybody know a good tutorial for this? I need the object read only... so the thread will not modify them in any way. Some other, unrelated entities may be added on the main thread while these objects are used in the background, but the background entities don't need to be modified at all..
Hmm seemed I fixed the background running issue, but now the problem is nothing is returned to the delegate... Why? In the thred if I nslog the results are all shown but that call to the delegate never happens
This is the code:
-(void)evaluateFormula:(Formula *)frm runNo:(NSUInteger)runCount{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:2];
NSManagedObjectID *formulaId = frm.objectID;
for (int i = 0; i < runCount; i++) {
NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(runFormula:) object:formulaId];
[queue addOperation:op];
}
}
-(void)runFormula:(NSManagedObjectID *)fId {
NSManagedObjectContext *thredContext =[[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *coord = (NSPersistentStoreCoordinator *)[(PSAppDelegate *)[[UIApplication sharedApplication] delegate] persistentStoreCoordinator];
[thredContext setPersistentStoreCoordinator:coord];
Formula *f = (Formula *)[thredContext objectWithID:fId];
NSDictionary *vars = [self evaluateVariables:[f.hasVariables allObjects]];
NSMutableString *formula = [NSMutableString stringWithString:f.formula];
for (NSString *var in [vars allKeys]) {
NSNumber *value =[vars objectForKey:var];
[formula replaceOccurrencesOfString:var withString:[value stringValue] options:NSCaseInsensitiveSearch range:NSMakeRange(0, [formula length])];
}
//parse formula
NSNumber *result = [formula numberByEvaluatingString];
// NSLog(#" formula %# result : %d",formula,[result intValue]);
//aggregate results
[self performSelectorOnMainThread:#selector(aggregate:) withObject:result waitUntilDone:YES]; // the delegate doesn't get called ...
}
-(void)aggregate:(NSNumber *)res {
[self.delegate didReceiveResult:res];
}
This one has been a big problem for me, and i´m still stuck with it so i was hopping that someone could give me some kind of guidance.
What i have is:
3 tableviews with multiple cells and each cell with several textfields.
1 tableview that appears inside a popover every time a specific textfield on those cells is pressed. This tableview has all!! the core data methods to retrieve the necessary data from my database.
Everything works ok...but i need to distinguish what kind of data shall appear in tableview 1 or 2 or 3...So i know i have to use predicate!.
What i have done: ( and i have tried other things)
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController == nil)
{
NSFetchRequest *fetchRequestList = [[NSFetchRequest alloc] init];
NSEntityDescription *entityList = [NSEntityDescription entityForName:#"List" inManagedObjectContext:self.managedObjectContext];
[fetchRequestLista setEntity:entityList];
TableViewOne *table1 = [[Cobertura alloc]init];
TableViewTwo *table2 = [[Cobertura alloc]init];
if (table1 textFieldShouldBeginEditing:table1.textFieldPressed)
{
fetchRequestList.predicate = [NSPredicate predicateWithFormat:#"%K IN %#", #"reference", arrayTableview1];
}
if (table2 textFieldShouldBeginEditing:table2.textFieldPressed)
{
fetchRequestList.predicate = [NSPredicate predicateWithFormat:#"%K IN %#", #"reference", arrayTableview2];
}
NSSortDescriptor *cellTitle = [[NSSortDescriptor alloc] initWithKey:#"reference" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:cellTitle, nil];
[fetchRequestLista setSortDescriptors:sortDescriptors];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequestLista managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"referencia" cacheName:nil];
_fetchedResultsController.delegate = self;
self.fetchedResultsController = _fetchedResultsController;
return _fetchedResultsController;
}
In each of my tableviews, i have an instance of the "popoverTableview" in my method textFieldShouldBeginEditing:
popoverTableview = [self.storyboard instantiateViewControllerWithIdentifier:#"popoverTableview"];
popover = [[UIPopoverController alloc] initWithContentViewController:popoverTableview];
[popover presentPopoverFromRect:textField.bounds inView:textField permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
popoverTableview.delegate = self;
popoverTableview.popView = self.popover;
So, if i´m in tableview1 i need to get [NSPredicate predicateWithFormat:#"%K IN %#", #"reference", arrayTableview1];
Should i be creating some kind of method that my tableviewS could access? What am i forgetting here or not paying attention?
Thanks in advance, and any kind of advise would be welcome!
Regards
For everyone that was experiencing the same problem that i was, here is what i have done to resolve:
This is when i´m creating the popoverview when a specific textfield is pressed:
popoverTableview = [self.storyboard instantiateViewControllerWithIdentifier:#"popoverTableview"]initWithTextFieldTag:myTextFieldThatWasPressed.tag]
popover = [[UIPopoverController alloc] initWithContentViewController:popoverTableview];
[popover presentPopoverFromRect:textField.bounds inView:textField permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
popoverTableview.delegate = self;
popoverTableview.popView = self.popover;
popoverTableview.aIntVariable = myTextFieldThatWasPressed;
then in my popovertableview:
- (id)initWithTextFieldTag:(int)textFieldTag
{
self.aIntVariable = textFieldTag;
return self;
}
Then in the fetchedResultsController method, you´ll just have to create simple if´s telling wich predicate you want...
Regards
If the fetched results controller is for the popover table and you need to know in which table the text field was selected, I'd recommend tagging each of the text fields when you create them and creating an int _currentTable instance variable. That way, when your textFieldShouldBeginEditing: method is called, you can set the ivar's value with the tag and check that tag when creating the fetched results controller for the popover table.
So, instead of
if (table1 textFieldShouldBeginEditing:table1.textFieldPressed)
{
fetchRequestList.predicate = [NSPredicate predicateWithFormat:#"%K IN %#", #"reference", arrayTableview1];
}
if (table2 textFieldShouldBeginEditing:table2.textFieldPressed)
{
fetchRequestList.predicate = [NSPredicate predicateWithFormat:#"%K IN %#", #"reference", arrayTableview2];
}
you'll have
if (_currentTable == 1) {
fetchRequestList.predicate = // table one predicate
} else if (_currentTable == 2) {
fetchRequestList.predicate = // table two predicate
}
UPDATE:
This is how I would override the init from code. In your popover table view controller implementation:
- (id)initWithTableTag:(int)tableTag
{
self = [super init];
if (self) {
_currentTable = tableTag;
}
return self;
}
(Make sure you also declare - (id)initWithTableTag:(int)tableTag; in your header.) Then, when you create and present the popover controller (which I'm assuming you're doing in the textFieldShouldBeginEditing: delegate call):
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
// ...
YourPopoverTableViewControllerClass *vc = [[YourPopoverTableViewControllerClass alloc] initWithTableTag:textField.tag];
// ...
// display the popover
return YES;
}
Unfortunately, I don't know how to do this using storyboards.
I would like to know if it's possible to init a subclass of NSManagedObject ?
I have a class "Actualite" which is a subclass of NSManagedObject and when I want to initialize this class, I get this error :
"CoreData: error: Failed to call designated initializer on NSManagedObject class Actualite", and the app crashes after this message "-[Actualite setTitre:]: unrecognized selector sent to instance 0x8433be0"
Here is my code :
-(void) recupererActualites {
listeNouvellesActualites = [[NSMutableArray alloc] init];
// Convert the supplied URL string into a usable URL object
NSURL *url = [NSURL URLWithString:FACE06_RSS];
// Create a new rssParser object based on the TouchXML "CXMLDocument" class, this is the
// object that actually grabs and processes the RSS data
CXMLDocument *rssParser = [[CXMLDocument alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding options:0 error:nil];
// Create a new Array object to be used with the looping of the results from the rssParser
NSArray *resultNodes = NULL;
// Set the resultNodes Array to contain an object for every instance of an node in our RSS feed
resultNodes = [rssParser nodesForXPath:#"//item" error:nil];
NSMutableArray* tmp = [[NSMutableArray alloc] init];
for (CXMLElement* resultElement in resultNodes) {
// Create a temporary MutableDictionary to store the items fields in, which will eventually end up in blogEntries
NSMutableDictionary *blogItem = [[NSMutableDictionary alloc] init];
NSMutableArray* categories = [[NSMutableArray alloc] init];
// Create a counter variable as type "int"
int counter;
// Loop through the children of the current node
for(counter = 0; counter < [resultElement childCount]; counter++) {
// Add each field to the blogItem Dictionary with the node name as key and node value as the value
if([[[resultElement childAtIndex:counter] name] isEqual:#"category"])
[categories addObject:[[resultElement childAtIndex:counter] stringValue]];
else {
if ([[resultElement childAtIndex:counter] stringValue] != nil)
[blogItem setObject:[[resultElement childAtIndex:counter] stringValue] forKey:[[resultElement childAtIndex:counter] name]];
else
[blogItem setObject:#"" forKey:[[resultElement childAtIndex:counter] name]];
}
}
Actualite* actu = [[Actualite alloc] init];
[blogItem setObject:categories forKey:#"categories"];
[actu initWithDictionnary:blogItem];
[tmp addObject:actu];
//[actu release];
[categories release];
[blogItem release];
}
listeNouvellesActualites = tmp;
[rssParser release];
resultNodes = nil;
// Stockage des actualités en local
[self stockerActualites];
}
And the initWithDictionary method set all the attributes of the Actualite class.
I also tried
Actualite* actu = [[Actualite alloc] initWithEntity:[NSEntityDescription entityForName:#"Actualite" inManagedObjectContext:managedObjectContext] insertIntoManagedObjectContext:managedObjectContext];
and
Actualite* actu = (Actualite*)[NSEntityDescription entityForName:#"Actualite" inManagedObjectContext:managedObjectContext];
instead of
Actualite* actu = [[Actualite alloc] init];
The errors disappear but the app stops at this point. I don't know what can I do...
Is someone already had this problem ?
Thanks a lot !
The idea is that you ask for a new object to be created inside a context and then you use it:
Actualite* actu = [NSEntityDescription insertNewObjectForEntityForName:#"Actualite" inManagedObjectContext:managedObjectContext];