I need to parse a json in the background thread. This parser also needs to update my progress bar using delegate method, but for some reason my progress bar is only updated and the end of the process. My code is the following:
-(void)downloadStuff
{
// some other code
downloaderController.delegate = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[downloaderController parseJson];
});
}
The method parseJson during the process calls the delegate several times to update the progress bar like this:
-(void)updateProgressBar
{
//Is anyone listening
if([_delegate respondsToSelector:#selector(updateProgressBar)])
{
[_delegate updateProgressBar];
}
}
And my updateProgressBar is the following:
(void)updateProgressBar
{
_progressBar.progress =+ 0.05f;
}
Can anyone explain me what am I doing wrong here?
Switch back to the main thread before calling updateProgressBar. You can use GCD or performSelector to do so.
[self.delegate performSelectorOnMainThread:#selector(updateProgressBar) withObject:nil waitUntilDone:NO];
Related
I've found this method to work with background thread. My question is that I've run a whole process in background thread which include number of methods. Frist method calls the second one and the the second one makes some data and pass it to the third one.
-(void)firstMethod
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
if(someCondition == 0)
{
[self secondMethod:myArray];
}
}
dispatch_async(dispatch_get_main_queue(), ^(void){
[self.navigationController popViewControllerAnimated:YES];
});
});
}
-(void)secondMethod:(NSArray *)array {
a= a+3;
[self thirdMethod:array[a];
}
So you get the general idea right? So do I have to put the functionality of second and third method in background thread too? Or how this whole process will take place?
I implemented login method in this way:
[KVNProgress show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//some error handling like:
if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:#"Username too short!"];
_passwordField.text = #"";
return;
}
//Then I call login web service synchronously here:
result = [ServerRequests login];
dispatch_async(dispatch_get_main_queue(), ^{
if(!result)
{
[KVNProgress showErrorWithStatus:#"problem!" completion:NULL];
_passwordField.text = #"";
}
else if([result.successful boolValue])
{
[KVNProgress showSuccessWithStatus:result.message];
}
});
});
It crashed mostly and by surrounding blocks with only Main Queue (no priority default one) that solved! but the problem is:KVNProgress is only showing in error handling area not the next part that we call web service. It's not user friendly at all! Any idea is welcomed :)
You MUST call methods that update the user interface in any way from the main thread, as per the UIKit documentation:
For the most part, use UIKit classes only from your app’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your app’s user interface in any way.
I suggest you try to limit the number of callbacks you make to the main thread, so therefore you want to batch as much user interface updates together as you can.
Then all you have to do, as you correctly say, is to use a dispatch_async to callback to your main thread whenever you need to update the UI, from within your background processing.
Because it's asynchronous, it won't interrupt your background processing, and should have a minimal interruption on the main thread itself as updating values on most UIKit components is fairly cheap, they'll just update their value and trigger their setNeedsDisplay so that they'll get re-drawn at the next run loop.
From your code, it looks like your issue is that you're calling this from the background thread:
if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:#"Username too short!"];
_passwordField.text = #"";
return;
}
This is 100% UI updating code, and should therefore take place on the main thread.
Although, I have no idea about the thread safety of KVNProgress, I assume it should also be called on the main thread as it's presenting an error to the user.
Your code therefore should look something like this (assuming it's taking place on the main thread to begin with):
[KVNProgress show];
//some error handling like:
if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:#"Username too short!"];
_passwordField.text = #"";
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Then I call login web service synchronously here:
result = [ServerRequests login];
dispatch_async(dispatch_get_main_queue(), ^{
if(!result) {
[KVNProgress showErrorWithStatus:#"problem!" completion:NULL];
_passwordField.text = #"";
} else if([result.successful boolValue]) {
[KVNProgress showSuccessWithStatus:result.message];
}
});
});
It seems that the function call [self updateUI]; blocked by boo.
Is boo run in another background thread or same as foo as the code below?
How can the [self updateUI]; not block by boo?
- (void)MainFunction
{
[self performSelectorInBackground#selector(foo) withObject:nil];
}
- (void)foo
{
[self performSelectorInBackground#selector(boo) withObject:nil];
//updaate UI in MainThread
[self updateUI];
}
- (void)boo
{
//function here take long time to run;
}
In your code seems that you call foo in background and so the UI is updated in the background thread that is not possible because you need to do that in the main thread. In any case, performSelectorInBackground is a little bit old...use the dispatcher in this way:
- (void)MainFunction
{
[self foo];
}
- (void)foo
{
dispatch_async(dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAUL, 0ull), ^{
[self boo];
dispatch_async(dispatch_get_main_queue(), ^{
//updaate UI in MainThread
[self updateUI];
};
};
}
- (void)boo
{
//function here take long time to run;
}
In this case updateUI wait boo, but if you want updateUI before and doesn't matter when boo finish:
- (void)foo
{
dispatch_async(dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAUL, 0ull), ^{
[self boo];
};
[self updateUI];
}
performSelectorInBackground performs the selector on a NEW thread. From Apple docs:
This method creates a new thread in your application, putting your
application into multithreaded mode if it was not already. The method
represented by aSelector must set up the thread environment just as
you would for any other new thread in your program.
If you would like to perform both functions on the SAME background thread, you'll have to declare the background thread (also called queue) as a private member of the class (so it will be accessible from both functions) and perform the selector on that queue
I've got class:
ClassX.m
#property (assign) BOOL wasProcessed;
-(void) methodA { //<- this can be called many times in short period of time
dispatch_async(dispatch_get_main_queue(), ^{
[self methodB];
});
}
- (void) methodB {
if (!self.wasProcessed) {
self.wasProcessed = YES;
//... some code
}
}
Since dispatch_async is used so a few calls to methodB can be processed concurrently at the same time and following code needs to be atomic:
if (!self.wasProcessed) {
self.wasProcessed = YES; //e.g two calls can enter here before setting YES and it would be bad because I want to process it only one time
How can those 2 lines be made atomic (checking and setting variable)? I dont want to make atomic code that is after "self.wasProcessed = YES;" so moving whole if to #synchronize(self) won't be good solution. If there is anything wrong with my thinking please point it out as I'm not very experienced in those topics, Thank you.
Try #synchronized. While the enclosed code is being executed on a thread, it will block other threads from executing it.
- (void) methodB {
#synchronized(self) {
if (!self.wasProcessed) {
self.wasProcessed = YES;
//... some code
}
}
}
-(void) methodA {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^(){
[self methodB];
}];
});
}
Your's methodB will be only called in main thread, so it will be never performed simultaneously.
I have a method like:
- (BOOL)shouldDoSomeWork {
BOOL result = // here I need do hard work with data in background thread and return result, so main thread should wait until the data is calculated and then return result;
return result;
}
How to implement that?
Are you looking for this:
-(void) startWork
{
//Show activity indicator
[NSThread detachNewThreadSelector:#selector(doSomeWork) toTarget:self withObject:nil];
}
-(void) doSomeWork
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
//Do your work here
[pool release];
[self performSelectorOnMainThread:#selector(doneWork) withObject:nil waitUntilDone:NO];
}
-(void) doneWork
{
//Hide activity indicator
}
Example how to do it with GCD:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Your hard code here
// ...
//BOOL result = ...
dispatch_async(dispatch_get_main_queue(),^{
[self callbackWithResult:result]; // Call some method and pass the result back to main thread
});
});
That's not typically how you would do it. You need something structured more like this:
- (void)doSomeWorkAndThen:(^void)block {
dispatch_async(dispatch_get_global_queue(0, 0), ^ {
// do
// some
// work
dispatch_sync(dispatch_get_main_queue(), ^ {
block();
});
});
That is, you keep the request and what you do afterwards in one place.
Common advice is to use the highest level of abstraction available to you to perform a task. As such NSThread should be relatively low down in the list of things you can do to execute work in the background.
The order you investigate APIs should be like this:
NSOperation / NSOperationQueue
Grand Central Dispatch (libdispatch)
NSThread
POSIX threads
With the first two you write your code as a "unit of work" and then put this work on a queue to be executed at some point. The system takes care of creating and destroying threads for you and the APIs are easy to work with. Here's an example using NSOperationQueue.
NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
//Do work
//update your UI on the main thread.
[self performSelectorOnMainThread:#selector(workDone:) withObject:workResults waitUntilDone:NO];
}];
[self.operationQueue addOperation:blockOperation];
easy as that.