Change autorun mechanism from background thread error from C ++

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 can solve the problem in Objective-C with the solution given here:

Getting "This application changes autorun error"?

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

My question is: is there a way to somehow do this from C ++ without having to call dispatch_async in Objective-C every time the user interface is called? Or is every call from C ++ considered a background thread in relation to Xcode?


The posting of the corresponding code with automatically generated source files is omitted, the full project is 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 
0
source share
1 answer

Access to user interface classes must take place in the main thread. Your accelerator does not work in the main thread.

Thus, it probably makes sense for your timer to light up in the main thread. You can use the standard libdispatch API even with pure C ++ code (it is not necessary to be .mm ObjC ++).

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

The following code change ensures that the timer always runs in the main Cocoa 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 this code works. All I did was change the way callbacks are called. Note that we are creating a copy of the values โ€‹โ€‹of listener_ shared_ptr and time_remaining_ . They will be captured (and copied) by a block that will be executed in the main thread.

If you can guarantee that this will not be deleted before this block is executed, 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 commit it this way ...

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

There are several ways to do this, but it might be the easiest, and now you guarantee that all your timers will go through the main Cocoa thread.

+2
source

Source: https://habr.com/ru/post/1243922/


All Articles