Sorry I'm noobs on Cocoa, since I'm new on iOS mobile development.. straight to the point, I've use GCD method on Cocoa to assign data to tableview but when I fire [tableview reloadData] it's not working. Here my sample code:
-(void)updateCell{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSIndexPath* indexPath;
// Insert code to be executed on another thread here
if(clean_data){
for (int i=0; i<clean_data.count; i++) {
indexPath= [NSIndexPath indexPathForRow:i inSection:0];
sqCell *cell = (sqCell *)[stockQ cellForRowAtIndexPath:indexPath];
for (int j=0; j<plist.count; j++) {
NSString *a =[[clean_data objectAtIndex:i]objectAtIndex:1];
NSString *b =[[[plist objectAtIndex:j]objectForKey:#"data"]objectAtIndex:0];
if([a isEqualToString:b]){
cell.last.text =[[clean_data objectAtIndex:i]objectAtIndex:1];
cell.change.text = #"1231231312312";
}
}
}
}
dispatch_async(dispatch_get_main_queue(), ^{
// Insert code to be executed on the main thread here
///reload table here
[self reload];
});
});
}
here method to reload data
-(void)reload{
NSLog(#"reload");
[stockQ setNeedsLayout];
[stockQ setNeedsDisplay];
[stockQ reloadData];
}
the console show "reload" text, but not firing stockQ UITableCiew.. what happen in my code?
You don't do UI handling code in an async thread. Modifying the UI (like updating the tableView) - should always be done in the main thread. Try to pull that code from an async task and see if it works or not.
Another remark - why in any way do you need to reload the data in an async task? Table view is very lightweight & fast. All your code from the post should be ran within milliseconds. Use dispatch_async only for long running, time consuming tasks!
Related
I've come to a problem where proper threading is needed, but I can't seem to optimise it correctly.
Here's my method:
-(void) method1
{
// -1 to an NSInteger
nsint1--;
[self showActiviyIndicator:YES]; //act as loading screen
[alloc database etc stuffs and retrieving of data here]
//for loop here to check with database, and grey out button depending on database values
for (int i = 1; i<12; i ++)
{
//get values from database and store into variables, then grey out the button if variables are 0.
}
int Val1 = [get from database]
if Val1 = 0
[button setTitleColor:[UIColor Grey]];
someLabel.text = [NSString stringWithFormat:#"%ld", (long)nsint1];
//here's where the problem lies
[self refreshTableSessionList:xx];
[self showActiviyIndicator:NO]
}
inside [self refreshTableSessionList:xx], there's a
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
to get data from server database, then a
dispatch_async(dispatch_get_main_queue(),
to populate and reload tableViewCell.
But there'll be a conflict for when I put a
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
before [alloc database etc stuffs and retrieving of data here]
and put dispatch_async(dispatch_get_main_queue(),
when greying out the button, but that's inside a loop, which i don't think it is the right way.
What's the solution to overcome this?
As I understood you don't wait for the finish of the background database stuff.
Have you read about multithreading? For example, Ray's article.
In a simple way, you can call dispatch_async inside the dipatch_async block inside the dispatch_async and etc.
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do some database stuff
dispatch_async(dispatch_get_main_queue(), ^{
// do some UI stuff
});
});
So you should switch between the main thread and a global queue. Also, you can use delegates, notifications or even reactivity for such purposes.
I have table view that load new data (depend on page) from SQL data base. Problem is, when i load it in main thread, it block UI for a while. When i try to do "hard work" in background, and reload data in main thread, odd things start to happen, for example, table view section header move in wrong place, and i load enormous amount of data.
First case, all work but block UI for while:
[self.tableView addInfiniteScrollingWithActionHandler:^{
#strongify(self)
if (!self.viewModel.isUpdating){
self.viewModel.isUpdating = YES;
[self.tableView.infiniteScrollingView startAnimating];
[self.viewModel nextPage];
[self.tableView reloadData];
self.viewModel.isUpdating = NO;
}
}];
In second case, i tried to do background work, following not work as expected:
if (!self.viewModel.isUpdating){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Add some method process in global queue - normal for data processing
self.viewModel.isUpdating = YES;
[self.tableView.infiniteScrollingView startAnimating];
[self.viewModel nextPage];
dispatch_async(dispatch_get_main_queue(), ^(){
//Add method, task you want perform on mainQueue
//Control UIView, IBOutlet all here
[self.tableView reloadData];
self.viewModel.isUpdating = NO;
});
//Add some method process in global queue - normal for data processing
});
}
}];
How should i modify my code to not load main thread, and without "weird" things?
have you tried something like this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
.....
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelectorOnMainThread:#selector(updateView) withObject:nil waitUntilDone:YES];
});
});
......
-(void)updateView{
[self.tableView reloadData];
self.viewModel.isUpdating = NO;
}
user PerformSelectorOnMainThread it may help you.
I read that [_tableView reloadData] sends a message to the main queue to update and display the data in the Table View. Based on this I would like to discuss the following case. Suppose another method sends a message to the main queue before [_tableView reloadData] in that case will the second message get processed before [_tableView reloadData] ?
Now this is my case
Suppose I have two threads TA and TB and I have two methods MethodAand MethodB which look like this
This is MethodA
- (void) MethodA
{
dispatch_async(dispatch_get_main_queue(), ^{
[Myarray addObject:#"SomeObject"];
///----------<TimeFrameA>----------------
[_tableView reloadData]
});
}
This is MethodB
- (void) MethodB
{
dispatch_async(dispatch_get_main_queue(), ^{
//Runs under the assumption the "SomeObject" has already been displayed in Tableview
//Make changes to TableView/
});
}
Suppose that MethodB is called by ThreadB and occurs during TimeFRameA.
in that case will MethodB be called before [_tableView reloadData] ?
Is there any way for me to make sure that the MethodB only runs when the tableView is displaying the updated data ?
The main dispatch queue (which is associated with the main thread)
is a serial queue, not concurrent. Therefore it cannot happen
that in
dispatch_async(dispatch_get_main_queue(), ^{
[Myarray addObject:#"SomeObject"];
///----------<TimeFrameA>----------------
[_tableView reloadData]
});
}
any other code executes on the main queue between adding the object
and reloading the table view.
Any other block dispatched to the main queue executes either before
or after this block.
I would recommend you to use "dispatch_group" to synchronize.
Create a dispatch group object by calling:
dispatch_group_t group = dispatch_group_create();
In MethodA use:
-(void) MethodA {
dispatch_group_enter(group);
dispatch_async(dispatch_get_main_queue(), ^{
[Myarray addObject:#"SomeObject"];
///----------<TimeFrameA>----------------
[_tableView reloadData];
dispatch_group_leave();
});
}
In MethodB wait for the group to complete and then perform further operations:
-(void) MethodB {
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//Runs under the assumption the "SomeObject" has already been displayed in Tableview
//Make changes to TableView/
});
}
I am new in iOS.
I am trying to implement upload data in uitableview one by one row.
for that i am using background task.
using following code.
-(void)MethodUploadBgTaskAssign
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *responseString = [self MethodlblUploadClicked];
dispatch_async(dispatch_get_main_queue(), ^(void)
{
if([responseString isEqualToString:#"UploadSuccess"])
{
[self ReloadTblData];
[self ContinueUploadData];
}
});
});
}
-(void) ReloadTblData
{
if([dataArr count]>0)
[dataArr removeObjectAtIndex:0];
[uitblData reloadData];
}
-(void) ContinueUploadData
{
if((dataArr count]>0)
[self MethodUploadBgTaskAssign];
}
My problem is uploading data in table after some time table reload with empty data
because all data uploaded at that time.
I want show updated ui after uploading each cell in table.
What will be necessary changes in code?
appreciate for help.
It looks to me as if you are updating the table - but in the wrong thread (hence the table never actually appears to update). You need to use performSelectorOnMainThread to update the UI.
[self performSelectorOnMainThread:#selector(ReloadTblData:) // Update the table on the main thread
withObject:nil
waitUntilDone:NO];
I think this should work - give it a go!
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...
}
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];
}
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:
List your original data.
When the user scrolls past the last cell, show a footer that displays a loading symbol.
at the same time While the footer is displaying a loading symbol, then and ONLY then call the fetchLoadData method, and WAIT
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];