iosclangwarningsgrand-central-dispatchlibdispatch

local dispatch_once value unsafe (transient memory) triggers warning


So on my spare time I'm working on an HTTP request 'engine'. What I'm trying to build is an 'engine' that generate a request / parse response to a generic object for an iphone app.

And most of all, it should _allways_ callback the UI.

Whatever happened (NSURLRequest timeout / parsing error / NSExcept raised) (maybe SIG as well some day ?)

So I created something gory using blocks and dispatch_once. It works but I'm having this Clang warning. I mean my requests works, and in case of an exception the UI is called once apparently.

Call to 'dispatch_once' uses the local variable 'once' for the predicate value. Using such transient memory for the predicate is potentially dangerous

This is the heart of the problem

// invoke a bloc in a proper thread with a proper autorelease / trycatch / logs
static inline void api_dispatch_concurrent(EngineOnceCallback block, SEL caller, EngineCallback callback) {
    dispatch_async(APIengineQueue(), ^{  // serial dispatch_queue
    @autoreleasepool {    

            dispatch_once_t once = 0;

            @try {
                // block may callback using 'once'
                (block) ? block(once) : NULL;
            }

            @catch (NSException *exception) {
            [[ACLogs sharedLogs] logException:exception caller:caller
                                        class:[ACRetroscopeEngine class]];

            APILog(@"NSException invoking API block:%@", exception);
            dispatch_once(&once, ^{
                APILog(@"Perform callback as recover from exception");
                ACResponse *defaultResponse = [ACResponse responseWithException:exception];

                try_catch(block_safe_invoke_main_thread(callback, defaultResponse));
            });
        }
        @finally {

        }
    }
});
}

Some macros used there :

#define block_safe_invoke(block, ...) (block != NULL) ? block(__VA_ARGS__) : NULL;

#define block_safe_invoke_main_thread(block, ...)               \
if ([NSThread isMainThread]) {                                  \
    block_safe_invoke(block, __VA_ARGS__)                       \
} else {                                                        \
    dispatch_sync(dispatch_get_main_queue(), ^{                 \
        block_safe_invoke(block, __VA_ARGS__)                   \
    });                                                         \
}                                                               \

Thanks in advance :p


Solution

  • Your 'once' variable should be static for a dispatch_once block - i.e.

      static dispatch_once_t once = 0;
    

    That will ensure your code only performs the recovery callback once, ever. In practice, you could get away with just a static boolean - you don't actually need a dispatch_once block because you're dispatching to a serial queue (according to your comment). However, it is good practice.

    As your code currently stands, your recovery callback will be performed once per exception, which means if you dispatch two blocks to APIEngineQueue, both blocks can in theory perform the callback - as each block has its own local 'once' variable. In other words at the moment your dispatch_once is having no effect at all, it's the same as writing:

        @catch (NSException *exception) {
            [[ACLogs sharedLogs] logException:exception caller:caller
                                        class:[ACRetroscopeEngine class]];
    
            APILog(@"NSException invoking API block:%@", exception);
            APILog(@"Perform callback as recover from exception");
            ACResponse *defaultResponse = [ACResponse responseWithException:exception];
            try_catch(block_safe_invoke_main_thread(callback, defaultResponse));
    

    The block_safe_invoke_main_thread is a bit convoluted too, as you know you're not on the main thread, so why test for it?