My condition is that when I scroll my tableview to the bottom or to the top, I need to do some reload, refresh job that will ask for new data from the server, but I want to check if the last job is done or not. If the last request is still working, I should not fire another request.
I'm using the same background queue created from dispatch_queue_create() to deal with the httpRequest.
- (id)init {
self = [super init];
if (self) {
...
dataLoadingQueue = dispatch_queue_create(#"DataLoadingQueue", NULL);
}
return self;
}
From now on, I just use a BOOL value to detect if the job is on working or not. Something like this:
if(!self.isLoading){
dispatch_async(dataLoadingQueue, ^{
self.isLoading = YES;
[self loadDataFromServer];
});
}
I just wonder if there is any way to change the code to be like the following:
if(isQueueEmpty(dataLoadingQueue)){
dispatch_async(dataLoadingQueue, ^{
[self loadDataFromServer];
});
}
Thus I can remove the annoying BOOL value that shows everywhere and need to keep tracking on.
Why don't you instead use NSOperationQueue (check [operationQueue operationCount]) ?
If you just want to use GCD, dispatch_group_t may be fit you.
#property (atomic) BOOL isQueueEmpty;
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_async(dispatchGroup, dataLoadingQueue, ^{
self.isQueueEmpty = NO;
//Do something
});
dispatch_group_notify(dispatchGroup, dataLoadingQueue, ^{
NSLog(#"Work is done!");
self.isQueueEmpty = YES;
});
While tasks have completed, the group will be empty and trigger the notification block in dispatch_group_notify.
Related
I'am now using dispatch_group to manage multi-threads in my project. something like this:
- (void)functionA{
self.taskGroup = dispatch_group_create();
// Call functionB more than one time
[self functionB];
...
[self functionB];
dispatch_group_notify(..., ^{
// Do something if all [self functionB] complete
});
- (void)functionB{
dispatch_group_enter(self.taskGroup);
if (condition) {
dispatch_group_leave(self.taskGroup);
return;
}
[self doSomethingInBackground:^{
NSLog(#"completed!");
dispatch_group_leave(self.taskGroup);
}];
}
My question is how to automatically call dispatch_group_leave when functionB is complete, so that I don't need to call it before every return statement in functionB or any background task called by functionB.
As #Sk0prion mentioned in comments, I can simply involve macro here.
#define m_return dispatch_group_leave(self.taskGroup);return
I have a UIViewController that does the following in viewDidLoad
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
items = [[DataFetcher sharedInstance] getItems handler:^(NSArray *currentItems){
if (currentItems.count % 30 == 0) { //don't wait for all the items to be ready show by chunks of 30
items = currentItems;
[tableView reloadData];
}
items = currentItems;
}];//Pretty complex call that takes some time to finish there is WebService calls, data parsing, storing some results ...
dispatch_async(dispatch_get_main_queue(), ^{
[tableView reloadData];
});
});
What I need to do is to stop getItems when I pop this viewController. It's pointless and it takes CPU Time and energy (This call may take up to a minute on some cases).
I am assuming I should be doing this in viewWillDisappear but how exactly?
You can use NSBlockOperation. Periodically check if it's been cancelled, and stop doing work if it has been:
- (void)getItemsWithHandler:(void (^)(NSArray *currentItems))handler {
self.operation = [NSBlockOperation blockOperationWithBlock:^{
if (self.operation.isCancelled) {
return;
}
// Do something expensive
if (self.operation.isCancelled) {
return;
}
// Do something else expensive
for (int i = 0; i < 10000; i++) {
if (self.operation.isCancelled) {
return;
}
// Do expensive things in a loop
}
}];
}
- (void) cancelGetItemsRequest {
[self.operation cancel];
self.operation = nil;
}
Alternatively, you can put a bunch of NSBlockOperations in an NSOperationQueue. You can set dependencies for the work, and cancel the entire queue at once if you want.
Cancelling asynchronous operations is nicely supported in NSOperation and NSOperationQueue, but it's quite a bit more complicated. In any case, your asynchronous code has to check from time to time whether it is cancelled or should still continue.
Best thing is to have a property "cancelled" somewhere, that you set when you don't want any more work to be done, and whenever you try to do more work, you check that property.
I've been looking at this question to try to solve the problem I have here. The tl;dr is I want to use GCD to let me show a "Waiting" screen while I preform some tasks, then hide the screen when it's done. Right now, I have
- (void) doStuff
{
// Show wait on start
[self.waitScreen setHidden:NO];
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Double nesting the dispatches seems to allow me to do UI changes as part of 'Code to execute' below.
// If I do not double nest like this, the UI still freezes while it executes
dispatch_queue_t queue2 = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue2, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Code to execute
{
//... Do my time consuming stuff here ...
// For testing purposes, I'm using
int i = 0;
while (i < 1000000000)
{
i++;
}
}
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
});
});
}
And this works just how I want. I'm calling [self doStuff] like so
- (IBAction) buttonTouchUpInside:(id)sender
{
[self doStuff];
}
- (void) doStuff
{
// ... code from first code block here ...
}
Everything to this point works perfectly. Now, I've discovered I will need to use this in a function call. So I need something like:
- (IBAction) buttonTouchUpInside:(id)sender
{
NSMutableString *string= [self doStuff];
// ... use 'string' to do other stuff ...
// For testing, I'm using
self.label.text = string;
}
- (NSMutableString *) doStuff
{
// ... code from first code block here ...
}
How do I need to change the syntax to be able to pass variables around with dispatch_async?
I looked at the Apple Docs and tried
- (IBAction) buttonTouchUpInside:(id)sender
{
NSMutableString *string= [self doStuff];
// ... use 'string' to do other stuff - shows 'nil' when I put breakpoints here ...
// For testing, I'm using
self.label.text = string;
}
- (NSMutableString *) doStuff
{
__block NSMutableString *string = [[NSMutableString alloc] initWithString:#"Initing"];
// Show wait on start
[self.waitScreen setHidden:NO];
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Code to execute
{
int i = 0;
while (i < 1000000000)
{
i++;
}
[string setString:#"Hello World"];
}
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
});
});
return string;
}
But when I run this, label just shows Initing. I need it to show Hello World. None of the changes I make in the block are being passed through.
After looking at some other questions, this seems to be referred to as a "race condition". As I understand it, once it hits the dispatch_async, the code in the block starts running on a new thread, but the code outside of the block continues to run at the same time on the old thread. So it looks like the thread outside the block is getting to self.label.text = string before the thread running the block can get to [string setString:#"Hello World"];. How can I make the self.label.text = string line wait until [string setString:#"Hello World"]; finishes?
First of all your reasoning of double nesting is flawed. Not sure why it might have worked, but the correct way is to do some async work, and any time you want to update the ui wrap that code in a block on the main queue.
- (void) doStuff
{
// Show wait on start
[self.waitScreen setHidden:NO];
// queue should be a global variable, you don't want to create it every time you
// execute doStuff
dispatch_async(queue, ^{
// Code to execute
{
//... Do my time consuming stuff here ...
// For testing purposes, I'm using
int i = 0;
while (i < 1000000000)
{
i++;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
}
Since your queue is performing work asynchronously you can't simply return a value from doStuff without waiting, which will block the queue you call doStuff on again.
If you just want to set a value on a label, do that too in the block executed on the main queue, like hiding the wait screen.
Another common way to do things it to provide a callback block to execute as soon as work is finished.
- (void) doStuffWithCompletionBlock:(void(^)(NSString *))block
{
// again, a global variable for the queue
dispatch_async(queue, ^{
// do some work here that shouldn't block the UI
dispatch_async(dispatch_get_main_queue(), ^{
block(#"My result string");
});
});
}
- (void) myAction:(id)sender
{
__weak typeof(self) weakSelf = self;
[self doStuffWithCompletionBlock:^(NSString *result) {
weakSelf.label.text = result;
}];
}
Notice that I call the completion block on the main queue, this is a choice. You could leave that out, but then you would still have do all UI updates on the main queue later in the completion block itself.
I am having some trouble updating my UI using performSelectorOnMainThread. Here is my situation. In my viewDidLoad I set up an activity indicator and a label. Then I call a selector to retrieve some data from a server. Then I call a selector to update the UI after a delay. Here's the code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.reloadSchools = [[UIAlertView alloc] init];
self.reloadSchools.message = #"There was an error loading the schools. Please try again.";
self.reloadSchools.title = #"We're Sorry";
self.schoolPickerLabel = [[UILabel alloc]init];
self.schoolPicker = [[UIPickerView alloc] init];
self.schoolPicker.delegate = self;
self.schoolPicker.dataSource = self;
self.server = [[Server alloc]init];
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self.view addSubview:_activityIndicator];
[self.view bringSubviewToFront:_activityIndicator];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
}
The selector updateUI checks to see if the data was retrieved, and calls a selector on the main thread to update the UI accordingly. Here is the code for these parts:
-(void)updateUI
{
self.schools = [_server returnData];
if(!(self.schools == nil)) {
[self performSelectorOnMainThread:#selector(fillPickerView) withObject:nil waitUntilDone:YES];
}
else {
[self performSelectorOnMainThread:#selector(showError) withObject:nil waitUntilDone:YES];
}
}
-(void)showError {
NSLog(#"show error");
[_activityIndicator stopAnimating];
[self.reloadSchools show];
}
-(void)fillPickerView {
NSLog(#"fill picker view");
schoolList = YES;
NSString *schoolString = [[NSString alloc] initWithData:self.schools encoding:NSUTF8StringEncoding];
self.schoolPickerLabel.text = #"Please select your school:";
self.shoolArray = [[schoolString componentsSeparatedByString:#"#"] mutableCopy];
[self.schoolPicker reloadAllComponents];
[_activityIndicator stopAnimating];
}
When the selector fillPickerView is called the activity indicator keeps spinning, the label text doesn't change, and the picker view doesn't reload its content. Can someone explain to me why the method I am using isn't working to update my ui on the main thread?
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//load your data here.
dispatch_async(dispatch_get_main_queue(), ^{
//update UI in main thread.
});
});
First of all you should not be using detachNewThreadSelector. You should use GCD and submit your background task to an async queue. Threads are costly to create. GCD does a much better job of managing system resources.
Ignoring that, your code doesn't make a lot of sense to me. You submit a method, getSchoolList, to run on a background thread. You don't show the code that you are running in the background.
Then use performSelector:withObject:afterDelay to run the method updateUI on the main thread after a fixed delay of 20 seconds.
updateUI checks for self.schools, which presumably was set up by your background thread, and may or may not be done. If self.schools IS nil, you call fillPickerView using performSelectorOnMainThread. That doesn't make sense because if self.schools is nil, there is no data to fill the picker.
If self.schools is not nil, you display an error, again using performSelectorOnMainThread.
It seems to me that the logic on your check of self.schools is backwards. If it is nil you should display an error and if it is NOT nil you should fill the picker.
Next problem: In both cases you're calling performSelectorOnMainThread:withObject:waitUntilDone: from the main thread. Calling that method from the main thread doesn't make sense.
Third problem: It doesn't make sense to wait an arbitrary amount of time for a background task to run to completion, and then either succeed or fail. You won't have any idea what's going on for the full 20 seconds. If the background task finishes sooner, you'll never know.
Instead, you should have your background task notify the main thread once the task is done. That would be a valid use of performSelectorOnMainThread:withObject:waitUntilDone:, while calling it from the main thread is not. (Again, though, you should refactor this code to use GCD, not using threads directly.
It seems pretty clear that you are in over your head. The code you posted needs to be rewritten completely.
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.