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.
Related
I have three methods, two of them run at the same time. And the third method should be started only when the first and second method together complete their work. Either the first or second method, competitors, can finish their work first.
- (void)method1 {
//DO Long Work
isMethod1Complete = YES;
[self method3];
}
- (void)method2 {
//DO Long Work
isMethod2Complete = YES;
[self method3];
}
- (void)method3 {
if (isMethod1Complete && isMethod2Complete) {
//DO Work once
}
}
Method 3 should always be called once. But the problem is that there is a situation that method1 and method2 have finished working at the same time, and method3 is called twice. Tell me how to solve this problem in objective c for iOS?
Update:A concrete example, I have two services that call delegates when they finish their work.
- (void)method1Handler {
isMethod1Complete = YES;
[self method3];
}
- (void)method2Handler {
isMethod1Complete = YES;
[self method3];
}
How can this be solved without blocks?
For blocks, Rob's example is the best.
You say:
I have three methods, two of them run at the same time.
That means that they must be asynchronous or running on background queues (otherwise there's no way for them to run at the same time).
So, the idea is that you should give them both completion handlers (which will be called when they're done):
- (void)method1WithCompletion:(void(^ _Nonnull)(void))completion {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
//DO Long Work asynchronously
completion();
});
}
- (void)method2WithCompletion:(void(^ _Nonnull)(void))completion {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
//DO Long Work asynchronously
completion();
});
}
- (void)method3 {
// final task
}
In the above example, I added explicit dispatch_async calls to a background queue to ensure that the two long tasks run asynchronously. But if the code is already doing something asynchronous (e.g. a network request), then you will likely not need these dispatch_async calls, but just put the completion() call inside the completion handler provided by whatever API you are already using. But without more information about what method1 and method2 are doing, I cannot be more specific.
But, setting that aside, once your method1 and method2 have their own completion handlers, you can use dispatch_group_notify to identify what should be done when all of the dispatch_group_enter calls are balanced by their corresponding dispatch_group_leave calls:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self method1WithCompletion:^{
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self method2WithCompletion:^{
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self method3];
});
In subsequent comments, you mentioned that you are not using a completion block-based API, but rather a delegate-protocol-based API. You have a few options, for example:
You can use the same above closure pattern, but just save the completion handlers as block properties, e.g.:
For example, define block properties:
#property (nonatomic, copy, nullable) void (^completionOne)(void);
#property (nonatomic, copy, nullable) void (^completionTwo)(void);
Then, your method1 and method2 would save these blocks:
- (void)method1WithCompletion:(void(^ _Nonnull)(void))completion {
self.completionOne = completion;
// start your time consuming asynchronous process
}
// and your completion delegate method can then call the saved closure
// and then remove it
- (void)method1DidComplete {
self.completionOne();
self.completionOne = nil;
}
- (void)method2WithCompletion:(void(^ _Nonnull)(void))completion {
self.completionTwo = completion;
// start second asynchronous process
}
// same as above
- (void)method2DidComplete {
self.completionTwo();
self.completionTwo = nil;
}
The delegate-protocol completion API would then just call the saved block properties (and probably reset them to nil to free the memory associated with those blocks).
Then you can use the dispatch group notify process as shown in my original answer, above.
Alternatively, rather than using blocks, you can just use dispatch group by itself. For example, define dispatch group property:
#property (nonatomic, strong, nullable) dispatch_group_t group;
Then, you create your group and start your two tasks:
self.group = dispatch_group_create();
[self method1];
[self method2];
dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
[self method3];
});
And, the two methods then dispatch_group_enter when you start the tasks and dispatch_group_leave in their respective completion handler delegate methods:
- (void)method1 {
dispatch_group_enter(self.group);
// start first asynchronous process
}
// in your delegate completion method, you "leave" the group
- (void)method1DidComplete {
dispatch_group_leave(self.group);
}
- (void)method2 {
dispatch_group_enter(self.group);
// start second asynchronous process
}
- (void)method2DidComplete {
dispatch_group_leave(self.group);
}
- (void)method3 {
// you might as well remove the group now that you're done with it
self.group = nil;
// final task
NSLog(#"doing three");
}
Personally, I would generally lean towards the first option (that way, the dispatch group stuff is contained in a single method), but either approach works.
Why not dispatching the "call" to method3 in a serial queue?
dispatch_queue_t notSimQ;
notSimQ = dispatch_queue_create("notSimQ", NULL);
- (void)method1 {
//DO Long Work
isMethod1Complete = YES;
dispatch_async( notSimQ, // or sync
^{
[self method3];
});
}
- (void)method2 { … } // similiasr
- (void)method3 { … } // unchanged
The calls to method3 are never in competition.
- (void)method1 {
//DO Long Work
isMethod2Complete = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){
[self method3];
}
}
- (void)method2 { … }
- (void)method3 { … }
- (void)viewDidLoad {
[super viewDidLoad];
[self method1];
[self method2];
}
- (void)method1 {
//DO Long Work
isMethod1Complete = YES;
}
- (void)method2 {
//DO Long Work
isMethod2Complete = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){
[self method3];
}
}
- (void)method3 {
if (isMethod1Complete && isMethod2Complete) {
//DO Work once
}
}
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
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.
I am building an iOS app and some part of its code relies on the success/failure value returned from a particular task.This tasks involves callbacks from a library. I want the return value from this task to be returned only after the callback has returned either success/failure. But since I wrote a sequential code the return value is returned even before the callback returns a success/failure.
I looked into using modal view controllers and from what I understand I can make the task execute from this view controller and then return the code back.
But this also doesn't suit my requirements as when the code which initiates the callback sequence is executed I don't want a new view controller to be displayed. Although there is a certain callback which requires me to prompt the user for information. I do this in a popover and I considered making the view controller within the popover modal. But then the callbacks will still be part of the main thread and I won't receive them when my popover is presented modally(?).
With my current understanding of these concepts I don't know how to proceed. Is there some way to do this in iOS?
EDIT:
The code does something like this
//In CustomTableViewController
-(void) someFunc
{
ENUM_NAME code = [TaskController startTheTask:args];
if(code == SUCCEEDED)
{
//Do Something
}
if(code == FAILED)
{
//Do Something Else
}
}
//In TaskController
-(ENUM_NAME) startTheTask:args
{
startWorkflow(args); //This function registers callback function with the library.
return finalCode; //This is returned even before it is set to SUCCEEDED/FAILED
}
-(void) onCallback:params
{
MSG_TYPE msg = [params getMsg];
if(msg == TASK_FAILED)
finalCode = FAILED;
if(msg == TASK_SUCCEEDED)
finalCode = SUCCEEDED;
if(msg == TASK_SHOW_PROMPT)
{
[PopOverController showPopOver];
}
}
-(void) onUserInfoAdded
{
//This is called when Confirm is clicked in the popover
continueWorkflow(params); //asks for the next callback to happen
}
-(void) onCancleClicked
{
//This is called when Popover is dismissed without entering Info
cancleWorkflow(params); //asks for result of the workflow through callback
}
You can use GCD. For example:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
//put one process here
dispatch_group_leave(group); //when done
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
//put another process here
dispatch_group_leave(group); //when done
});
// All updates finished
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// add last steps here after all processess are finished
});
dispatch_release(group);
You can use a semaphore to delay the execution until a block returns:
__block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSData *dataFromTheBlock = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// block implementation
// dataFromTheBlock = some data;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
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.