So I've got an NSFetchedResultsController that I activate on ViewDidLoad with the managedobjectcontext that has been passed on from the appdelegate on load.
I put a predicate on some field let's call it "sectionNumber" and say it needs to equal 1 in my predicate.
NSFetchResultsController works fine until I add a new object to the MOContext...
I use MyMO *newObj = [NSEntityDescription insertnewentity]...
start filling the different fields
[newobj setName:#"me"];
[newobj setAge:12];
etc...
Once I put [newobj setSectionNumber:1] - it finds it at that very instant and causes the app to crash with different weird errors that all lead to EXC_BAD_ACCESS.
All of this happens on the MAIN THREAD.
Any ideas why? How could one get around that?
UPDATE:
It only happens when I use my saveMOC method which is called at the end of an NSXMLParser specific thread I spawned off. The saveMOC is called on a successful parse with the [self performSelectorOnMainThread].... If i just added the extra managedobject via ViewDidLoad (just to check weather this is related somehow to to threading) the problem does NOT occur.
So it's obviously something with the new thread even tho the selector should have been run on the main thread.
UPDATE #2:
This is my spawned thread for the XML Parser:
-(void)getAndParseXML {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
DLog(#"Online storage");
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:theUrl];
XMLTranslator *translator = [[XMLTranslator alloc] init];
[parser setDelegate:translator];
if ([parser parse]) {
//success call MOC change routine on main thread
DLog(#"success parsing");
[self performSelectorOnMainThread:#selector(saveMOC:) withObject:translator waitUntilDone:NO];
} else {
DLog(#"error: %#",[parser parserError]);
}
[parser setDelegate:nil];
[parser release];
DLog(#"XML parsing completed");
[pool release];
}
Then this is my saveMOC:
-(void)saveMOC:(XMLTranslator*)translator {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
for (NSDictionary *dict in [translator retrievedData]) {
APost *newPost = [NSEntityDescription insertNewObjectForEntityForName:#"APost"
inManagedObjectContext:managedObjectContext];
//parse time into NSDate
[newPost setTime:[formatter dateFromString:[dict objectForKey:#"time"]]];
//author, category, description
[newPost setAuthor:[dict objectForKey:#"author"]];
[newPost setCategory:[dict objectForKey:#"category"]];
[newPost setContent:[dict objectForKey:#"description"]];
//create a post id so that the validation will be alright
[newPost setPostid:[NSNumber numberWithInteger:[[dict objectForKey:#"postid"] integerValue]]];
[newPost setSectionDesignator:sectionDesignator];
}
This saveMoc method continues and has a [managedobjectcontext save:&error] and more... but it's not relevan to our case as my method crashes I've discovered thru commenting one line after another at the point where I set the sectionDesignator since it equals to the current predicate in my NSFetchedResultsController.
The problem is most likely in the NSFetchedResultsController delegate methods or the lack thereof.
When you add a new object to any context and then save the context, that changes the persistent store which triggers the FRC on any thread to begin an update of the tableview. All the index paths change, especially if you set a value for an attribute used as a sectionNameKeyPath. If the table ask for a cell during the update, it will cause a crash because the table can ask for a cell at a index path rendered invalid by the insertion of the new managed object.
You need to make sure you implement the FRC's delegate methods and that you send the table a beginUpdate message to freeze it while the FRC changes all its index paths.
I am sorry to admit that the problem this whole time was releasing an array that held the sort descriptors in the fetch request that was used within the FRC.
Looking at alot of examples I released that array tho unlike the examples I created my array with [NSArray arrayWithObject:.............];
So there was an overrelease each time the fetch request was accessed more than once.
Feel free to close this. Thank you everybody for your help. I discovered this when peter wrote to look at the whole stack and not just one frame.
I have further analyzed the problem and have realized it occurs inside the loop.
I have further understood that it only happens when I have more than one object, meaning that one FRC takes over after an object insertion into MOC and tries to come back to the for loop, it tries to access an object or a reference that's not there. I haven't found what object causes it and how to retain it properly.
Any suggestions?
Consider the following:
for (int i=0; i<2; i++) {
NSLog(#"%i",i);
APost *thePost = [NSEntityDescription insertNewObjectForEntityForName:#"HWBPost" inManagedObjectContext:managedObjectContext];
[thePost setCategory:#"CAAA"];
[thePost setContent:#"SSSSSS"];
[thePost setSectionDesignator:sectionDesignator];
}
If I change the for loop to i<1 meaning it only runs once, the app does NOT crash. As soon as it is more than one object insertion the app crashes.
Related
I have two view controllers. One a searchResults tableview controller (VC1) where the user see a list of rows matching a selection criteria and other ViewDetail (tableview controller) (VC2) where the user sees Detail for the chosen row of VC1. The info required to fetch detail for the chosen row along with the managedContext reference are passed from VC1 to VC2 in the prepareforsegue method of VC1 by setting the relevant properties of VC2. During my test, I switched between (using the navigation controller back button) VC1 and VC2 each time selecting a different row on VC1 to see the detail of a different item. This works normally for 7-15 times of switching but crashes suddenly after some attempts of switching. I have investigated this as far as I could but stuck without a solution and hence posting this. Please help. The error is that a particular Array is out of bounds for index 0. While I understand however, I do not expect this array which is populated by results of a fetch request to be empty. Hence I suspect that there is something wrong with the managedcontext. Snippet of code from VC2 is provided
//All this is in ViewDidLoad of VC2 App crashes at the last line of this snippet. trying to get an object at index 0 which is non existent but should not be ...
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#" cameraid == %#",(NSNumber *)self.selectedCameraid];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Cameras"
inManagedObjectContext:self.managedContext];
//Configure core data request
[request setEntity:entity];
[request setPredicate:predicate];
//Execute request
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"Some error in fetching results");
}
NSLog(#"Mutable fetch results data %#",mutableFetchResults);
self.resultsArray = mutableFetchResults;
Cameras *rowdata = [self.resultsArray objectAtIndex:0]; //Cameras is a managed object
After a bit of further digging I found the issue... I am type casting a string as NSNumber (NSNumber *)self.selectedCameraid in the above code. Apparently this is what has been causing the crash. I converted the NSString object to a NSNumber object using NSNumberformatter and everything seems to work fine. I realised it is not safe to type cast objects in this manner.
I want to create a new UILocalNotification every time I enter a certain method. I would assume this would have to be done by reading from an array or something along this line but I cannot figure it out. How do I do such a thing dynamically without hardcoding something like the following:
-(void) createNotification
{
UILocalNotification *notification1;
}
Now I would like to be able to create notification2, notification3, etc etc each time I enter createNotification. For the specific reason that then I can cancel the appropriate notification without cancelling them all.
The following is what I have attempted, perhaps Im way off... maybe not. Either way if someone could provide some input, would be appreciated. Thanks!
-(void) AddNewNotification
{
UILocalNotification *newNotification = [[UILocalNotification alloc] init];
//[notificationArray addObject:newNotification withKey:#"notificationN"];
notificationArray= [[NSMutableArray alloc] init];
[notificationArray addObject:[[NSMutableDictionary alloc]
initWithObjectsAndKeys:newNotification,#"theNotification",nil]];
}
You are almost there: using an array is certainly the right thing to do! The only problem is that you keep creating a new instance of the array every time you go through your AddNewNotification method. You should make notificationArray an instance variable, and move its initialization code notificationArray= [[NSMutableArray alloc] init]; to the designated initializer of the class where notificationArray is declared.
If you would like to give each notification that you insert an individual key by which you can find it later, use NSMutableDictionary instead of NSMutableArray. Re-write the AddNewNotification method as follows:
-(void) addNewNotificationWithKey:(NSString*)key {
UILocalNotification *newNotification = [[UILocalNotification alloc] init];
[notificationDict setObject:[[NSMutableDictionary alloc]
initWithObjectsAndKeys:newNotification,#"theNotification",nil]
forKey:key];
}
When you call the addNewNotificationWithKey: method, you'd be able to provide a key for the newly added notification, for example
[self addNewNotificationWithKey:#"notification1"];
[self addNewNotificationWithKey:#"notification2"];
and so on.
I'm working on a RSS Reader using this tutorial. All table cells data come from a NSMutableArray instance (_allEntries). Then I import
EGOTableViewPullRefresh and add [self refresh] in -(void)reloadTableViewDataSource (self.refresh is a method to populate data of allEntries).
Then pull to refresh works but cells got duplicated every time I refresh. I tried to solve it in two ways.
When download data from internet, add if (![_allEntries containsObject:entry]) before [_allEntries insertObject:entry atIndex:insertIdx] but it didn't work, maybe I should use entry.title or some other attribute in the object to compare but it's not effective.
Like what I did in -viewDidLoad, add self.allEntries = [NSMutableArray array], but I don't know where should I put this line.
Is there anyone who can give me a direction?
[EDIT]
There's no too much logic in viewDidLoad, just
self.allEntries = [NSMutableArray array];
self.queue = [[NSOperationQueue alloc] init]; //add download&parse operation to a queue
self.feeds = [self getFeeds]; //load feeds from local file
And I put [self refresh] in reloadTableViewDataSource, the first time I open my app, there's nothing showed in the tableview. Then I pull to refresh, it works. Then pull to refresh again, it got duplicated.This is the "refresh" method.
- (void)refresh {
for (NSString *feed in _feeds) {
NSURL *url = [NSURL URLWithString:feed];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[_queue addOperation:request];
}
}
I want to rebuild the array so I write self.allEntries = [NSMutableArray array] again but it turns out "Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (140)". So as mentioned, I really get confused about where should I put this line.Thx~~
The logic you have in viewDidLoad that builds your array should be moved to its own method (reloadTableViewData), and then you would just call that method in viewDidLoad.
[self reloadTableViewData];
You would also call that same method when you do the pull to refresh.
Make sure you are rebuilding that array and not just adding objects to the existing one.
I have a UITableView which displays images. Every cell has an image and every time a cell loads, I call a selector (from the cellForRowAtIndexPath) in the background like this:
[self performSelectorInBackground:#selector(lazyLoad:) withObject:aArrayOfData];
The only problem is that sometimes I get a crash (because I am changing data in the background while it's trying to be read elsewhere). Here's the error:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <CALayerArray: 0xce1a920> was mutated while being enumerated.'
When updating the data in the background, should I move it to the main selector and change it? Or should I call the #selector() differently?
Thanks!
If you can leave the operation on the main thread and have no lagginess nor problems you are done.
However: Let's assume you've already done that and encounter problems. The answer is: don't modify the array in the lazy load. Switch to the main thread to modify the array. See Brad's answer here:
https://stackoverflow.com/a/8186206/8047
for a way to do it with blocks, so you can send your objects over to the main queue (you should probably also use GCD for the call to the lazy load in the first place, but it's not necessary).
You can use #synchronized blocks to keep the threads from walking over each other. If you do
#synchronized(array)
{
id item = [array objectAtIndex:row];
}
in the main thread and
#synchronized(array)
{
[array addObject:item];
}
in the background, you're guaranteed they won't happen at the same time. (Hopefully you can extrapolate from that to your code—I'm not sure what all you're doing with the array there..)
It seems, though, like you'd have to notify the main thread anyway that you've loaded the data for a cell (via performSelectorOnMainThread:withObject:waitUntilDone:, say), so why not pass the data along, too?
Given the term 'lazy load' I am assuming that means you are pulling your images down from a server. (If the images are local then there is really no need for multithreading).
If you are downloading images off a server I would suggest using something along these lines (using ASIHTTPRequest)
static NSCache *cellCache; //Create a Static cache
if (!cellCache)//If the cache is not initialized initialize it
{
cellCache = [[NSCache alloc] init];
}
NSString *key = imageURL;
//Look in the cache for image matching this url
NSData *imageData = [cellCache objectForKey:key];
if (!imageData)
{
//Set a default image while it's loading
cell.icon.image = [UIImage imageNamed:#"defaultImage.png"];'
//Create an async request to the server to get the image
__unsafe_unretained ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:imageURL]];
//This code will run when the request finishes
[request setCompletionBlock:^{
//Put downloaded image into the cache
[cellCache setObject:[request responseData] forKey:key];
//Display image
cell.icon.image = [UIImage imageWithData:[request responseData]];
}];
[request startAsynchronous];
}
else
{
//Image was found in the cache no need to redownload
cell.icon.image = [UIImage imageWithData:imageData];
}
i am stuck with my first GCD and first core-data using application =)
two views access the same data (which is handled by a single DAO).
if i wait for the current view to finish loading its content no problem occors when changing view.
however: if i change the view (its tabbased) while one controller tries to fetch data from my model, the new controller tries the same and the threads 'collide' and my application freezes.
the freeze occurs in this line of code of my DAO:
NSArray *results = [managedObjectContext executeFetchRequest:fetch error:&error];
reloadAllMonth() accesses the fetch routine of my DAO
how i load the data in the first controller:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[self reloadAllMonth];
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self.allMonthTable reloadData];
});
in the second viewcontroller the first thing i do is update my DAO, this of course uses (beneath others) the very same fetch routine i called before:
[self.dataHandler updateData];
i have tried two approaches so far:
first using a c-semaphore:
-(NSArray *)fetchAllMonthExpenses{
//#return: array of all expenses in month (day & month type)
NSNumber *monthNumber = [self getMonthNumber:[NSDate date]];
NSEntityDescription *exp = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetch = [[NSFetchRequest alloc]init];
[fetch setEntity:exp];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"month == %#",monthNumber]];
NSError *error = nil;
sem_wait(&isLoading);
NSArray *results = [self.managedObjectContext executeFetchRequest:fetch error:&error];
sem_post(&isLoading);
return results;
}
second using the synchronized directive
-(NSArray *)fetchAllMonthExpenses{
//#return: array of all expenses in month (day & month type)
NSNumber *monthNumber = [self getMonthNumber:[NSDate date]];
NSEntityDescription *exp = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetch = [[NSFetchRequest alloc]init];
[fetch setEntity:exp];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"month == %#",monthNumber]];
NSError *error = nil;
#synchronized(self.class){
NSArray *results = [self.managedObjectContext executeFetchRequest:fetch error:&error];
return results;
}
}
sadly both of the approaches did not work, the application freezes whatever i do.
so my question is: what am i doing wrong (as i mentioned first time using threads), what am i missing, where should i look?
this has been keeping me busy for 2 days now and i cant seem to wrap my head around it :/
An NSManagedObjectContext and all of the NSManagedObjects inside it are not thread safe.
Whatever thread you use to create the context, that needs to be the only thread where you do anything relating to that context. Even just reading values from one of the managed object must be done on that thread and not on any other thread.
If you need two threads which both deal with the same database, you've got two options:
use dispatch_sync() to jump into the other thread momentarily to perform all read/write operations on the managed objects and/or the context
Or:
create a second NSManagedObjectContext in the other thread for the same database, and keep any changes made to the two contexts in sync.
The first option is much easier, but may remove much of the benefits of threading. The second option is harder, but it can be done, and there is a fairly good API for keeping two contexts on different threads in sync.
Lookup the Core Data Programming Guide for more details.