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];
}
});
});
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 have a use case where i am writing data to local couchebase database in ios. Here it will not support concurrent access of write operation. So i want to run the CRUD operation on main thread and return result after running some algorithm on data on secondary threads. when main thread took over control and executes code, current running thread is not waiting till main thread completes its operation. How can i handover result from main thread to other thread.
Ex :
+(BOOL)createDocument:(NSDictionary*)data withId:(NSString*)docId {
__block CBLDocument* doc = nil;
// NSLog(#"%d count ", [[self database] documentCount]);
dispatch_async(dispatch_get_main_queue(), ^{
if(docId.length > 0) {
doc = [[self getDatabase] documentWithID:docId];
} else {
doc = [[self getDatabase] createDocument];
}
});
//I want current thread to wait till main thread completes its execution
if(doc){
return YES;
}else{
return NO;
}
}
If you know for a fact that this method is not called from the main queue, you can use dispatch_sync:
+(BOOL)createDocument:(NSDictionary*)data withId:(NSString*)docId {
__block CBLDocument* doc = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
if(docId.length > 0) {
doc = [[self getDatabase] documentWithID:docId];
} else {
doc = [[self getDatabase] createDocument];
}
});
//I want current thread to wait till main thread completes its execution
if(doc){
return YES;
}else{
return NO;
}
}
A more generalized approach would be to create a dedicated, custom dispatch queue for your database interaction. Then, any thread (either the main thread or any background thread) that wants to interact with the database would perform a dispatch_sync to that dedicated queue.
This provides a cleaner implementation, making the functional intent more explicit, and ensures that database interaction initiated from a background thread will not block the main thread (unless, of course, the main thread happens to be initiating database interactions with this database queue at the same time). This dedicated queue approach is in the spirit of the "One Queue per Subsystem" design pattern discussed in WWDC 2012 video, Asynchronous Design Patterns with Blocks, GCD, and XPC (it's the fifth design pattern discussed in the latter part of the video).
You can make another dispatch_async call to your "current thread" from the main thread. So you'll use another function block and put your if(doc) stuff into that. That's how chaining between threads are handled with GCD API.
So the problem with your code is, createDocument returning after dispatching to another thread. Instead, you should change createDocument to take a function block argument.
+(BOOL)createDocument:(NSDictionary*)data
withId:(NSString*)docId
onComplete:(void (^)(CBLDocument*))onComplete;
And change your dispatch_async call as follows:
dispatch_async(dispatch_get_main_queue(), ^{
if(docId.length > 0) {
doc = [[self getDatabase] documentWithID:docId];
} else {
doc = [[self getDatabase] createDocument];
}
dispatch_async(yourCurrentThread, ^{
onComplete(doc);
});
});
However if you really want to BLOCK your current thread, you should use dispatch_sync instead of dispatch_async.
dispatch_sync(dispatch_get_main_queue(), ^{
...
});
return doc != nil;
Sorry if there are any syntax errors, I haven't tested this.
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 an app that I'm accessing a remote website with NSURLConnection to run some code and then save out some XML files. I am then accessing those XML Files and parsing through them for information. The process works fine except that my User Interface isn't getting updated properly. I want to keep the user updated through my UILabel. I'm trying to update the text by using setBottomBarToUpdating:. It works the first time when I set it to "Processing Please Wait..."; however, in the connectionDidFinishLoading: it doesn't update. I'm thinking my NSURLConnection is running on a separate thread and my attempt with the dispatch_get_main_queue to update on the main thread isn't working. How can I alter my code to resolve this? Thanks! [If I need to include more information/code just let me know!]
myFile.m
NSLog(#"Refreshing...");
dispatch_sync( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getResponse:#"http://mylocation/path/to/file.aspx"];
});
[self setBottomBarToUpdating:#"Processing Please Wait..."];
queue = dispatch_queue_create("updateQueue", DISPATCH_QUEUE_CONCURRENT);
connectionDidFinishLoading:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
In my connectionDidFinishLoading: I would try something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
});
Then all that file access is happening in a background queue so the main queue is not locked up. The main queue will also complete this call to connectionDidFinishLoading much more quickly, since you're throwing all the hard work onto the default queue instead, which should leave it (and the main thread) ready to accept your enqueuing of the updates to the UI which will be done by the default queue as it processes the block you just enqueued to it.
The queue handover becomes
main thread callback to connectionDidFinishLoad:
rapid handoff to default global queue releasing main thread
eventual hand off to main queue for setBottomBarToUpdating: calls
performing main queue blocks on main thread to properly update UI
eventual completion of blocks on main queue
eventual completion of blocks on default queue
You've increased concurrency (good where you've good multi-core devices) and you've taken the burden of I/O off the main thread (never a good place for it) and instead got it focused on user interface work (the right place for it).
Ideally you woud run the NSURLConnection run loop off the main thread too, but this will might be enough for you to get going.
Which run loop are you running the NSURLConnection in? If it's the main loop, you're queueing up the setBottomBarToUpdating: calls behind the work you're already doing, hence the probable reason why you're not seeing the UI update.
You could also give performSelectorOnMainThread try like so:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Contacts..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Emails..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
In Java you can suspend the current thread's execution for an amount of time using Thread.sleep(). Is there something like this in Objective-C?
Yes, there's +[NSThread sleepForTimeInterval:]
(Just so you know for future questions, Objective-C is the language itself; the library of objects (one of them at least) is Cocoa.)
Sleeping for one second in Java:
Thread.sleep(1000);
Sleeping for one second in Objective C:
[NSThread sleepForTimeInterval:1.0f];
Why are you sleeping? When you sleep, you are blocking the UI and also any background URL loading not in other threads (using the NSURL asynchronous methods still operates on the current thread).
Chances are what you really want is performSelector:withObject:AfterDelay. That's a method on NSObject you can use to call a method at some pre-determined interval later - it schedules a call that will be performed at a later time, but all of the other stuff the thread handles (like UI and data loads) will still continue.
Of course, you could also use the standard Unix sleep() and usleep() calls, too. (If writing Cocoa, I'd stay with the [NSThread sleepForTimeInterval:], however.)
If you use NSThread sleepForTimeInterval(commented code) to sleep, fetching data will be blocked, but +[NSThread sleepForTimeInterval:] (checkLoad method) will not block fetching data.
My example code as below:
- (void)viewDidAppear:(BOOL)animated
{
//....
//show loader view
[HUD showUIBlockingIndicatorWithText:#"Fetching JSON data"];
// while (_loans == nil || _loans.count == 0)
// {
// [NSThread sleepForTimeInterval:1.0f];
// [self reloadLoansFormApi];
// NSLog(#"sleep ");
// }
[self performSelector:#selector(checkLoad) withObject:self afterDelay:1.0f];
}
-(void) checkLoad
{
[self reloadLoansFormApi];
if (_loans == nil || _loans.count == 0)
{
[self performSelector:#selector(checkLoad) withObject:self afterDelay:1.0f];
} else
{
NSLog(#"size %d", _loans.count);
[self.tableView reloadData];
//hide the loader view
[HUD hideUIBlockingIndicator];
}
}
usleep() can also be used as ive used this to pause the current thread at times