Have got an issue with my NSOrderedSets.
I have coredata entities - 'Project' and 'Drawing'. A Project has many Drawings. The relationship is One-to-Many and Ordered, hence my Project object contains an NSOrderedSet of Drawings.
My app is single-threaded and is causing me headaches if wish I re-order my Drawing objects.
I re-order the Drawings with the following code...
-(IBAction)onTestReOrder:(id)sender
{
NSMutableOrderedSet *exchange = [self.currentProject.drawings mutableCopy];
[exchange exchangeObjectAtIndex:0 withObjectAtIndex:1];
self.currentProject.drawings = exchange;
// Save
id delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *managedObjectContext = [delegate managedObjectContext];
NSError *error = nil;
if( ![managedObjectContext save:&error] )
{
NSLog(#"%# Save: Unresolved Error on Save %#, %#", methodName, error, [error userInfo] );
abort();
}
}
This all seems to work well. My underlying Sqlite datastore appears to be updated to reflect the re-order.
My problem arises when I try to write a change to a property within my Drawing after the re-order. For instance...
drawing = [self.currentProject.drawings objectAtIndex:1];
drawing.current = [NSNumber numberWithBool:YES];
// Save....causes NSMergeConflict
id delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *managedObjectContext = [delegate managedObjectContext];
NSError *error = nil;
// BANG ON SAVE....NSMergeConflict
if( ![managedObjectContext save:&error] )
{
NSLog(#"%# Save: Unresolved Error on Save %#, %#", methodName, error, [error userInfo] );
abort();
}
The Save call here creates an NSMergeConflict. Looking at the snapshots, it seems the Drawing's Project is different between Old and New but this is a single-threaded app and only one ManagedObjectContext. How can I have different references to the 'Project'?
I'm pulling my hair on this one and any hints to help me resolve the NSMergeConflict are greatly appreciated.
/Fitto
Error...
2013-10-26 23:40:38.047 testDesign[34625:a0b] setCurrentDrawing Save: Unresolved Error on Save Error Domain=NSCocoaErrorDomain Code=133020 "The operation couldn’t be completed. (Cocoa error 133020.)" UserInfo=0xc148180 {conflictList=(
"NSMergeConflict (0xc151860) for NSManagedObject (0xb3f0030) with objectID '0xb379040 ' with oldVersion = 58 and newVersion = 59 and old object snapshot = {\n angle = 0;\n current = 1;\n depth = \"4.8768\";\n project = \"0xb3eec60 \";\n offsetX = 0;\n offsetY = 0;\n type = 0;\n width = \"9.7536\";\n} and new cached row = {\n angle = 0;\n current = 1;\n depth = \"4.8768\";\n project = \"0xc1f3cb0 \";\n offsetX = 0;\n offsetY = 0;\n type = 0;\n width = \"9.7536\";\n}"
)},
I am developing a calendar app using eventkit framework in ios 6. I am trying to get the permission using the [self.store respondsToSelector:#selector(requestAccessToEntityType:completion:)] method and after getting permission to access the calendars, I am trying to create the new calendar with identifier using EKSourceTypeLocal source and add events to the newly created calendar. I am facing the problem here that when I try to run the app in iPhone 4s it shows the error that "calendar has no source" and it doesn't save my calendar and hence no events gets added to the calendar. I don't know what am I doing wrong here. Please guide me to solve this issue.
I am posting my code below
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(#"in view did load method");
// For iOS 6.0 and later
self.store = [[EKEventStore alloc] init];
if([self.store respondsToSelector:#selector(requestAccessToEntityType:completion:)]) {
[self.store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
// handle access here
dispatch_async(dispatch_get_main_queue(), ^{
if (granted) {
NSLog(#"permission granted");
EKCalendar *myCalendar;
EKSource *myLocalSource = nil;
for (EKSource *calendarSource in self.store.sources) {
if (calendarSource.sourceType == EKSourceTypeLocal) {
myLocalSource = calendarSource;
break;
}
}
if (!myLocalSource)
return;
NSString *mycalIndentifier = [[NSUserDefaults standardUserDefaults] valueForKey:#"my_calendar_identifier"];
if (mycalIndentifier == NULL) {
// Create a new calendar of type Local... save and commit
myCalendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:self.store];
myCalendar.title = #"New_Calendar";
myCalendar.source = myLocalSource;
NSString *calendarIdentifier = myCalendar.calendarIdentifier;
NSError *error = nil;
[_store saveCalendar:myCalendar commit:YES error:&error];
if (!error) {
NSLog(#"created, saved, and commited my calendar with id %#", myCalendar.calendarIdentifier);
[[NSUserDefaults standardUserDefaults] setObject:calendarIdentifier forKey:#"my_calendar_identifier"];
} else {
NSLog(#"an error occured when creating the calendar = %#", error.description);
error = nil;
}
//create an event
// Create a new event... save and commit
EKEvent *myEvent = [EKEvent eventWithEventStore:_store];
myEvent.allDay = NO;
myEvent.startDate = [NSDate date];
myEvent.endDate = [NSDate date];
myEvent.title = #"Birthday";
myEvent.calendar = myCalendar;
[_store saveEvent:myEvent span:EKSpanThisEvent commit:YES error:&error];
if (!error) {
NSLog(#"the event saved and committed correctly with identifier %#", myEvent.eventIdentifier);
} else {
NSLog(#"there was an error saving and committing the event");
error = nil;
}
EKEvent *savedEvent = [_store eventWithIdentifier:myEvent.eventIdentifier];
NSLog(#"saved event description: %#",savedEvent);
}
else{
myCalendar = [_store calendarWithIdentifier:mycalIndentifier];
}
}
else if(!granted){
NSLog(#"Permission not granted");
}
else{
NSLog(#"error = %#", error.localizedDescription);
}
});
}];
}
}
this is the error I am getting :
Predicate call to calendar daemon failed: Error Domain=EKCADErrorDomain Code=1013 "The operation couldn’t be completed. (EKCADErrorDomain error 1013.)"
not saved = Error Domain=EKErrorDomain Code=14 "Calendar has no source" UserInfo=0x1d5f6950 {NSLocalizedDescription=Calendar has no source}
UPDATE :
I solved the problem using this link try it
Problem seems to be that the local calendar is hidden when iCloud is enabled so you need to handle both cases (iCloud enabled and not).
See this answer for a working solution: https://stackoverflow.com/a/15980556/72176
An app I'm building contains a catalogue of thousands of items, which need to be stored on the phone. Currently I am achieving this through CoreData, as logically it seemed like the best place to put it. I'm using GCD to run the CoreData insertion processes in the background and showing a progress bar / current percentage complete. This works as expected, however for only 5000 items, it's taking 8 minutes to complete on an iPhone 4. This application will be used on the 3GS and up, and will more likely contain 30/40 thousand items once it launches. Therefore this processing time is going to be horrifically long.
Is there any way I can use a CSV file or something to search through instead of storing each item in CoreData? I'm assuming there are some efficiency downfalls with an approach like this, but it would alleviate the excessive wait times. Unless there is another solution that would help with this problem.
Thanks.
EDIT:
I'm not sure how I'd go about saving the context at the end of the entire operation, as it uses a separate context within the loop. Any suggestions for this would be very much appreciated. I've got no idea how to progress with this.
Insertion Code Being Used
- (void) processUpdatesGCD {
NSArray *jsonArray=[NSJSONSerialization JSONObjectWithData:_responseData options:0 error:nil];
NSArray *products = [jsonArray valueForKey:#"products"];
NSArray *deletions;
if ([jsonArray valueForKey:#"deletions"] == (id)[NSNull null]){
self.totalCount = [products count];
} else {
deletions = [jsonArray valueForKey:#"deletions"];
self.totalCount = [products count] + [deletions count];
}
self.productDBCount = 0;
_delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = _delegate.managedObjectContext;
self.persistentStoreCoordinator = [managedObjectContext persistentStoreCoordinator];
_managedObjectContext = managedObjectContext;
// Create a new background queue for GCD
dispatch_queue_t backgroundDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
for (id p in products) {
// id product = p;
// Dispatch the following code on our background queue
dispatch_async(backgroundDispatchQueue,
^{
id product = p;
// Because at this point we are running in another thread we need to create a
// new NSManagedContext using the app's persistance store coordinator
NSManagedObjectContext *backgroundThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundThreadContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
NSFetchRequest *BGRequest = [[NSFetchRequest alloc] init];
NSLog(#"Running.. (%#)", product);
[BGRequest setEntity:[NSEntityDescription entityForName:#"Products" inManagedObjectContext:backgroundThreadContext]];
[BGRequest setIncludesSubentities:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"codes == %#", [product valueForKey:#"product_codes"]];
[BGRequest setPredicate:predicate];
NSError *err;
NSArray *results = [backgroundThreadContext executeFetchRequest:BGRequest error:&err];
if (results.count == 0){
// Product doesn't exist with code, make a new product
NSLog(#"Product not found for add/update (%#)", [product valueForKey:#"product_name"]);
NSManagedObject* newProduct;
newProduct = [NSEntityDescription insertNewObjectForEntityForName:#"Products" inManagedObjectContext:backgroundThreadContext];
[newProduct setValue:[product valueForKey:#"product_name"] forKey:#"name"];
[newProduct setValue:[product valueForKey:#"product_codes"] forKey:#"codes"];
if ([product valueForKey:#"information"] == (id)[NSNull null]){
// No information, NULL
[newProduct setValue:#"" forKey:#"information"];
} else {
NSString *information = [product valueForKey:#"information"];
[newProduct setValue:information forKey:#"information"];
}
} else {
NSLog(#"Product found for add/update (%#)", [product valueForKey:#"product_name"]);
// Product exists, update existing product
for (NSManagedObject *r in results) {
[r setValue:[product valueForKey:#"product_name"] forKey:#"name"];
if ([product valueForKey:#"information"] == (id)[NSNull null]){
// No information, NULL
[r setValue:#"" forKey:#"information"];
} else {
NSString *information = [product valueForKey:#"information"];
[r setValue:information forKey:#"information"];
}
}
}
// Is very important that you save the context before moving to the Main Thread,
// because we need that the new object is writted on the database before continuing
NSError *error;
if(![backgroundThreadContext save:&error])
{
NSLog(#"There was a problem saving the context (add/update). With error: %#, and user info: %#",
[error localizedDescription],
[error userInfo]);
}
// Now let's move to the main thread
dispatch_async(dispatch_get_main_queue(), ^
{
// If you have a main thread context you can use it, this time i will create a
// new one
// NSManagedObjectContext *mainThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
// [mainThreadContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
self.productDBCount = self.productDBCount + 1;
float progress = ((float)self.productDBCount / (float)self.totalCount);
int percent = progress * 100.0f;
// NSNumber *progress = [NSNumber numberWithFloat:((float)self.productDBCount / (float)self.totalCount)];
self.downloadUpdateProgress.progress = progress;
self.percentageComplete.text = [NSString stringWithFormat:#"%i", percent];
NSLog(#"Added / updated product %f // ProductDBCount: %i // Percentage progress: %i // Total Count: %i", progress, self.productDBCount, percent, self.totalCount);
if (self.productDBCount == self.totalCount){
[self updatesCompleted:[jsonArray valueForKey:#"last_updated"]];
}
});
});
}
if ([deletions count] > 0){
for (id d in deletions){
dispatch_async(backgroundDispatchQueue,
^{
id deleted = d;
// Because at this point we are running in another thread we need to create a
// new NSManagedContext using the app's persistance store coordinator
NSManagedObjectContext *backgroundThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundThreadContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
NSFetchRequest *BGRequest = [[NSFetchRequest alloc] init];
// NSLog(#"Running.. (%#)", deleted);
[BGRequest setEntity:[NSEntityDescription entityForName:#"Products" inManagedObjectContext:backgroundThreadContext]];
[BGRequest setIncludesSubentities:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"codes == %#", [deleted valueForKey:#"product_codes"]];
[BGRequest setPredicate:predicate];
NSError *err;
NSArray *results = [backgroundThreadContext executeFetchRequest:BGRequest error:&err];
if (results.count == 0){
// Product doesn't exist with code, make a new product
NSLog(#"Product not found, can't delete.. %#", [deleted valueForKey:#"product_name"]);
} else {
NSLog(#"Product found, deleting: %#", [deleted valueForKey:#"product_name"]);
// Product exists, update existing product
for (NSManagedObject *r in results) {
[backgroundThreadContext deleteObject:r];
}
}
// Is very important that you save the context before moving to the Main Thread,
// because we need that the new object is writted on the database before continuing
NSError *error;
if(![backgroundThreadContext save:&error])
{
NSLog(#"There was a problem saving the context (delete). With error: %#, and user info: %#",
[error localizedDescription],
[error userInfo]);
}
// Now let's move to the main thread
dispatch_async(dispatch_get_main_queue(), ^
{
self.productDBCount = self.productDBCount + 1;
float progress = ((float)self.productDBCount / (float)self.totalCount);
int percent = progress * 100.0f;
// NSNumber *progress = [NSNumber numberWithFloat:((float)self.productDBCount / (float)self.totalCount)];
self.downloadUpdateProgress.progress = progress;
self.percentageComplete.text = [NSString stringWithFormat:#"%i", percent];
NSLog(#"Deleted product %f // ProductDBCount: %i // Percentage progress: %i // Total Count: %i", progress, self.productDBCount, percent, self.totalCount);
if (self.productDBCount == self.totalCount){
[self updatesCompleted:[jsonArray valueForKey:#"last_updated"]];
}
/*
*
* Change the completion changes to a method. Check to see if the total number of products == total count. If it does, run the completion method.
*
*/
});
});
}
}
}
Put the IF inside the dispatch, run one save at the end
// Create a new background queue for GCD
dispatch_queue_t backgroundDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// id product = p;
// Dispatch the following code on our background queue
dispatch_async(backgroundDispatchQueue,
^{
NSManagedObjectContext *backgroundThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundThreadContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
for (id p in products) {
id product = p;
// Because at this point we are running in another thread we need to create a
// new NSManagedContext using the app's persistance store coordinator
NSFetchRequest *BGRequest = [[NSFetchRequest alloc] init];
NSLog(#"Running.. (%#)", product);
[BGRequest setEntity:[NSEntityDescription entityForName:#"Products" inManagedObjectContext:backgroundThreadContext]];
[BGRequest setIncludesSubentities:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"codes == %#", [product valueForKey:#"product_codes"]];
[BGRequest setPredicate:predicate];
NSError *err;
NSArray *results = [backgroundThreadContext executeFetchRequest:BGRequest error:&err];
if (results.count == 0){
// Product doesn't exist with code, make a new product
NSLog(#"Product not found for add/update (%#)", [product valueForKey:#"product_name"]);
NSManagedObject* newProduct;
newProduct = [NSEntityDescription insertNewObjectForEntityForName:#"Products" inManagedObjectContext:backgroundThreadContext];
[newProduct setValue:[product valueForKey:#"product_name"] forKey:#"name"];
[newProduct setValue:[product valueForKey:#"product_codes"] forKey:#"codes"];
if ([product valueForKey:#"information"] == (id)[NSNull null]){
// No information, NULL
[newProduct setValue:#"" forKey:#"information"];
} else {
NSString *information = [product valueForKey:#"information"];
[newProduct setValue:information forKey:#"information"];
}
} else {
NSLog(#"Product found for add/update (%#)", [product valueForKey:#"product_name"]);
// Product exists, update existing product
for (NSManagedObject *r in results) {
[r setValue:[product valueForKey:#"product_name"] forKey:#"name"];
if ([product valueForKey:#"information"] == (id)[NSNull null]){
// No information, NULL
[r setValue:#"" forKey:#"information"];
} else {
NSString *information = [product valueForKey:#"information"];
[r setValue:information forKey:#"information"];
}
}
}
// Is very important that you save the context before moving to the Main Thread,
// because we need that the new object is writted on the database before continuing
// Now let's move to the main thread
dispatch_async(dispatch_get_main_queue(), ^
{
// If you have a main thread context you can use it, this time i will create a
// new one
// NSManagedObjectContext *mainThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
// [mainThreadContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
self.productDBCount = self.productDBCount + 1;
float progress = ((float)self.productDBCount / (float)self.totalCount);
int percent = progress * 100.0f;
// NSNumber *progress = [NSNumber numberWithFloat:((float)self.productDBCount / (float)self.totalCount)];
self.downloadUpdateProgress.progress = progress;
self.percentageComplete.text = [NSString stringWithFormat:#"%i", percent];
NSLog(#"Added / updated product %f // ProductDBCount: %i // Percentage progress: %i // Total Count: %i", progress, self.productDBCount, percent, self.totalCount);
NSDate *currentProcessedDate = [NSDate date];
NSTimeInterval timeSinceStarted = [currentProcessedDate timeIntervalSinceDate:self.startProcessing];
NSInteger remainingProcesses = self.totalCount - self.productDBCount;
float timePerProcess = timeSinceStarted / (float)self.productDBCount;
float remainingTime = timePerProcess * (float)remainingProcesses;
self.timeRemaining.text = [NSString stringWithFormat:#"ETA: %0.0f minutes", fmodf(remainingTime, 60.0f)];
if (self.productDBCount == self.totalCount){
[self updatesCompleted:[jsonArray valueForKey:#"last_updated"]];
}
/*
*
* Change the completion changes to a method. Check to see if the total number of products == total count. If it does, run the completion method.
*
*/
});
}
NSError *error;
if(![backgroundThreadContext save:&error])
{
NSLog(#"There was a problem saving the context (add/update). With error: %#, and user info: %#",
[error localizedDescription],
[error userInfo]);
}
});
Ok, here's your problem.
Every time you insert a record, you do a save operation to the context.
Now, don't do it, that's what takes alot of time.
Do the save operation once, in the end of the loop, not every time you insert a record.
In your case I would check what is really time consuming?
Is it downloading data, is it importing data to CoreData?
Where do you get data from? Do you download it or you have it in Application Bundle?
CoreData is faster then CSV file. So it wont make your app faster.
Some tricks:
While importing data just save context at the end of the process. Do not save context in a loop.
If you do not need to download data and can put in the bundle, you can create coredata file in the simulator, put in the bundle and copy the file on first launch. It is really much more faster then importing data.
I’m missing the boat on something that I thought I had the hang of and was hoping someone here could help.
I’m using Xcode version 4.4 and my project is using Core Data, Storyboards and ARC.
My project has the following 3 entities.
All works well with the Livestock and Notes entities. But when I try to save data to the Taxonomy entity nothing happens. I get no error but the data is not saved.
Is it ok that I not use an array for the fetched results? Based on my predicate, I’m expecting only one object to be returned so I thought I didn’t need an array. Below is my code that does the saving. I have verified that data is being passed in from the view's text variables.
AppDelegate *appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Livestock" inManagedObjectContext:managedObjectContext];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"tank == %# AND type == %# AND name == %#", self.detailTank, self.detailType, self.detailName];
NSError *error = nil;
Livestock *livestock = [[managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
if (error) {
NSLog(#"TaxonViewController: saveTaxonomy: Retrieving Livestock Record for saving: error = %#", error);
}
Taxonomy *taxonomy = livestock.taxonChildren;
taxonomy.kingdom = self.kingdom.text;
taxonomy.phylum = self.phylum.text;
taxonomy.classs = self.classs.text;
taxonomy.order = self.order.text;
taxonomy.family = self.family.text;
taxonomy.genus = self.genus.text;
taxonomy.species = self.species.text;
taxonomy.common = self.common.text;
taxonomy.livestockParent = livestock;
error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Taxonomy save error. error = %#, userInfo = %#", error, [error userInfo]);
}
Any insight is greatly appreciated! Thanks!
UPDATE 1:
Modified code to test for NULL taxonChildren value. This solved it for me. Thanks Jesse!
if (livestock.taxonChildren == NULL) {
Taxonomy *taxonomy = [NSEntityDescription insertNewObjectForEntityForName:#"Taxonomy" inManagedObjectContext:managedObjectContext];
taxonomy.kingdom = self.kingdom.text;
taxonomy.phylum = self.phylum.text;
taxonomy.classs = self.classs.text;
taxonomy.order = self.order.text;
taxonomy.family = self.family.text;
taxonomy.genus = self.genus.text;
taxonomy.species = self.species.text;
taxonomy.common = self.common.text;
taxonomy.livestockParent = livestock;
}
else {
Taxonomy *taxonomy = livestock.taxonChildren;
taxonomy.kingdom = self.kingdom.text;
taxonomy.phylum = self.phylum.text;
taxonomy.classs = self.classs.text;
taxonomy.order = self.order.text;
taxonomy.family = self.family.text;
taxonomy.genus = self.genus.text;
taxonomy.species = self.species.text;
taxonomy.common = self.common.text;
taxonomy.livestockParent = livestock;
}
Is your livestock.taxonChildren nil? You'll have to insert an instance of that object into your context first; it won't create one automatically, even with a one-to-one relationship.
Note that you should not test for error like this:
if (error) { ... }
because error may be garbage when the fetch request is successful. You should instead test the return value:
NSArray *results = [managedObjectContext executeFetchRequest...]
if (!results) { ... }
App using tableview and NSFetchedResultsController.
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSMutableArray *array=[[NSMutableArray alloc] init];
for (int i=0; i<[self.selectedEvents count]; i++) {
CustomDictionary *dic=[selectedEvents objectAtIndex:i];
if (dic.isSelected) {
Event *evt=[fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
[array addObject:evt];
}
}
for (Event *evt in array) {
[context deleteObject:evt];
}
NSError *error;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
I got an error
Serious application error.
Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no object at index 1 in section at index 0'
In the code above selectedevents is an array whose count is equal to that of objects in fetched results controller.