Question

I am developing iPhone app in which i am downloading image from server in background,

Here is view of my application,

when i click on Button 1 i am fetching 5 data from server also images, after fetching data when user scrolls up i am fetching new 5 data from server again when user scrolls up i am fetching new 5 data from server and so on.

while fetching data for Button 1 if i click on Button 2 am cancelling my previous thread of Button 1 and i am fetching new 5 data for Button 2 and on scrolling it again fetching new 5 data Same as Button 1

but after some time while reloading a tableview my app gets crashes and shows: Terminating app due to uncaught exception 'NSRangeException', reason: ' -[__NSArrayM objectAtIndex:]: index 28 beyond bounds [0 .. 4]

Here is my code snippet:

- (void)viewDidLoad
{
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        [self fetchDataForButton1]; 

        dispatch_async(dispatch_get_main_queue(), ^(void) {


                [tableView reloadData];


        });
    });

}


- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
 if(it is last row then fetch new 5 data)
 {
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

                        //Code runs on background thread
                        [self LoadMoreData];

                        dispatch_async(dispatch_get_main_queue(), ^(void) {

                            //Code here is run on the main thread
                            [_tblList reloadData];

                        });
                    });


      }else{
              dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

                                [self beginBackgroundFetchTask];

                               [self downloadImage_3:indexPath];

                                [self endBackgroundFetchTask];
                            });
      } 

}


-(void)downloadImage_3:(NSIndexPath *)path{


        UIImage *img = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:ImagePath]]];

        if (img) {
            [dicImages_msg setObject:img forKey:[[msg_array objectAtIndex:path.row] valueForKey:@"Merchant_SmallImage"]];
        }

        [_tblList performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];


}

- (void) beginBackgroundFetchTask
{
   self.backgroundFetchTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [self endBackgroundFetchTask];
    }];
}

- (void) endBackgroundFetchTask
{
    [[UIApplication sharedApplication] endBackgroundTask: self.backgroundFetchTask];
    self.backgroundFetchTask = UIBackgroundTaskInvalid;
    NSLog(@"ended BackgroundFetchTask");
}

-(void)LoadMoreData
{
   //Fetches new 5 data from server...
}
Was it helpful?

Solution

I think the problem maybe at [self downloadImage_3:indexPath]; you call dispatch_async when you at Button1, and the dispatch_async block have not invoked, then you click on Button2, then I guess msg_array is cleared and filled with new 5 object, after that, dispatch_async block is invoked, the path.row for block is 28, whereas, the msg_array has new array content with 5 new objects, then crash.

You should cancel dispatch_async before click on other button, which is impossible for dispatch_async , so you can have a judgement in block:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    [self beginBackgroundFetchTask];

    [self downloadImage_3:indexPath buttonIndex:index];

    [self endBackgroundFetchTask];
});

-(void)downloadImage_3:(NSIndexPath *)path buttonIndex:(int)buttonIndex{
     if(self.currentSelectIndex != buttonIndex) return; //skip reloadData if not same index


        UIImage *img = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:ImagePath]]];

        if (img) {
            [dicImages_msg setObject:img forKey:[[msg_array objectAtIndex:path.row] valueForKey:@"Merchant_SmallImage"]];
        }

        [_tblList performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];

}

OTHER TIPS

Try this out

1) Use SDWebImage for fetching Images from Server

2) Use Pull Refresher feature for fetching next 5 data.

This will helps to fetch data in background.

Use SDWebImage for fetching Images from Server.

Disable user interaction then go to top on table:

 [TableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];

then call:

 [TableView reloadData];

then enable userintercation. hope will solve the problem

This is because images take time to load from server and cell gets no data when it is allocated.

You need to implement lazy loading for it.

Here is a good tutorial.

I would not fetch data like that from the cellForRowAtIndexPath method. That is used to display cells.

Instead do it like this:

  1. List your original data.
  2. When the user scrolls past the last cell, show a footer that displays a loading symbol.
  3. at the same time While the footer is displaying a loading symbol, then and ONLY then call the fetchLoadData method, and WAIT
  4. Until you dont hear from the server, continue displaying the loading symbol, dont start adding new cells to prepare for anything, because chances are you might either not get anymore data back, which at this point you have to get rid of the footer cell and let the user know that there is no more data to retrieve, OR if there is more data to display, THEN set up your next 5 cells by doing the following in this order:
    • adding your new 5 data objects to your data array
    • updating the number of rows in section using the numberOfRowsInSection delegate method
    • You will find that your cells if coded correctly will be displayed correctly once you refresh the table using [myTable reloadTable];

AS per your log array is having only 5 count and you are fetching the data as per indexpath.row and in this crash case is 28. So it might be that you are adjusting your cell indexpath row according to your new array which is fetching again and again data on scrolling but in the repetition of 5.

Just put a breakpoint and check whether you are resetting the indexpath row in count of [0 4];

UPDATED:

[[NSThread currentThread]  populateDataInBackground];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top