I created an RSS reader for a blog. I am retrieving the articles of the blog and am filling a UITableView with them in a navigation controller. When a user taps in a row, a web view comes up with the original article on the blog. I use Core Data in order to save the title, link and date of the articles with the following code:
NSManagedObjectContext *context = [self managedObjectContext];
rssFeed = [NSEntityDescription insertNewObjectForEntityForName:#"RSS" inManagedObjectContext:context];
[rssFeed setValue:title forKey:#"title"];
[rssFeed setValue:link forKey:#"link"];
[rssFeed setValue:date forKey:#"date"];
Then I call it with the following code:
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"RSS"];
self.feedArray = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
NSManagedObject *matches = nil;
if ([_feedArray count] == 0)
{
NSLog(#"No matches");
}
else
{
for (int i = 0; i < [_feedArray count]; i++)
{
matches = _feedArray[i];
}
}
If I print the feedArray in the table view, the results are not so clear. I get something like null between the articles, and they aren't in the right order. Can anyone help me understand what happens?
And the error message:
Terminating app due to uncaught exception 'NSRangeException', reason: ' -[__NSArrayM objectAtIndex:]: index 25 beyond bounds [0 ..
24]'
Related
I've an annoying problem with Core Data. My app need to get contacts from iPhone and save it in my database. I'm trying to do that in background thread.
I use above code for that:
[self performSelectorInBackground:#selector(fetchingContact) withObject:nil];
-(void)fetchingContact{
// Some Code
for (int i = 0; i < nPeople; i++)
{
//Some Code
NSManagedObjectContext *context = [APP_DELEGATE managedObjectContext];
ABRecordID recordID = ABRecordGetRecordID(person);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Contact" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(contactId = '%d')",recordID]];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
//crash
NSArray *contactObjArray = [context executeFetchRequest:fetchRequest error:&error];
//crash
if (error) {}
Contact *contacts;
if (contactObjArray.count == 0) {
contacts = [NSEntityDescription insertNewObjectForEntityForName:#"Contact" inManagedObjectContext:context];
}else {
contacts = [contactObjArray objectAtIndex:0];
}
//Some Code
}
}
In AppDelegate:
- (NSManagedObjectContext *)managedObjectContext
{
NSLog(#"managedObjectContext");
// Returns ;the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc]initWithConcurrencyType: NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
Here I try to save in my Core Data but it crash with error :
* Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection <__NSCFSet: 0x15fad880> was mutated while being enumerated.'` at this line:
NSArray *contactObjArray = [context executeFetchRequest:fetchRequest error:&error];
I search already online, I found a lot of things but nothing helps me. When I run that, there is no place where Core Data is changed, or Contact entity. That does this error very strange.
If I run it in main thread I get no errors/ no crash, but if the app is quit in that time (while is executed) I lose all content from Contact
Please, any help. Tell me if I need to provide more information.
This error happen when you are modifying core data while you try to get them.
That also could be cause of the loop you're doing, you are inserting a new object in coredata without saving before you do an other retrieve. Try saving your managedobjectcontext :
favorite
I have an annoying problem with Core Data. My app need to get contacts from iPhone and save it in my database. I'am trying to do that in background thread.
I use above code for that:
[self performSelectorInBackground:#selector(fetchingContact) withObject:nil];
-(void)fetchingContact{
// Some Code
for (int i = 0; i < nPeople; i++)
{
//Some Code
NSManagedObjectContext *context = [APP_DELEGATE managedObjectContext];
ABRecordID recordID = ABRecordGetRecordID(person);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Contact" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(contactId = '%d')",recordID]];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *contactObjArray = [context executeFetchRequest:fetchRequest error:&error];
if (error) {}
Contact *contacts;
if (contactObjArray.count == 0) {
contacts = [NSEntityDescription insertNewObjectForEntityForName:#"Contact" inManagedObjectContext:context];
[context save:&error];
}else {
contacts = [contactObjArray objectAtIndex:0];
}
//Some Code
}
}
If that doesn't solve your problem, maybe check if your method is called multiple times.
I have the following CoreDataModel:
Each park is related to a set of inspections and each inspection is inversely related to the park. I am loading the data into the park from a web service response. So, from inside the NSManagedObject subclass of Park, I need to query for all the related Inspections(there will be only one satisfying that condition) with a particular inspectionID and assigmentID and create it if not exists, for which I used the following function:
- (Inspection *)getInspectionWithId:(NSInteger)inspectionId assignmentId:(NSInteger)assignmentId {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//Crash in the following line
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY (self.inspections.inspectionID == %d AND self.inspections.assignmentID == %d)", inspectionId, assignmentId];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *array = [[[Model sharedModel] managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (array == nil || array.count < 1) {
NSEntityDescription *inspectionEntity = [NSEntityDescription entityForName:#"Inspection" inManagedObjectContext:[[Model sharedModel] managedObjectContext]];
return (Inspection *)[[NSManagedObject alloc] initWithEntity:inspectionEntity insertIntoManagedObjectContext:[[Model sharedModel] managedObjectContext]];
}
else {
return (Inspection *)array[0];
}
}
But the app is crashing with the following exception. I am not sure of the condition in the predicate to use for this. Can anyone please help?
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'Unable to parse the format
string "ANY (self.inspections.inspectionID == %d AND
self.inspections.assignmentID == %d)"'
I have a core data model with single entity objects. Then i load these objects into an array and display them in a table with default cell styles. In the navigation bar there is a delete button that when a row is selected (e.g. object) and this button clicked, it should delete the row and the respective object in the core model. I have seen examples of deleting rows but with a delete button displayed in each row. I want to have only one button in the navig bar and be able to control the deleting of rows from it. I have tried the code below but it does not work. I can't find a way to make it identify the row selected in the delete method. Any help is appreciated.
In viewdidload of the tableviewcontroller class:
UIBarButtonItem *delButton = [[UIBarButtonItem alloc]
initWithTitle:#"Del"
style: UIBarButtonItemStyleBordered
target: self
action:#selector(delItemToArray)];
self.managedObjectContext = ((ecoAppDelegate *) [UIApplication sharedApplication].delegate).managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"FavoritesInfo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
fetchRequest.resultType = NSDictionaryResultType;
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:#"name", nil]];
NSError *error=nil;
self.favArr=[self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error!=nil) {
NSLog(#" fetchError=%#,details=%#",error,error.userInfo);
}
self.favName = [self.favArr valueForKey:#"name"];
//Delete Item To table array and delete object to data core model
- (void)delItemToArray {
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
[context deleteObject:appDelegatee.favoritesInfo]; // favoritesInfo is the NSManagedObject
NSIndexPath *indexPath;
[favName removeObjectAtIndex:indexPath.row];
[appDelegatee saveContext]; // to save changes in the core data model
[self.tableView reloadData];
}
Error:
[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x8852b90
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x8852b90'
You might got a warning like Incompatible pointer types initializing 'NSMutableArray *' with an expression of type 'NSArray *' here
self.favArr=[self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
You should make the fetched array mutable for removing objects from it. You can do it like
self.favArr = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
Also change this
self.favName = [self.favArr valueForKey:#"name"];
to
self.favName = [[self.favArr valueForKey:#"name"]mutableCopy];
I am trying to populate an NSArray with a collection of data I get from CoreData. But my array doesnt seem to be populating with the data. I have the following code to retrieve the data:
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]
initWithEntityName:#"WeightLog"];
self.contactarray = [[managedObjectContext executeFetchRequest:fetchRequest
error:nil] mutableCopy];
And I am using the following for loop to populate the NSArray with the data I collect from WeightLog for a particular field.
for (int i =0; i<=self.contactarray.count; i++) {
NSManagedObject *device = [self.contactarray objectAtIndex:i];
[titleNames addObject:device];
}
Just so you know contactarray is a property in my .h file of the following format:
#property (strong) NSMutableArray *contactarray;
Can you tell me where I am going wrong please, I am fairly new to iOS Development, if it doesn't show.
Thank you in advance
Initialise titleNames array before use. Try this,
titleNames = [[NSMutableArray alloc] init];
for (int i =0; i<=self.contactarray.count; i++) {
NSManagedObject *device = [self.contactarray objectAtIndex:i];
[titleNames addObject:device];
}
Just call this user-defined method. for ex -
self.titleNames = [self selectAllRowInEntity:#"WeightLog"];
-(NSArray *) selectAllRowInEntity:(NSString *) entityName
{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fRequest;
NSEntityDescription *eDesc;
NSArray *arr;
fRequest = [[NSFetchRequest alloc] init];
eDesc = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fRequest setEntity:eDesc];
arr = [managedObjectContext executeFetchRequest:fRequest error:nil];
return arr;
}
This line here:
self.contactarray = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
This is a cardinal sin in Core Data - to not use the provided error parameters.
NSError *error;
self.contactarray = [[managedObjectContext executeFetchRequest:fetchRequest
&error] mutableCopy];
if (!self.contactArray) {
// Fetch Requests return a nil on error, in which case you should check the error.
NSLog(#"Error occurred: %#", error);
} else {
// do whatever you want with the array
}
Now run your code and look at the console and you might see the reason for the error.
Edited to add
Following a comment:
You should always check that the return of the method is nil before evaluating the error object. For Cocoa (and Cocoa-Touch) methods this is the only time that the error parameter is guaranteed to be valid.
This is taken from the Error Handling Programming Guide
I hope this isn't a duplicate question. I can't seem to find anything similar. Most core data questions seem to be about new object creation...
I have a program with a database of around 23,000 items. I'm attempting to create an export/import function to send data to other devices (not linked with iCloud).
The export works just fine, as does the email...
I have the import functioning, but it functions slowly (and, more on this later, doesn't seem to work well with the iPhone 5 or iPad 3)
I have a function that parses the data I'm importing into an NSArray (_importedRows), then I run the following code:
self.managedObjectContext = [(AppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObjectContext *ctx = self.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"CHECKLIST"
inManagedObjectContext:ctx];
[fetchRequest setEntity:entity];
ImportedData *importedData;
NSString *verify;
NSError *error = nil;
NSManagedObject *updatedObject;
NSArray *matchingItems;
for (int i = 0; i < [_importedRows count]; i++) {
importedData = [_importedRows objectAtIndex:i];
verify = importedData.uniqueID;
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"uniqueID == %#", verify]];
[fetchRequest setFetchLimit:1];
matchingItems = [ctx executeFetchRequest:fetchRequest error:&error];
for (updatedObject in matchingItems) {
HUD.detailsLabelText = [NSString stringWithFormat:#"Updating %#" , [updatedObject valueForKey:#"figureName"]];
[updatedObject setValue:importedData.numberOwned forKey:#"numberOwned"];
[updatedObject setValue:importedData.numberWanted forKey:#"wishList"];
[updatedObject setValue:importedData.availableTrade forKey:#"tradeList"];
}
[ctx save:&error];
if (error != nil) {
NSLog(#"error saving managed object context: %#", error);
}
}
Basically, I'm grabbing a core data entity, and then looping through my array checking for matches. When I find a match (the uniqueID predicate), I'm updating the object with the imported data. This code works fine on my iPhone 4s, but rather slowly. 4,000 items takes around 4-5 minutes. Am I doing anything blatantly wrong? Should I be calling the save function more frequently?
As a bonus, for some reason this code almost never works when I test it on an iPhone 5. 9 times out of 10 (and 50% of the time on my iPad 3) I get a
"Jan 14 08:06:44 : * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'"
in the console. Thoughts?
Let me know if more details are needed!
UPDATE:
It seems that handleOpenURL is being called twice... once in applicationdidfinishlaunching
NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
if (url != nil && [url isFileURL]) {
[self.window.rootViewController performSelector:#selector(showWithLabel:) withObject:url afterDelay:6];
}
and once here:
-(BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{ if (url != nil && [url isFileURL]) {
[self.window.rootViewController performSelector:#selector(showWithLabel:) withObject:url];
}
return YES;
}
I have to lave both those in the app delegate, otherwise the function won't always get called (once is for when the application launches, and once if it the application was already in the background, I believe) - I've added a check to prevent it from launching a second time within the showWithLabel thread, but it doesn't seem like that is a very elegant solution...
UPDATE: #mundi advised cleaning up the fetchedresults code as follows:
NSArray *importedIDs = [_importedRows valueForKeyPath:#"uniqueID"];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"CHECKLIST"
inManagedObjectContext:ctx];
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"uniqueID in %#", importedIDs];
NSError *error = nil;
NSManagedObject *updatedObject;
NSArray *matchingItems;
matchingItems = [ctx executeFetchRequest:fetchRequest error:&error];
ImportedData *importedData;
for (int i = 0; i < [_importedRows count]; i++) {
importedData = [_importedRows objectAtIndex:i];
for (updatedObject in matchingItems) {
if ([importedData.uniqueID isEqualToString:[updatedObject valueForKey:#"uniqueID"]]) {
HUD.detailsLabelText = [NSString stringWithFormat:#"Updating %#" , [updatedObject valueForKey:#"figureName"]];
[updatedObject setValue:importedData.numberOwned forKey:#"numberOwned"];
[updatedObject setValue:importedData.numberWanted forKey:#"wishList"];
[updatedObject setValue:importedData.availableTrade forKey:#"tradeList"];
}
}
}
[ctx save:&error];
I'm sure it could still be a little cleaner and the actual updating portion (I'm not sure how to do it other than compare each item in the fetchedresults with each item in the initial array to make sure they are updated correctly, but the combined fetchedresults increased the speed tremendously (originally 240 seconds for 4000 items, now between 80-120 seconds)
sorting arrays first, then updating in order speeds it up tremendously yet again:
NSArray *matchingItemsSorted;
matchingItemsSorted = [matchingItems sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSString *first = [a valueForKey:#"uniqueID"];
NSString *second = [b valueForKey:#"uniqueID"];
return [first caseInsensitiveCompare:second];
}];
NSArray *importedRowsSorted;
importedRowsSorted = [_importedRows sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSString *first = [a valueForKeyPath:#"uniqueID"];
NSString *second = [b valueForKeyPath:#"uniqueID"];
return [first caseInsensitiveCompare:second];
}];
int i = 0;
for (updatedObject in matchingItemsSorted) {
NSLog(#"do we match? %# : %#", [[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"uniqueID"], [updatedObject valueForKey:#"uniqueID"]);
HUD.detailsLabelText = [NSString stringWithFormat:#"Updating %#" , [updatedObject valueForKey:#"figureName"]];
[updatedObject setValue:[[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"numberOwned"] forKey:#"numberOwned"];
[updatedObject setValue:[[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"numberWanted"] forKey:#"wishList"];
[updatedObject setValue:[[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"availableTrade"] forKey:#"tradeList"];
i++;
}
13 seconds or so for 4000 items with the nslog there... the only weird thing now is that when I comment out the nslog, it frequently crashes... is it happening so fast it's breaking core data - when it doesn't crash, it only takes about 4 seconds?
Thanks,
Zack
You have two nested loops. Use this pattern to speed this up:
NSArray *importedIDs = [_importedRows valueForKeyPath:#"uniqueID"];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"CHECKLIST"
inManagedObjectContext:ctx];
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"uniqueID in %#", importedIDs];
Like this you can fetch one array with all matching items.