PDA

View Full Version : Problems with NSOperationQueue




John Baughman
Nov 12, 2009, 02:28 PM
After getting my Stanford course Presence 2 assignment running smoothly, I am tackling Presence 3 which adds threading. My presence app first displays a list of twitter users in a navigation controller. Tapping an accessary button on a user pushes a detail form that contains a tableView that displays all of the user's status messages.

I have successfully converted my PersonListViewController to load the list of users using a NSOperationQueue. Now I want to us the same technique to load the status messages on the detail form when PersonDetailViewController gets pushed onto the stack.

The user's are contained in a dictionary of user dictionaries, and the detail form only needs the user that was tapped. So in my PersonListViewController's accessoryButtonTappedForRowWithIndexPath method I am passing that user dictionary to the pushed PersonDetailViewController...


- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath{

self.navigationController.navigationBarHidden = NO;

PersonDetailViewController *personDetailViewController = [[PersonDetailViewController alloc] initWithNibName:@"PersonDetailViewController" bundle:nil];
[self.navigationController pushViewController:personDetailViewController animated:YES];

PersonTableCell *cell = (PersonTableCell *) [tableView cellForRowAtIndexPath:indexPath];
personDetailViewController.person =[tweetsData objectAtIndex:indexPath.row];
//tweetsData is the dictionary of user dictionaries and person is the user dictionary on the detail view

[personDetailViewController release];
[cell release];
}


I am starting the NSOperationQueue in the numberOfRowsInSection of the detail view as that is the only place I could find that I was insured that the person dictionary had been set properly.


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

if(dict){ //dict is the status message dictionary and is being set in the NSOperationQueue
return dict.count/2;
}else {
[self beginLoadingStatus];
return 0;
}
}


I have the NSOperationQueue methods set up just like I did in the list view that works fine.


- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
operationsQueue2 = [[NSOperationQueue alloc] init];
[operationsQueue2 setMaxConcurrentOperationCount:1];
}
return self;
}

- (void)beginLoadingStatus{
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadStatus) object:nil];
[operationsQueue2 addOperation:operation];
[operation release];
}

- (void)loadStatus{
NSDictionary *dictTemp = [person statusDict]; // [[NSDictionary alloc] initWithDictionary:person.statusDict];
NSLog(@"LoadStatus: %@", dictTemp);
[self performSelectorOnMainThread:@selector(didFinishLoadingStatusWithResults:) withObject:dictTemp waitUntilDone:NO];
[dictTemp release];
dictTemp = nil;
}

- (void)didFinishLoadingStatusWithResults:(id)results{
dict = [[NSDictionary alloc] initWithDictionary:results];
[detailTableView reloadData];
}


It runs through the first pass with zero rows fine and displays the view. I know that the run after the operation completes is running because I have traced the numberOfRowsInSection section and can see it returning 19 rows for the user I am testing. I also am adjusting the row height in heightForRowAtIndexPath and watched it correctly fetch each status message to determine the row height.


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGFloat height;
if(dict){
NSString *text = [dict valueForKey:[NSString stringWithFormat:@"text%i", indexPath.row]];
UIFont *font = [UIFont fontWithName:@"Helvetica" size:13.0];
CGSize withinSize = CGSizeMake(280, 1000);
CGSize size = [text sizeWithFont:font constrainedToSize:withinSize lineBreakMode:UILineBreakModeWordWrap];

if(size.height > 49){
height = 81 + (size.height - 49);
}else {
height = 81;
}

}else {
height = 81; //this should never happen as if there is no dict then number of rows will be zero
}

return height;
}


After heightForRowAtIndexPath is called for the last (19th) time, the app crashes with "EXC_BAD_ACCESS". cellForRowAtIndexPath does not get called. I am stumped as to how to debug this as I don't know where to look after the heightForRowAtIndexPath method is called.

What else happening between heighForRowAtIndexPath and cellForRowAtIndexPath that could be causing the crash? cellForRowAtIndexPath is being called for the first time after the operation completes because the first time through I am setting the row count to zero.

Is there something else with my code that might be causing the problem? Build and Analyze does not report any problems.

Do I need to do something with the NSOperationQueue I created in my PersonListViewcontroller?

The only thing I am doing different from the operation on the list view is starting the operation from the numberOfRowsInSection method instead of the viewWillAppear method which I cannot do because the person dictionary has not been set yet when this method is called. Same holds for the viewDidLoad method.

Thanks for any help,

John



John Baughman
Nov 14, 2009, 01:36 PM
I had to do an alloc/init when I created person's status dictionary



- (void)loadStatus{
//NSDictionary *dictTemp = psrson.statusDict; //this does not work
NSDictionary *dictTemp = [[NSDictionary alloc] initWithDictionary:[person statusDict]]; //this fixed it
NSLog(@"LoadStatus: %@", dictTemp);
[self performSelectorOnMainThread:@selector(didFinishLoadingStatusWithResults:) withObject:dictTemp waitUntilDone:NO];
[dictTemp release];
dictTemp = nil;
}

PhoneyDeveloper
Nov 14, 2009, 02:28 PM
John, there is a series of memory errors in this code. The one you fixed is only one of them. You need to understand better where release is required and where it is forbidden.

John Baughman
Nov 14, 2009, 03:45 PM
John, there is a series of memory errors in this code. The one you fixed is only one of them. You need to understand better where release is required and where it is forbidden.

To be honest I am not surprised. I am having a very difficult time wrapping my head around the issue of memory management. Since posting this thread I have, however, with the help of the new Build and Analyze option made some changes. Also I did not include my dealloc method in this thread.

In accessoryButtonTappedForRowWithIndexPath I removed [cell release].

In the dealloc method of the detail view I have..
[person release]
[operationsQueue2 release]
[dict release];

Can you please point out anything that I have missed. Thanks.

PhoneyDeveloper
Nov 14, 2009, 09:47 PM
Maybe you got em all. Clang is usually good at finding the kinds of errors that you were making.

John Baughman
Nov 14, 2009, 10:03 PM
Maybe you got em all. Clang is usually good at finding the kinds of errors that you were making.

Thanks. Clang works great, but better yet I am finding it to be an excellent learning tool.

John