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.
Related
I want to illustrate the progress on MBProgressHUD item, but when i triger this method :
- (IBAction)signInBttn:(id)sender {
MBProgressHUD *hudd = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hudd.mode = MBProgressHUDModeAnnularDeterminate;
hudd.labelText = #"Loading";
__block float value = 0;
for (int j = 0; j<2000; j++) {
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i<20000 ; i++) {
}
value += 0.001;
dispatch_async( dispatch_get_main_queue(), ^{
hudd.progress = value;
});
});
}
}
hud appears fully to 100%. This is only for my information, I dont have idea how to create background task which calculate something and when he done with e.g. 40% the HUD is refreshing to 40% of his progress. I hope I made myself clear, and if anyone has time to help improve my code, thanks a lot for any answers
In this case, you can solve the problem by decoupling the updating of the counter from the updating of your HUD in your UI. Apple refers to this as "updating the state asynchronously" in WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC.
Generally this isn't necessary (most of the time the stuff we're doing asynchronously is slow enough that we don't have problems), but if doing something that is running faster than the UI can hope to keep up with, you create a "dispatch source" for this. I'm going to illustrate it with a UIProgressView, but the same applies to pretty much any UI:
// create source for which we'll be incrementing a counter,
// and tell it to run the event handler in the main loop
// (because we're going to be updating the UI)
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// specify what you want the even handler to do (i.e. update the HUD or progress bar)
dispatch_source_set_event_handler(source, ^{
self.iterations += dispatch_source_get_data(source);
[self.progressView setProgress: (float) self.iterations / kMaxIterations];
});
// start the dispatch source
dispatch_resume(source);
// now, initiate the process that will update the source
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (long i = 0; i < kMaxIterations; i++)
{
// presumably, do something meaningful here
// now increment counter (and the event handler will take care of the UI)
dispatch_source_merge_data(source, 1);
}
// when all done, cancel the dispatch source
dispatch_source_cancel(source);
});
In my example, iterations is just a long property:
#property (nonatomic) long iterations;
And I defined my kMaxIterations constant as follows:
static long const kMaxIterations = 10000000l;
First off, if you want to delay execution use dispatch_after: Apple Doc since it could be that Clang is optimizing your loop (i.e. by making it not exist).
Within that block call dispatch_sync on the main thread to update the UI, since dispatch_async is not guaranteed to execute 'evenly'. Something like this ought to work...
for (...) {
dispatch_after(<some formula of i>, DEFAULT_PRIORITY, ^{
dispatch_sync(MAIN_QUEUE, ^{ hudd.progress = value });
}
}
In my app, I have a UITableViewController.
Its tableView is divided in 3 sections.
I download datas for each of those sections from my server. To do this, I have 3 functions (for example f1 f2 and f3). Each updates a corresponding NSArray, used as data source for my table.
Now what I want is to reload datas using this functions and refresh my tableView once this 3 functions are done, but without disturbing the user.
I'm not used with asynchronous request, blocks, threads etc... and I'm looking for tips.
Actually, here is what I do :
-(void)viewDidLoad
{
//some settings
[NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:#selector(reloadDatas) userInfo:nil repeats:YES];
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
[self reloadDatas];
});
}
-(void)reloadDatas
{
dispatch_queue_t concurrentQueue = dispatch_get_main_queue();
dispatch_async(concurrentQueue, ^{
[self f1];
[self f2];
[self f3];
[myDisplayedTable reloadData];
});
}
-(void)f1
{
//load datas with a url request and update array1
}
-(void)f2
{
//load datas with a url request and update array2
}
-(void)f3
{
//load datas with a url request and update array3
}
But here, my tableView is "frozen" until it is refreshed.
I don't care about the order of execution of f1 f2 and f3, but I need to wait for this 3 functions to be done before refresh my tableView.
Thanks for your help.
EDIT
Thanks for all your answers.
Here is the working solution :
As mros suggets, I removed the dispatch queue from the viewDidLoad, and replace in reloadDatas:
dispatch_queue_t concurrentQueue = dispatch_get_main_queue();
with
dispatch_queue_t mainThreadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
And finally, I reload my table in a main thread
dispatch_async(dispatch_get_main_queue(), ^{ [myDisplayedTable reloadData]; });
So your "background thread" is actually your main thread. You have to use dispatch_get_global_queue and specify a priority to actually get a different thread. Also, the dispatch async in viewDidLoad is useless as all view controller lifecycle methods are called in the main thread. I would recommend doing something as follows in your f1, f2 and f3 methods:
Start by launching an asynchronous url request, then in the completion block, update arrayX and reload a particular section of your tableview. This way all three requests can happen simultaneously and the table just updates the necessary data when each one finishes. Alternatively, if you only want to reload once, just replace the concurrentQueue variable you have with a background thread and then perform [tableView reloadData] on the main thread.
The previous answers are absolutely right. However your implementation of reloadDatas & viewDidLoad is a bit problematic.
Just to clarify:
You want to complete the time consuming data loading stuff in a background thread, then update the UI/Cells when your data is ready on the main thread.
Like so:
-(void)viewDidLoad
{
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.my.backgroundQueue", NULL);
dispatch_async(concurrentQueue, ^{
[self reloadDatas];
});
}
-(void)reloadDatas
{
// Expensive operations i.e pull data from server and add it to NSArray or NSDictionary
[self f1];
[self f2];
[self f3];
// Operation done - now let's update our table cells on the main thread
dispatch_queue_t mainThreadQueue = dispatch_get_main_queue();
dispatch_async(mainThreadQueue, ^{
[myDisplayedTable reloadData]; // Update table UI
});
}
One other thing. Pulling data from a server and updating table cells is pretty common.
No need for queues or timers here.
Here's an alternative structure.
Say you're pulling mp3's from your server :
Your model class is : Music.h/m
Your Model manager is : MusicManager.h/m (Singleton) - it will contain an array of music objects - that singleton is basically your datasource;
and finally your UItableViewController : MusicTableVC.h/m
In MusicManager.h/m : You have an NSMutableArray which will be loaded with Music.h objects that you've pull from the server. You can do that as soon as you app loads without even waiting for the TableViewController.
Inside MusicManager you have a few helper methods to add or remove items from the mutableArray and provide the count and of course your networking methods.
Finally : Post a notification in your network code. Your UITableViewController should listen/observe that notification and "reload" accordingly.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NewMusicAdded" object:nil];
You query data from your server, parse the data into Music objects add them to your NSMutable array and post a notification to let the table update itself.
Pretty standard recipe.
In reloadDatas method you should change this line:
dispatch_queue_t concurrentQueue = dispatch_get_main_queue();
To:
dispatch_queue_t concurrentQueue = dispatch_queue_create("some queue", NULL);
But when you call [myDisplayedTable reloadData], you need to call this operation in the main queue.
dispatch_async(dispatch_get_main_queue(), ^{ [myDisplayedTable reloadData]; });
I'm trying to update my textView on screen before it starts downloading data. Right now, it only updates the view after all of the downloads are complete. How can I do it before or in between the downloads?
Edit: I want the self.textView.text = #"Connection is good, start syncing..."; to update the UI before the downloading starts. But right now, it only updates after the download finishes.
Here is what the code looks like.
if ([self.webApp oAuthTokenIsValid:&error responseError:&responseError]) {
self.textView.text = #"Connection is good, start syncing...";
[self.textView setNeedsDisplay];
[self performSelectorInBackground:#selector(downloadCustomers:) withObject:error];
}
I'm new to this and have yet to learn how threads work, but from what I read, the downloadCustomers function should be using a background thread leaving the main thread to update the UI.
if ([self.webApp oAuthTokenIsValid:&error responseError:&responseError]) {
self.textView.text = #"Connection is good, start syncing...";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self downloadCustomers];
dispatch_async(dispatch_get_main_queue(), ^{
//Do whatever you want when your download is finished, maybe self.textView.text = #"syncing finished"
});
});
}
The pattern here is to initialize your download on background thread and then call back to main thread for UI update.
Below is an example using GCD. The advantage of GCD version is that you can consider using whatever you do in -downloadCustomers, to insert in-line where you call it.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self downloadCustomers];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setNeedsDisplay];
});
});
I have the following For loop which iterates through an NSMutableArray and calls the setImage method :
//Code to iterate through pictures and create ImageView class for each one.
for (int i =0; i<=[pictureThumbnailArray count]-1; i++) {
NSLog(#"Thumbnail count is %d", [pictureThumbnailArray count]);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
[self setImage:[pictureThumbnailArray objectAtIndex:i]:i];
});
}
The setImage method sets various parameters on the picture and then finally adds it to the subview on the main thread :
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[self.view addSubview:onePicture];
});
The problem is that the images appear on the screen randomly, rather than loading in order one by one. Can any suggest how I can improve this ?
Thanks.
The global background queue is concurrent, not serial, meaning that it doesn't guarantee ordering. You can create a custom serial queue that does:
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
and use that in your dispatch_async.
I want to synchronize some data with a web service. For each item I have to make a asynchronous call.
I want to have a completion block witch is called, when each item was synchronized. For each item I am able to perform a completion block. Now, I don't know a good way how to do it.
This is the interface:
-(void) synchronizeItemsOnComplete:(CompleteBlock) block {
NSArray* items = // get items
for (int i = 0, n = [items count]; i < n; i++) {
[self synchronizeItem:[items objectAtIndex:i] onComplete:^{
// What do do here?
}];
}
// And/or here?
}
-(void) synchronizeItemOnComplete:(CompleteBlock) block {
// do something
block();
}
How can I wait for the synchronization and then perform the block?
I tried something like this:
NSArray* items = // get items
__block int countOfItemsUntilDone = [items count];
for (int i = 0, n = countOfItemsUntilDone; i < n; i++) {
[self synchronizeItem:[items objectAtIndex:i] onComplete:^{
countOfItemsUntilDone--;
}];
}
dispatch_queue_t queue = dispatch_queue_create("wait for syncing", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
while (countOfItemsUntilDone > 0) {
usleep(1000); // wait a little bit
}
block();
});
dispatch_release(queue);
But I think this is a quite bad way. Any ideas?
Instead of spinning in a loop waiting for the counter to equal zero, check the counter value each time you decrement it, then fire an event when it reaches zero.
-(void) synchronizeItemsOnComplete:(CompleteBlock) block {
NSArray* items = // get items
__block NSUInteger remaining = [items count];
for (ItemClass* item in items) {
[self synchronizeItemImage:item onComplete:^{
--remaining;
if (remaining == 0) {
block();
}
}];
}
}
To explain why it feels wrong, there are two things you're doing here that you should do either never or rarely:
Using background queues. This is difficult and bug-prone. Don't do it without reading up a lot about writing concurrent code. You also only really need to do this if an operation blocks for a substantial amount of time (eg., to read a file from disk, or perform an intensive calculation). Don't assume you need to do it unless you have a good reason (eg., a measurable performance problem).
Spinning in a loop, checking a variable for changes and calling sleep. You should never do this.
Also, if you're looping over the elements in an array, the for ... in syntax is much nicer (and potentially more efficient) calling objectAtIndex: on each index.
Never check or decrement shared memory in different threads like this, it can cause races. Use a dispatch group to do what you're doing.
dispatch_queue_t myBGQueue;
dispatch_group_t itemsGroup = dispatch_group_create();
for (ItemClass *item in items) {
dispatch_group_async(itemsGroup, myBGQueue, ^{
[self synchronizeItemImage:item];
});
}
/* execution will sleep here until all the blocks added in the `for` complete */
dispatch_group_wait(itemsGroup, DISPATCH_TIME_FOREVER);
dispatch_release(itemsGroup);
You can use these to use synchronously.
GCD and this
performSelector:waitUntilDone:YES