How to do asynchronous work in C ++ using std :: function callback from ObjC class?

Large image

I have a C ++ library that does asynchronous work including networking. It has a Darwin-specific backend that uses the Grand Central Dispatch C API to delegate work to other threads. Now I would like to use this library from a new iOS app written in Swift through a thin ObjC ++ layer.

I am using Xcode 6.3.2 for OS X 10.10.


In this minimal example, I have recreated the architecture described above. The problem is that the instance of the ObjC class that started the async operation is somehow "broken" when the operation is returned via the std :: function callback. This only happens if std :: function was declared as [&]

instead [=]

. I cannot use the latter, although it is not supported by "real" C ++ code.

The Swift code that calls in ObjC ++ looks like this:

class MyViewController : UIViewController {
    var test = ObjectiveTest()

    override func viewDidAppear(animated: Bool) {
        test.testAsyncWork("some data", withHandler: { (result: Int32) -> Void in
            println("Work result: \(result)")
        })
    }
}

      

The view controller is visible and remains visible if I comment out this code, so it shouldn't kill the ObjectiveTest instance. This is the ObjC ++ layer:

@interface ObjectiveTest () {
    __block Test *test;
    __block int _a;
}
@end

@implementation ObjectiveTest
- (id)init
{
    self = [super init];
    if (self) {
        test = new Test();
        _a = 42;
        if (!test)
            self = nil;
    }
    return self;
}

- (void)deinit
{
    delete test;
}

- (void)testAsyncWork:(NSString *)someData withHandler:(WorkBlock)handler
{
    _a++;
    NSLog(@"_a = %i", _a); // valid
    NSLog(@"handler = %@", handler); // valid
    test->testAsyncWork(someData.UTF8String, [&](int result) {
        _a++;
        NSLog(@"result = %i", result); // Expected: "result = 666" - valid
        NSLog(@"_a = %i", _a); // Expected: "_a = 44" - invalid
        NSLog(@"handler = %@", handler); // invalid, crashes here
    });
}
@end

      

Finally, the C ++ method that does the "job":

void Test::testAsyncWork(std::string someData, std::function<Handler> handler) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        printf("Data received: %s, calling handler on other thread\n", someData.c_str());
        sleep(1); // This is hard work!

        dispatch_async(dispatch_get_main_queue(), ^{
            printf("Hello, main thread here, calling back now!\n");
            handler(666); // into ObjectiveTest
        });
    });
}

      

Observations

As I said, it doesn't break if I use [=]

std :: for the function.

The Ivar _a

of the ObjectiveTest appears to have random values ​​in the callback, despite being declared with __block

. The program crashes when trying to access (print / call) a block handler

that refers to Swift code. Xcode displays it like this:

handler WorkBlock & error: summary string parsing error 0xbffa50cc
&handler    __block_literal_generic *   0x7c000000  0x0ae08500
__isa   void *  NULL    0x00000000
__flags int 0   0
__reserved  int 2080876705  2080876705
__FuncPtr   void (*)(int)   0x7c000000  0x7c000000
__descriptor    __block_descriptor *    0x7c085b10  0x7c085b10

      

This gave me the impression that the ObjectiveTest instance crashes somewhere in this process, but since it is stored in MyViewController, I can't see how this can happen. Perhaps I am missing something else?

+3


source to share


1 answer


[&]

captures variables by reference. The captured references, if the original variables expire before the task completes, will dangle.

Since asynchronous call is probably meant to complete asynchronously 1 you basically guarantee dangling links.

When making an asynchronous call, you almost always want to commit by value. You probably even want to list what you are committing explicitly so you can understand what dependencies you are introducing. (multi-threaded code is complex enough with no hidden / implicit dependencies)



The only valid use of a capture [&]

is when you instantiate a visitor to navigate to a function that will use the lambda and then discard it and all copies of it before returning it. Anything other than that you have to capture by value or very carefully choose what you do by referencing and proving that life-related problems are covered.


1 An example of an async method that might be required [&]

is an asynchronous call to "run on UI pump thread" in which you want the worker thread to stop until the result is reached back from the UI thread. There you would [&]

block the returned std::future

(or equivalent) before leaving the current area.

+3


source







All Articles