I'm running into index beyond bounds exception in one of my UITableViews and I think it could be down to some multithreading issues. Here's what I believe is happening:
I have a UITableView and it's data source is a regular NSMutableArray.
This NSMutableArray which is backing my UITableView is updated every couple of seconds with the contents of an API response.
After each update, UITableView's reloadData is being invoked to ensure that the user sees new data from the API server.
Sometimes a index beyonds bounds exception gets thrown.
Here's my code:
-(NSMutableArray*) currentBetEvents
{
return currentMarketId == nil ? [[BFOpenBetsModel sharedInstance] betEvents] : filteredBetEvents;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSArray *betEvents = [self currentBetEvents];
return [betEvents count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *betEvents = [self currentBetEvents];
id obj = [betEvents objectAtIndex:indexPath.section] // this is where it blows up
Basically, I get an exception while trying to access an object in the betEvents structure at index 0.
What I believe is happening is:
reloadData is called on the UITableView
numberOfSectionsInTableView: is invoked which returns a value > 0.
a rouge thread arrives and clears out the UITableView's data source.
cellForRowAtIndexPath: is invoked and it bombs.
Is there any way to ensure that this doesn't happen? Do I need to start using some primitive locks on the data source to ensure that it doesn't get updated while the table is being updated?
EDIT
Took another look at how the data structures returned by currentBetEvents can be altered and it looks like the filteredBets & betEvents can be cleared out as a result of the following code:
[[NSNotificationCenter defaultCenter] postNotificationName:kUserLoggedOutNotification object:nil];
This notification is posted whenever the user logs out. Whenever a user logs out of the app, I need to clear out the filteredBets and betEvents arrays. Is it possible that the following could happen:
reloadData is called on the UITableView
numberOfSectionsInTableView: is invoked which returns a value > 0.
User logs out which kicks off the notification & clears out the data structures.
cellForRowAtIndexPath: is invoked and it bombs.
Thanks,
Sean
Definitely sounds like a threading problem. You might try something like this:
// view controller
#synchronized([[BFOpenBetsModel sharedInstance] betEvents])
{
[self.tableView reloadData];
}
…
// data model
#synchronized(_betEvents) // or whatever the instance variable -betEvents returns is
{
[_betEvents addObject:whatever];
}
Related
Hi guys I have an UITableView that loads precomputed cells from NSMutableArray. I want to use NSOperationQueue or PerformSelectorOnMainThread to update the user interface to enable smooth scrolling but i get an error... this is my code...
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//queue is being initialized in viewDidLoad
[queue addOperationWithBlock:^ {
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
NSLog(#"Updating...");
return [self.CellObjects objectAtIndex:indexPath.row];
//if you remove the line above with the return, NSOperationQueue will work but I need the above line to load the cell.
}];
}];
}
Is there a way to make it work? Any help appreciated!
Why not simple ...
return [self.CellObjects objectAtIndex:indexPath.row];
...?
It's a mess. Why do you have two calls to addOperationWithBlock:? And also your return statement has nothing to do with return value of tableView:cellForRowAtIndexPath:. It's return value of your block, so, it will never work.
What's your error? I assume it's about incompatible block pointer, because it expects void(^)(void) and you're trying to send UITableViewCell *(^)(void).
Blocks are not gonna help you there. If you have precomputed cells in CellObjects, just use only the return self.CellObjects[indexPath.row]; line.
Also don't use property names like CellObjects. Should be named cellObjects. Check the case.
Offsetting your cell retrieval in that way is not going to give you any advantage. You need to compute your cell height/size/content prior to your UITableView instance asking for cells.
UITableView is expecting a UITableViewCell to be returned from that delegate callback on the
main thread.
A better idea would be to place the computation on other threads if they require time and their on completion you can call back to your UITableView to reloadData.
I have an NSTableView whose data source is an NSMutableArray. The count of the array is returned in the numberOfRowsInTableView: method.
The table is refreshed by make a selection in other components such as an NSPopupButton, which changes the content of the array and performs the reloadData method.
The problem is that sometimes I am getting kind of data being written all over each other. So for example if the count is 4 and then after an action is performed it is 10, the additional 6 records are written below the first 4. It is only after I manually scroll the table up and down that the data (correct data) is displayed.
The console displays a message that
-[NSCFArray objectAtIndex:]: index(-1(or possibly larger)) beyond bounds (1056).
I understand that the error message seems to point out that it is an array beyond bounds, however how can that be when I am only returning, say [array objectAtIndex: rowIndex] in the
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
method?
Also, the count return is returned in the numberOfRowsInTableView is always being correct (I know this since I am logging the count using a timer every few seconds).
Any ideas? Thanks!
Edit 1:
I am returning the data as follows:
pc = [[PrinterClass alloc]init];
pc = [ads_printers_array objectAtIndex:rowIndex];
if (aTableColumn == tc_make)
return [pc make];
if (aTableColumn == tc_model)
return [pc model];
if (aTableColumn == tc_driver_name)
return [pc ppd];
if (aTableColumn == tc_rdp_driver)
{
if ([pc decider] == 1)
return [pc rdp];
else return [pc model];
}
The array that I mention above is filled with object of a class that I created called PrintersClass.
To reload the data, it's just reload data as usual:
[ads_rdp_driver_table reloadData];
With ads_rdp_driver_table being the tableView name.
The thing is that this problem is coming up at random intervals at not at specific points. That is why I can't put my finger on it..
Are you 100% sure you have connected the delegate and datasource ?
You can also do this in code
ads_rdp_driver_table.datasource = self;
ads_rdp_driver_table.delegate = self;
add this is in f.ex. awake from nib.
You also have to connect the tableview itself to an iboutlet, otherwise you can not call it by name.
I wrote a Refresh function to reload the tableview in iPhone app with the argument of UITableview.
My question is how can I call or invoke this refresh function?
- (void)RefreshTableView:(UITableView *)tableView
{
if(tableView != nil){
[tableView reloadData];
}
}
First of all rename your method like the following:
- (void)refreshTableView:(UITableView *)tableView
Then, to call that method you need to perform the following:
[self refreshTableView:yourTableview];
where self stands for the object that will receive the message. In this case self is an instance of the object that contains that method.
Now, why do you need to pass also an instance of the table view?
if you have a instance variable for that table view and you have synthesized it (#property/#synthesize pattern), you could simple do the following:
- (void)refreshTableView
{
[[self myTable] reloadData];
}
and then invoke that method like the following:
[self refreshTableView];
Edit
As danh suggested, if you have a property (or an instance variable), you can also call directly
[[self myTable] reloadData]; // or [self.myTable reloadData];
without passing through refreshTableView.
If - (void)RefreshTableView:(UITableView *)tableView is in the same file then you can call it
[self RefreshTableView:aTableView]; when you want to call.
from the class itself [self RefreshTableView:yourTableView];
form outside of the class [yourClassInstance RefreshTableView:yourTableView];
P.S:
You should refactor your code and rename RefreshTableView: with refreshTableView:
I must use s7graphview library for draw simple histogram, and I've got custom function called
-(IBAction)histogram:(id)sender;. in this function every pixel from image is passed to array as RGB representation. then pixels are counted and I've got red, green and blue array. I can send to NSLog or something but problem is, when I try to send 3 arrays to - (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex;. both functions are in the same .m file, and I have no idea how to pass data between them, because when I write redArray, Xcode don't suggest me this name.
Since - (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex is a delegate method, it should be implemented in your class that is posing as a delegate to S7GraphView object. You don't call explicitly, you define it as such in your .m implementation:
- (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex
{
if ( plotIndex == <some index value> )
return redArray;
else
return nil;
}
I have no idea what plotIndex corresponds with your various color arrays, but you should get the idea.
When the S7GraphView object needs that data, it will invoke that delegate method.
This is not unlike implementing UITableViewDelegate and UITableViewDataSource methods. When a UITableView method -reloadData is invoked, it will call upon your view controller (presuming it is delegate/data source of the table) to supply UITableViewCell objects via
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = <... dequeue or created ... >.
/*
do some cell set up code based on indexPath.section and indexPath.row
*/
return cell;
}
Similar with S7GraphView I'm sure (I don't have the API to see all it does). In your IBAction method, you will probably be doing something like:
- (IBAction)histogram:(id)sender
{
// maybe you recalculate your red, green, and blue component arrays here and cache
// or maybe you calculate them when requested by the delegate method
// tell the S7GraphView it needs to update
// (not sure what the reload method is actually called)
[self.myS7GraphView reloadGraph];
}
I have a UITableViewCell with a method like this.
-(void) setupStore:(StoreModel *) store {
self.title.text = store.title; // crash here when scrolling
}
So that method is called from within a UIViewController class that contains the UITableView.
Something like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
[cell setupStore:[storesArray objectAtIndex:indexPath.row]];
...
}
That works when the table first loaded, but when I scroll the table, it crash with error EXC_BAD_ACCESS.
What could be causing that?
Please enlight.
Thanks,
Tee
Try to build your code with NSZombieEnabled = YES and report here what is happening. Give us the full error description.
http://cocoa-nut.de/?p=16
In general We will get EXC_BAD_ACCESS when we are trying to use a released object.
So you can check whether you are using any released object.
As you have mentioned that storesArray = [[[storesLocation alloc] init]retain]; there is no need to retain the object. Give a try by using this line
storesArray = [[storesLocation alloc] init];.
Also make sure that storemodel object exists by logging it in this method
-(void) setupStore:(StoreModel *) store
{
NSLog(#"store model %#",store);
}
You can go through the link
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html