Problems with NSOperationQueue

Discussion in 'iOS Programming' started by John Baughman, Nov 12, 2009.

  1. macrumors member

    Joined:
    Oct 27, 2003
    #1
    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...

    Code:
    - (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.

    Code:
    - (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.

    Code:
    - (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.

    Code:
    - (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
     
  2. thread starter macrumors member

    Joined:
    Oct 27, 2003
    #2
    Figured it out

    I had to do an alloc/init when I created person's status dictionary


    Code:
    - (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;
    }
     
  3. macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #3
    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.
     
  4. thread starter macrumors member

    Joined:
    Oct 27, 2003
    #4
    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.
     
  5. macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #5
    Maybe you got em all. Clang is usually good at finding the kinds of errors that you were making.
     
  6. thread starter macrumors member

    Joined:
    Oct 27, 2003
    #6
    Thanks. Clang works great, but better yet I am finding it to be an excellent learning tool.

    John
     

Share This Page