0

I get the following error in Xcode 7.1 when making a UI call from C++, through a two-way djinni architecture:

This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.

I am able to solve the problem in Objective-C with the solution given here:

Getting a “This application is modifying the autolayout engine” error?

dispatch_async(dispatch_get_main_queue(), ^{
    // code here
});

My question is, is there a way to somehow accomplish this from within C++ without having to call dispatch_async in Objective-C at every call to the UI? Or is every call from C++ considered a background thread as far as Xcode is concerned?


Posting relevant code with auto-generated source files omitted, full project also available on github:

cpptimer.djinni:

timer = interface +c {
    static create_with_listener(listener: timer_listener): timer;
    start_timer(seconds: i32);
}

timer_listener = interface +j +o {
    timer_ticked(seconds_remaining: i32);
    timer_ended();
}

timer_impl.hpp

#pragma once

#include <boost/asio.hpp>

#include "timer.hpp"
#include "timer_listener.hpp"

namespace cpptimer {

    class TimerImpl : public Timer {

    public:

        TimerImpl(const std::shared_ptr<TimerListener> & listener);

        void StartTimer(int32_t seconds);

    private:

        void TimerTick(const boost::system::error_code& e);

        std::shared_ptr<TimerListener> listener_;

        boost::asio::io_service io_service_;
        boost::asio::deadline_timer timer_;
        int time_remaining_;

    };

}

timer_impl.cpp

#include <boost/bind.hpp>
#include <boost/thread.hpp>

#include "timer_impl.hpp"

namespace cpptimer {

    std::shared_ptr<Timer> Timer::CreateWithListener(const std::shared_ptr<TimerListener> & listener) {
        return std::make_shared<TimerImpl>(listener);
    }

    TimerImpl::TimerImpl(const std::shared_ptr<TimerListener> & listener):
            io_service_(),
            timer_(io_service_, boost::posix_time::seconds(1)) {
        listener_ = listener;
    }

    void TimerImpl::StartTimer(int32_t seconds) {
        time_remaining_ = seconds;
        io_service_.reset();
        timer_.async_wait(boost::bind(&TimerImpl::TimerTick, this, boost::asio::placeholders::error));
        boost::thread th([&] { io_service_.run(); });
    }

    void TimerImpl::TimerTick(const boost::system::error_code& e) {
        if(e != boost::asio::error::operation_aborted) {
            time_remaining_--;
            std:: cout << "C++: TimerTick() with " << std::to_string(time_remaining_) << " seconds remaining.\n";
            if (time_remaining_ > 0) {
                timer_.expires_from_now(boost::posix_time::seconds(1));
                timer_.async_wait(boost::bind(&TimerImpl::TimerTick, this, boost::asio::placeholders::error));
                listener_->TimerTicked(time_remaining_);
            } else {
                listener_->TimerEnded();
            }
        }
    }

}

ViewController.h

#import <UIKit/UIKit.h>

#import "CPPTTimerListener.h"

@interface ViewController : UIViewController<CPPTTimerListener>

@property (nonatomic, strong) IBOutlet UILabel *timerLabel;

@end

ViewController.m

#import "ViewController.h"
#import "CPPTTimer.h"

@interface ViewController () {
    CPPTTimer *_timer;
}

@end

@implementation ViewController

@synthesize timerLabel;

- (void)viewDidLoad {

    [super viewDidLoad];

    // initialize the timer
    _timer = [CPPTTimer createWithListener:self];

    // start a 5 second timer
    [_timer startTimer:5];

}

# pragma mark CPPTTimerListener methods

- (void)timerEnded {
    NSLog(@"Obj-C: timerEnded.");
}

- (void)timerTicked:(int32_t)secondsRemaining {
    NSLog(@"Obj-C: timerTicked with %d seconds remaining.", secondsRemaining);
    // without dispatch_async, background thread warning is thrown
    dispatch_async(dispatch_get_main_queue(), ^{
        timerLabel.text = [NSString stringWithFormat:@"%d", secondsRemaining];
    });
}

@end
Community
  • 1
  • 1
stephenspann
  • 1,823
  • 1
  • 16
  • 31
  • 1
    `dispatch_async` is *not* objective-c; there shouldnt be any reason you can't call it from C++ code if you include the correct GCD headers. additionally being a background thread has noting to do with C++ or any other language. something you are doing is running the code in question on a thread other than main. – Brad Allred Nov 11 '15 at 18:31
  • @BradAllred makes sense. so it seems like the culprit is most likely `boost::thread` which starts a new thread different from main. – stephenspann Nov 11 '15 at 20:10
  • 1
    The primary question is already answered, but note that while dispatch_async isn't ObjC, it is specific to Apple platforms. If your goal is to keep your C++ code platform-agnostic, you could use another Djinni interface to expose a callOnUIThread() method, which you could implement in ObjC with dispatch_async for iOS, then in Java with `new Handler(Looper.getMainLooper()).post()`. Djinni has a sample platform_threads interface which doesn't do this, but does do thread creation, which is a very similar need of per-platform code having to call back to C++ code on a specific thread. – Andrew Nov 13 '15 at 02:21
  • good to know, thank you @atwyman! – stephenspann Nov 16 '15 at 15:10

1 Answers1

2

All access to the UI classes must take place on the main thread. Your boost timer does not run on the main thread.

Thus, it probably makes sense to have your timer fire on the main thread. You can use the standard libdispatch API, even with pure C++ code (does not have to be .mm ObjC++).

Be sure to add #include <dispatch/dispatch.h> to your CPP implementation file.

The following code change will make sure that the timer always runs on the Cocoa main thread.

void TimerImpl::TimerTick(const boost::system::error_code& e) {
    if(e != boost::asio::error::operation_aborted) {
        time_remaining_--;
        std:: cout << "C++: TimerTick() with " << std::to_string(time_remaining_) << " seconds remaining.\n";

        if (time_remaining_ > 0) {
            timer_.expires_from_now(boost::posix_time::seconds(1));
            timer_.async_wait(boost::bind(&TimerImpl::TimerTick, this, boost::asio::placeholders::error));

            auto listener = listener_;
            auto time_remaining = time_remaining_;
            dispatch_async(dispatch_get_main_queue(), ^{
               listener->TimerTicked(time_remaining);
            });
        } else {
            auto listener = listener_;
            dispatch_async(dispatch_get_main_queue(), ^{
                listener->TimerEnded();
            });
        }
    }
}

I assume the rest of that code works. All I did was modify how the callbacks are invoked. Note that we create a copy of the listener_ shared_ptr and the time_remaining_ value. These will be captured (and copied) by the block that gets executed on the main thread.

If you can guarantee that this will not delete before that block executes, then you can implicitly capture this...

dispatch_async(dispatch_get_main_queue(), ^{
   listener_->TimerTicked(time_remaining_);
});

Or, if you enable shared-from-this, you can create a copy of the shared pointer to this and capture it that way...

auto self = shared_from_this();
dispatch_async(dispatch_get_main_queue(), ^{
   self->listener_->TimerTicked(self->time_remaining_);
});

There are a number of ways to do this, but this may be the easiest, and now you ensure all your timers fire through to the Cocoa main thread.

stephenspann
  • 1,823
  • 1
  • 16
  • 31
Jody Hagins
  • 27,943
  • 6
  • 58
  • 87