5

I am calling the code below from within the AppDelegate:

-(void) application:(UIApplication *)application performFetchWithCompletionHandler:
(void (^)(UIBackgroundFetchResult))completionHandler {


-(BOOL)backgroundRefresh{
    newData = false;
    callStartTime = [NSDate date];

    [self processAll];
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while(!fetchComplete);
        NSLog(@"NOW COMPLETE");

   });
    NSLog(@"Got here now!");
    return newData;
}

The call to [self processAll] runs code that has async calls etc, and will keep looping around until all activities are complete, repeatedly calling itself. Once all tasks are complete, fetchComplete is set to true. This part works fine.

I need the code to wait for fetchComplete to be true, and then return a bool back to AppDelegate.

The issue is, that quite rightly, the current checking, whilst it works to show the NSLogs etc., is of no use to returning the BOOL back to the caller, as the process is currently async. I have set it to async so that it runs, otherwise, I find that the processAll code is being blocked from running.

Could someone please shed some light onto how I can monitor for fetchComplete, and only once that is true return the bool to the calling function?

I have tried moving the return to into the async block, after the while has returned, but this returns a syntax error due to calling within the block.

Cœur
  • 32,421
  • 21
  • 173
  • 232
NeilMortonNet
  • 1,460
  • 4
  • 16
  • 33
  • You cant just wait for the async task to complete in order to return, that would make it sync after all, you have to use either some delegate or NSNotificationCenter to tell the intersted party that the fetch is done and get them the data. – Daniel May 03 '14 at 17:07
  • 6
    `while(!fetchComplete);` Don't do that. Ever. This is a busy-poll that'll spin at least one core of the CPU as fast as possible while doing nothing useful. It'll burn battery life and slow overall performance. – bbum May 03 '14 at 17:29
  • Thanks for the comments on this. Have taken on board all of them, including the fact I shouldn't use the while(!fetctComplete). Thank you once again. – NeilMortonNet May 03 '14 at 20:35

3 Answers3

6

Use a notification. First, you must start listening to the notification like this

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(fetchDidComplete)
                                             name:@"fetchDidComplete"
                                           object:nil];

Where ever you set fetchComplete to true, post a notification like this

[[NSNotificationCenter defaultCenter] postNotificationName:@"fetchDidComplete"
                                                    object:self
                                                  userInfo:nil];

Then you should have a method called fetchDidComplete that will do the post completion work. Don't forget to stop listening to the notification once done.

[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:@"fetchDidComplete"
                                              object:nil];
Zia
  • 14,163
  • 7
  • 37
  • 55
  • 2
    Also make sure you're posting the notification on the main thread. – Ondřej Mirtes May 03 '14 at 17:05
  • 1
    Thank you for your comments. Having read the different responses (and taken the comments on board (thanks all)), I feel that due to the way the processes are called I think that notifications is the way to go. However, whilst I believe I can understand how to post the notification, I am trying to understand how I can use the listen within the completion handler in AppDelegate. In effect, I am asking how I can call the completion handler with result, and also remove the observer within that originating code? Thanks in advance. – NeilMortonNet May 03 '14 at 18:22
  • You can find a detailed answer to this here http://stackoverflow.com/questions/2191594/send-and-receive-messages-through-nsnotificationcenter-in-objective-c – Zia May 03 '14 at 19:27
  • 1
    Thanks for that. I have only just spotted your reply, however I have sorted it now. I used blocks with the notification so I was able to call the completion block. I have just had a look at the link and that too shows this. Thank you so much for your help. Much appreciated. – NeilMortonNet May 03 '14 at 20:34
6

You don't wait for an async process to finish. That makes it synchronous. You need to change your thinking.

There are a number of different ways to handle async APIs.

  1. Pass in a completion block
  2. Assign a delegate and define a method that gets called on completion.
  3. Send a notification when the process completes.

There are no doubt more, but the whole idea of an async API is that it returns immediately, and you DON'T wait for it to finish. As the other person said, never, ever, use code like while(!fetchComplete). That is a seriously bad idea.

Duncan C
  • 115,063
  • 19
  • 151
  • 241
  • 1
    Thanks for the comments on this. Have taken on board all of them, including the fact I shouldn't use the while(!fetctComplete). Thank you once again. I have gone with using Notifications. – NeilMortonNet May 03 '14 at 20:35
  • 1
    Sometimes you want a synchronous api but you might only have asynchronous functions provided. When you are invoking methods from a background thread, you might be OK with the method being synchronous. In such cases you may have to force synchronize an asynchronous method. – SayeedHussain Dec 05 '15 at 13:03
  • @paranoidcoder, true, it's sometimes necessary to do such things, but NEVER on the main thread, and usually you'll use something like a lock or a semaphore. – Duncan C Sep 20 '16 at 11:25
1

The manor of which you are trying to solve this problem is a bit atypical.

Generally this is done with either delegation, blocks or notifications.

The idea being you only want to perform some function after the completion of some asynchronous method.

Let's say we have an asyc processor called AyncProcessor.

AyncProcessor * processor = [AyncProcessor sharedProcessor];
processor.delegate = self;
[processor start];

is a fairly common way to do the delegation. The assumption here is that there is a protocol defined with a callback method that is run upon completion.

//protocol method
-(void) processingComplete;

// and at some point in AsyncProcessor
[self.delegate processingComplete] 

more info on delegation here

Blocks can be used as well

AyncProcessor * processor = [AyncProcessor sharedProcessor];
[processor start:^()/{
  ///code in this block will complete after processing has completed
 }];

more info on blocks can be found here

And as already shown you can send a NSNotification.

For your problem delegation is likely a good idea.

Rishil Patel
  • 1,938
  • 3
  • 11
  • 28
madmik3
  • 6,861
  • 3
  • 36
  • 58
  • 1
    Thanks for your comments. I have gone with using Notifications as I felt it lent itself better in the particular scenario, however, I shall also bear in mind your comments for future reference. Thanks again. – NeilMortonNet May 03 '14 at 20:37