iOS - Thread does not going back to the main thread - iphone

I having trouble with my threads.
After i segue a couple of times between 2 screen when the thread is busy. The thread don't perform every line.., The breakpoint just disappear when it has to return to the main thread.
Can somebody please help me ?
I release the thread when the view is unload.
Thanks,
- (void)fetchFeedDataIntoDocument
{
NSString * labelString = [NSString stringWithFormat:#"Feed Fetcher %#", self.pageTitle];
const char *label = [labelString UTF8String];
self.fetchF = dispatch_queue_create(label, NULL);
dispatch_async(self.fetchF, ^{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate getManagedObjectContext];
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
dispatch_async(dispatch_get_main_queue(), ^{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});

You are accessing the managedObjectContext from a different thread from where it was created. This is Core Data Threading Rule #1.
You are getting the MOC from the app delegate. If it's the normal Xcode-generated MOC, then it is created with thread-confinement concurrency. You can't even call performBlock with it. You can only access that MOC from the main thread. Period. Anything else is playing with fire, at best.
If you want to do all the work in a separate thread, you need a separate MOC as well. Like this (just typed - not compiled)...
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = appDelegate.managedObjectContext;
[moc performBlock:^{
// Go get your remote data and whatever you want to do
// Calling save on this MOC will push the data up into the "main" MOC
// (though it is now in the main MOC it has not been saved to the store).
[moc save:&error];
}];
Which would translate into something like this...
- (void)fetchFeedDataIntoDocument
{
NSString * labelString = [NSString stringWithFormat:#"Feed Fetcher %#", self.pageTitle];
const char *label = [labelString UTF8String];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate getManagedObjectContext];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext = mainContext;
[context performBlock:^{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
DO you really want to continue on error?
dispatch_async(dispatch_get_main_queue(), ^{
// Data has been pushed into main context from the background
// but it still needs to be saved to store...
// Do not forget to perform error handling...
NSError *error = nil;
[mainContext save:&error];
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
EDIT
The code generated by Xcode for creating the MOC uses init, which uses NSConfinementConcurrencyType. You can replace it with MainConcurrency, without any problems, but get several benefits.
In your app delegate file, replace...
__managedObjectContext = [[NSManagedObjectContext alloc] init];
with this...
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
Now, your main MOC can be "parented" and you can also call performBlock on it as well.

what about doing this...
-(void)functionToCallFetch {
[self performSelectorInBackground:#selector(fetchFeedDataIntoDocument) withObject:nil];
}
- (void)fetchFeedDataIntoDocument
{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate getManagedObjectContext];
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
//dispatch_async(dispatch_get_main_queue(), ^{
// [self setupFetchedResultsController];
// [self.tableView reloadData];
// [self downloadImagesForFeeds:feeds];
//});
[self performSelectorOnMainThread:#selector(setupFetchedResultsController) withObject:nil waitUntilDone:NO];
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
[self performSelectorOnMainThread:#selector(downloadImagesForFeeds:) withObject:feeds waitUntilDone:NO];
}
Maybe that would work better?

Change following code..
dispatch_queue_t queue1 = dispatch_queue_create("com.MyApp.AppTask",NULL);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue1,
^{
dispatch_async(main,
^{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
Hope, this will help you

Did you try building a method like :
- (void)methodToBePerformedOnMainThread{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
}
and call it with
[self performSelectorOnMainThread:#selector(methodToBePerformedOnMainThread) withObject:nil waitUntilDone:NO];
at the end of fetchFeedDataIntoDocument
Edit :
Did you try to wrap it with NSOperationQueue in place of dispatch_queue ?
Like :
NSOperationQueue *operationQueue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(fetchFeedDataIntoDocument) object:nil];
if(operation != nil){
[operationQueue addOperation:operation];

Related

Core Data : inserting Objects crashed in global queue [ARC - iPhone simulator 6.1]

I have a very simple Core Data demo, in which there is only one button.
When I click the 'run' button, the App creates 10,000 objects in a for-loop, which is running in the global queue.
Update for more detail : If I put the for-loop in main thread, it runs well.
Update for my intent : I know that MOC is not thread-safe, but according to the Apple doc, we can also use serial queue to access the MOC, and the serial queue uses more than one threads.
Here I create the Core Data stack:
#pragma mark - Core Data Stack
- (NSManagedObjectContext *)managedObjectContext
{
if (nil != _managedObjectContext) {
return _managedObjectContext;
}
_managedObjectContext = [[NSManagedObjectContext alloc] init];
if (self.persistentStoreCoordinator) {
[_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return _managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel
{
if (nil != _managedObjectModel) {
return _managedObjectModel;
}
_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (nil != _persistentStoreCoordinator) {
return _persistentStoreCoordinator;
}
NSString *storeType = NSSQLiteStoreType;
NSString *storeName = #"model.sqlite";
NSURL *storeURL = [NSURL fileURLWithPath:[[self applicationDocumentsDirectory] stringByAppendingPathComponent:storeName]];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
NSError *error = nil;
if (![_persistentStoreCoordinator addPersistentStoreWithType:storeType
configuration:nil
URL:storeURL
options:nil
error:&error])
{
NSLog(#"Error : %#\n", [error localizedDescription]);
NSAssert1(YES, #"Failed to create store %# with NSSQLiteStoreType", [storeURL path]);
}
return _persistentStoreCoordinator;
}
#pragma mark -
#pragma mark Application's Documents Directory
- (NSString *)applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
after app has launched :
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
if (self.managedObjectContext) {
;
}
return YES;
}
When I click the button :
- (IBAction)runButtonDidClick:(id)sender
{
/**
* Access the moc using different threads to make deadlock.
*/
[self runSave];
}
- (void)runSave
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = appDelegate.managedObjectContext;
if (moc) {
for (int j = 0; j < 10000; ++j) {
People *people = [NSEntityDescription insertNewObjectForEntityForName:#"People" inManagedObjectContext:moc];
people.name = #"noname";
}
NSLog(#"**********IN SAVE %#", [NSThread currentThread]);
NSError *error = nil;
if ([moc save:&error]) {
;
}
NSLog(#"**********OUT SAVE %#", [NSThread currentThread]);
}
});
}
For clicking the run button some times, maybe 2 or 3 or 4... It crashes
I could not figure out why...
Thanks for any help.
Core data should be always work on thread witch have moc.
the only job for performBlock and performBlockAndWait is that take care of thread safety. With it inserting to Core Data will always running in the right thread. You can define moc on whatever thread you want - performBlock always choose the right one.
So:
[self.managedObjectContext performBlock:^{
for(NSDictionary *dic in arr) {
//inserting here!
}
}];
In your case:
- (void)runSave
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = appDelegate.managedObjectContext;
if (moc) {
[moc performBlock:^{
for (int j = 0; j < 10000; ++j) {
People *people = [NSEntityDescription insertNewObjectForEntityForName:#"People" inManagedObjectContext:moc];
people.name = #"noname";
}
NSError *error = nil;
if ([moc save:&error]) {
;
}
}];
}
});
}

Multiple sharedInstance called failed

In my application i have called sharedinstance multiple time in multiple method deffinition ,
Here my code,
Method 1
-(void) showActionSheet:(id)sender forEvent:(UIEvent*)event
{
if(isQuantity==YES)
{
[[WebService sharedInstance] getQuantity:^(BOOL result)
{
if(result)
{
NSLog(#"success");
NSManagedObjectContext *context = [[DataAccessLayer sharedInstance] managedObjectContext];
Quantity = [context fetchObjectsForEntityName:NSStringFromClass([GetQuantity class]) withSortColumn:nil withSortDescending:TRUE withPredicate:nil];
NSLog(#"array ->%#",Quantity);
isQuantity=NO;
}
}];
}
popoverController1 = [[TSPopoverController alloc]initWithContentViewController:tableViewController1];
popoverController1.cornerRadius = 5;
popoverController1.titleText = #"Quantity";
popoverController1.popoverBaseColor = [UIColor blackColor];
popoverController1.popoverGradient= NO;
[popoverController1 showPopoverWithTouch:event];
}
Method 2
-(void) showActionSheetw:(id)sender forEvent:(UIEvent*)events
{
if(isSize==YES)
{
[[WebService sharedInstance] getDimension:^(BOOL result)
{
if(result){
NSLog(#"success");
NSManagedObjectContext *context = [[DataAccessLayer sharedInstance] managedObjectContext];
dime = [context fetchObjectsForEntityName:NSStringFromClass([Getdimension class]) withSortColumn:nil withSortDescending:FALSE withPredicate:nil];
NSLog(#"array ->%#",dime);
}
}];
}
popoverController2 = [[TSPopoverController alloc] initWithContentViewController:tableViewController2];
popoverController2.cornerRadius = 5;
popoverController2.titleText = #"Size";
popoverController2.popoverBaseColor = [UIColor blackColor];
popoverController2.popoverGradient= NO;
// popoverController.arrowPosition = TSPopoverArrowPositionHorizontal;
[popoverController2 showPopoverWithTouch:events];
}
EDIT
- (void) getDimension:(void (^)(BOOL))handler
{
JBContainedURLConnection *connection = [[JBContainedURLConnection alloc]init ];
[connection initWithGETUrl:IP methodName:GETDIMENSION param:nil andCompletionHandler:^(JBContainedURLConnection *connection, NSError *error, NSString *urlString, NSDictionary *userInfo, NSData *response)
{
if(error)
{
NSLog(#"Error: %#", error);
handler(FALSE);
}
else
{
if(response == nil)
handler(FALSE);
else
{
NSManagedObjectContext *context = [[DataAccessLayer sharedInstance] managedObjectContext];
NSArray *existingResults = [context fetchObjectsForEntityName:NSStringFromClass([Getdimension class]) withSortColumn:nil withSortDescending:FALSE withPredicate:nil];
for (NSManagedObject *obj in existingResults)
[context deleteObject:obj];
[[DataAccessLayer sharedInstance] saveContext];
id responseData = [self DictionaryFromResponse:response];
if(responseData == nil)
handler(FALSE);
else
{
NSLog(#"Dimension Response: %#", [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding]);
NSArray *data=[responseData objectForKey:#"GetDimensionResult"];
NSLog(#"GetDimensionResult :%#",data);
for( NSDictionary *dict in data){
Getdimension *userDetails = [Getdimension newObject];
[userDetails fillFromDictionary:dict];
}
[[DataAccessLayer sharedInstance] saveContext];
handler(TRUE);
}
} }
}];
}
- (void) getQuantity:(void (^)(BOOL))handler
{
JBContainedURLConnection *connection = [[JBContainedURLConnection alloc]init ];
[connection initWithGETUrl:IP methodName:GETQUANTITY param:nil andCompletionHandler:^(JBContainedURLConnection *connection, NSError *error, NSString *urlString, NSDictionary *userInfo, NSData *response)
{
if(error)
{
NSLog(#"Error: %#", error);
handler(FALSE);
}
else
{
if(response == nil)
handler(FALSE);
else
{
NSManagedObjectContext *context = [[DataAccessLayer sharedInstance] managedObjectContext];
NSArray *existingResults = [context fetchObjectsForEntityName:NSStringFromClass([GetQuantity class]) withSortColumn:nil withSortDescending:FALSE withPredicate:nil];
for (NSManagedObject *obj in existingResults)
[context deleteObject:obj];
[[DataAccessLayer sharedInstance] saveContext];
id responseData = [self DictionaryFromResponse:response];
if(responseData == nil)
handler(FALSE);
else
{
NSLog(#"GetQuantityResult Response: %#", [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding]);
NSArray *data=[responseData objectForKey:#"GetQuantityResult"];
// NSLog(#"GetDimensionResult :%#",data);
for( NSDictionary *dict in data){
GetQuantity *userDetails = [GetQuantity newObject];
[userDetails fillFromDictionary:dict];
}
[[DataAccessLayer sharedInstance] saveContext];
handler(TRUE);
}
} }
}];
}
Instance method
+ (id)sharedInstance
{
#synchronized(self)
{
if (manager == nil)
manager = [[self alloc] init];
}
return manager;
}
-(id)init
{
if(self = [super init])
{
}
return self;
}
-(NSString *)NSStringFromDictionaryUsingJSON:(id)dictionary
{
SBJsonWriter *writer = [[SBJsonWriter alloc]init];
return [writer stringWithObject:dictionary];
}
-(id)DictionaryFromResponse:(NSData *)response
{
NSString *responseBody = [[NSString alloc] initWithData:response encoding:NSASCIIStringEncoding];
SBJsonParser *parser = [[SBJsonParser alloc]init];
return [parser objectWithString:responseBody error:nil];
}
sharedInstance only works one time,ie,. if i call any of the method first its worked,if calls other method second time app gets crashed.Can any one please help me to sort it out
I guess sharedInstance method is messy.
It should be
+ (id)sharedInstance
{
if (manager == nil)
manager = [[self alloc] init];
return manager;
}
Enjoy Programming !
Have you declared instance of your class as static,
Declare you class object as :
static ClassName *manager;
And the allocate the same object in your sharedInstance method.
The reason may be that the memory object was released but reference is still there in the memory.So when you execute the shared instance method it found a reference to !nil object and leads your application to crash.
This is singleton class feature of iOS(objective C)

bool _WebTryThreadLock(bool) crash with UITableView

I am trying to display tweets in UITableView.
Code :-
- (void)requestTimeline:(int)count
{
NSURL *url = [NSURL URLWithString:#"http://api.twitter.com/1/statuses/user_timeline.json"];
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setObject:#"abc" forKey:#"screen_name"];
[parameters setObject:#"20" forKey:#"count"];
[parameters setObject:#"1" forKey:#"include_entities"];
TWRequest *request = [[TWRequest alloc] initWithURL:url parameters:parameters requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:^(NSData *responseData1, NSHTTPURLResponse *urlResponse, NSError *error)
{
if (responseData1 != nil)
{
NSError *error = nil;
self.itemsToDisplay = [NSJSONSerialization JSONObjectWithData:responseData1 options:NSJSONReadingMutableLeaves error:&error];
if (self.itemsToDisplay != nil)
{
[self.tableView reloadData];
}
else
{
NSLog(#"Error serializing response data %# with user info %#.", error, error.userInfo);
}
}
else
{
NSLog(#"Error requesting timeline %# with user info %#.", error, error.userInfo);
}
}];
}
self is UITableViewController's subclass.
Due to block, twitter request will be performed on another thread not on main thread. Thats why it get crashed.
//TWRequest.h
// Issue the request. This block is not guaranteed to be called on any particular thread.
- (void)performRequestWithHandler:(TWRequestHandler)handler;
Crash reort:-
bool _WebTryThreadLock(bool), 0xa8e4f50: Tried to obtain the web lock
from a thread other than the main thread or the web thread. This may
be a result of calling to UIKit from a secondary thread. Crashing
now...
How resolve this issue ?
Thanks...
the web view must only ever be used from the main thread. much of UIKit -- the tableView reloadData command is also not thread-safe
dispatch_async(dispatch_get_main_queue(), ^{
//all you ever do with UIKit.. in your case the reloadData call
[self.tableView reloadData];
});
Solved :
Also below code works :
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.tableView reloadData];
}];
try:
[self.tableView performSelectorInMainThread:#selector(reloadData)];
instead of:
[self.tableView reloadData];
Insert this, may be it helps:
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];

Why doesn't my program enter the viewDidLoad method?

I am trying to test this program on use of Core data. Once again, this is an example from the Dave Marks book. It has four text fields on a view and it loads it by using core data to connect to the database.
The app was created as a window based application and then I added a viewController to it. The file's owner is a sub class of the custom viewController class that I have created.
When I execute it, the UIView comes up with a blank view with no text boxes or labels that I had created in the view.
I put a break point in the main method, it does not even go anywhere from there when I click on step into method button. When I place a break point on the viewDidLoad method, it does not even get to it.
Lastly I do not get any errors on the console. What is going on?
Here is the viewController class:
#import "PersistenceViewController.h"
#import "CoreDataPersistenceAppDelegate.h"
#implementation PersistenceViewController
#synthesize line1;
#synthesize line2;
#synthesize line3;
#synthesize line4;
#pragma mark - View lifecycle
- (void)viewDidLoad
{
CoreDataPersistenceAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Line" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(#"There was an error");
}
for (NSManagedObject *oneObject in objects) {
NSNumber *lineNum = [oneObject valueForKey:#"lineNum"];
NSString *lineText = [oneObject valueForKey:#"lineText"];
NSString *fieldName = [NSString stringWithFormat:#"line%d", [lineNum integerValue]];
UITextField *theField = [self valueForKey:fieldName];
theField.text = lineText;
}
[request release];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:app];
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
-(void) applicationWillResignActive:(NSNotification *)notification {
CoreDataPersistenceAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSError *error;
for (int i=1; i<=4; i++) {
NSString *fieldName = [NSString stringWithFormat:#"line%d",i];
UITextField *theField = [self valueForKey:fieldName];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Line" inManagedObjectContext:context];
[request setEntity:entityDescription];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(lineNum = %d)", i];
[request setPredicate:pred];
NSManagedObject *theLine = nil;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(#"There was an error");
}
if ([objects count] > 0)
theLine = [objects objectAtIndex:0];
else
theLine = [NSEntityDescription insertNewObjectForEntityForName:#"Line" inManagedObjectContext:context];
[theLine setValue:[NSNumber numberWithInt:i] forKey:#"lineNum"];
[theLine setValue:theField.text forKey:#"lineText"];
[request release];
}
[context save:&error];
}
- (void)viewDidUnload
{
self.line1 = nil;
self.line2 = nil;
self.line3 = nil;
self.line4 = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc
{
[line1 release];
[line2 release];
[line1 release];
[line1 release];
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#end
Be sure to change the viewcontroller that gets intialized in the appdelegate with the correct nib. The blank UIView is probably the view created when you started the project.
In your AppDelegate there is a method called applicationdidFinishLaunchingWithOptions, in which something like the following code is located:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[RIViewController alloc] initWithNibName:#"RIViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
In my case I load the RIViewController. By default the nib file has the same name as the controller and is hence named RIViewController aswell.

NSOperation and EXC_BAD_ACCESS

I have a few apps which are largely data driven, so most screens are basically composed of:
Open the screen
Download the data via an NSOperation
Display data in a UITableView
Make a selection from the UITableView
Go to new screen, and start over from step 1
I am finding that everything works in normal usage, but if the user goes away from the app for a while and then comes back, I'm getting an EXC_BAD_ACCESS error when the next NSOperation runs. This doesn't seem to matter if the user sends the app into the background or not, and it only seems to occur if there's been at least a few mins since the previous data connection was made.
I realise this must be some form of over-releasing, but I'm pretty good with my memory management and I can't see anything wrong. My data calls generally look like this:
-(void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue* tmpQueue = [[NSOperationQueue alloc] init];
self.queue = tmpQueue;
[tmpQueue release];
}
-(void)loadHistory {
GetHistoryOperation* operation = [[GetHistoryOperation alloc] init];
[operation addObserver:self forKeyPath:#"isFinished" options:0 context:NULL];
[self.queue addOperation:operation];
[operation release];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqual:#"isFinished"] && [object isKindOfClass:[GetHistoryOperation class]]) {
GetHistoryOperation* operation = (GetHistoryOperation*)object;
if(operation.success) {
[self performSelectorOnMainThread:#selector(loadHistorySuceeded:) withObject:operation waitUntilDone:YES];
} else {
[self performSelectorOnMainThread:#selector(loadHistoryFailed:) withObject:operation waitUntilDone:YES];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
-(void)loadHistorySuceeded:(GetHistoryOperation*)operation {
if([operation.historyItems count] > 0) {
//display data here
} else {
//display no data alert
}
}
-(void)loadHistoryFailed:(GetHistoryOperation*)operation {
//show failure alert
}
And my operations generally looks something like this:
-(void)main {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSError* error = nil;
NSString* postData = [self postData];
NSDictionary *dictionary = [RequestHelper performPostRequest:kGetUserWalkHistoryUrl:postData:&error];
if(dictionary) {
NSNumber* isValid = [dictionary objectForKey:#"IsValid"];
if([isValid boolValue]) {
NSMutableArray* tmpDays = [[NSMutableArray alloc] init];
NSMutableDictionary* tmpWalksDictionary = [[NSMutableDictionary alloc] init];
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyyMMdd"];
NSArray* walksArray = [dictionary objectForKey:#"WalkHistories"];
for(NSDictionary* walkDictionary in walksArray) {
Walk* walk = [[Walk alloc] init];
walk.name = [walkDictionary objectForKey:#"WalkName"];
NSNumber* seconds = [walkDictionary objectForKey:#"TimeTaken"];
walk.seconds = [seconds longLongValue];
NSString* dateStart = [walkDictionary objectForKey:#"DateStart"];
NSString* dateEnd = [walkDictionary objectForKey:#"DateEnd"];
walk.startDate = [JSONHelper convertJSONDate:dateStart];
walk.endDate = [JSONHelper convertJSONDate:dateEnd];
NSString* dayKey = [dateFormatter stringFromDate:walk.startDate];
NSMutableArray* dayWalks = [tmpWalksDictionary objectForKey:dayKey];
if(!dayWalks) {
[tmpDays addObject:dayKey];
NSMutableArray* dayArray = [[NSMutableArray alloc] init];
[tmpWalksDictionary setObject:dayArray forKey:dayKey];
[dayArray release];
dayWalks = [tmpWalksDictionary objectForKey:dayKey];
}
[dayWalks addObject:walk];
[walk release];
}
for(NSString* dayKey in tmpDays) {
NSMutableArray* dayArray = [tmpWalksDictionary objectForKey:dayKey];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"startDate" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray* sortedDayArray = [dayArray sortedArrayUsingDescriptors:sortDescriptors];
[sortDescriptor release];
[tmpWalksDictionary setObject:sortedDayArray forKey:dayKey];
}
NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:NO selector:#selector(localizedCompare:)];
self.days = [tmpDays sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
self.walks = [NSDictionary dictionaryWithDictionary:tmpWalksDictionary];
[tmpDays release];
[tmpWalksDictionary release];
[dateFormatter release];
self.success = YES;
} else {
self.success = NO;
self.errorString = [dictionary objectForKey:#"Error"];
}
if([dictionary objectForKey:#"Key"]) {
self.key = [dictionary objectForKey:#"Key"];
}
} else {
self.errorString = [error localizedDescription];
if(!self.errorString) {
self.errorString = #"Unknown Error";
}
self.success = NO;
}
[pool release];
}
-(NSString*)postData {
NSMutableString* postData = [[[NSMutableString alloc] init] autorelease];
[postData appendFormat:#"%#=%#", #"LoginKey", self.key];
return [NSString stringWithString:postData];
}
----
#implementation RequestHelper
+(NSDictionary*)performPostRequest:(NSString*)urlString:(NSString*)postData:(NSError**)error {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#", kHostName, urlString]];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
[urlRequest setHTTPMethod:#"POST"];
if(postData && ![postData isEqualToString:#""]) {
NSString *postLength = [NSString stringWithFormat:#"%d", [postData length]];
[urlRequest setHTTPBody:[postData dataUsingEncoding:NSASCIIStringEncoding]];
[urlRequest setValue:postLength forHTTPHeaderField:#"Content-Length"];
[urlRequest setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
}
NSURLResponse *response = nil;
error = nil;
NSData *jsonData = [NSURLConnection sendSynchronousRequest:(NSURLRequest *)urlRequest returningResponse:(NSURLResponse **)&response error:(NSError **)&error];
NSString *jsonString = [[NSString alloc] initWithBytes: [jsonData bytes] length:[jsonData length] encoding:NSUTF8StringEncoding];
NSLog(#"JSON: %#",jsonString);
//parse JSON
NSDictionary *dictionary = nil;
if([jsonData length] > 0) {
dictionary = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:error];
}
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
return dictionary;
}
If I have the autorelease pool in place, the crash occurs on [pool release]. If I don't, then the crash just looks to appear in the main.m method, and I don't seem to get any useful information. It's difficult to track down when I have to wait 10 mins in between every test!
If anyone can offer any clues or directions to go, that'd be much appreciated.
It's almost certain you're overreleasing something in your code, seeing that the crash is occurring during a [pool release] (There's a autorelease pool in the main method as well).
You can find it using Xcode - use build and analyze to have the static analyser pinpoint potential problems. Run it and post the results.
try this:
http://cocoadev.com/index.pl?NSZombieEnabled
also, you should avoid:
1) calling UIKit methods from secondary threads
2) making (synchronous) url requests from the main thread.
you must be doing one in any case in RequestHelper's performPostRequest method.
My guess is this section
GetHistoryOperation* operation = (GetHistoryOperation*)object;
if(operation.success) {
[self performSelectorOnMainThread:#selector(loadHistorySuceeded:) withObject:operation waitUntilDone:YES];
} else {
[self performSelectorOnMainThread:#selector(loadHistoryFailed:) withObject:operation waitUntilDone:YES];
}
If the sleep happens at a bad point here, you have an object being passed to another thread. I'd find a way around having to pass the operation as the object.
This is a really old question, so sorry for the dredge, but there is no accepted answer.
I was also getting a EXC_BAD_ACCESS on NSOperationQueue -addOperation for seemingly no reason, and after a few days of hunting down memory leaks, and turning on all the debugger options i could find (malloc guard, zombies) and getting nothing, I found an NSLog warning that said: "[NSoperation subclass] set to IsFinished before being started by the queue."
When I modified my base operation subclass, so that its -cancel function only set (IsRunning = NO) and (IsFinished = YES) IF AND ONLY IF (IsRunning == YES), NSOperationQueue stopped crashing.
So if you're ever calling NSOperationQueue -cancelAllOperations, or you're doing that manually (i.e. for (NSOperation *op in queue.allOperations) ) double check to make sure that you don't set IsFinished on those operations in your subclass implementation.