цель c — модифицировать механизм автоматического размещения из ошибки фонового потока, из переполнения стека

Я получаю следующую ошибку в Xcode 7.1 при вызове пользовательского интерфейса из C ++ через двухстороннюю архитектуру djinni:

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.

Я могу решить проблему в Objective-C с помощью решения, приведенного здесь:

Появляется ошибка «Это приложение модифицирует механизм автоматического определения местоположения»?

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

Мой вопрос заключается в том, есть ли способ как-то сделать это из C ++ без необходимости вызывать dispatch_async в Objective-C при каждом вызове пользовательского интерфейса? Или каждый вызов из C ++ считается фоновым потоком для Xcode?


Размещение соответствующего кода с автоматически сгенерированными исходными файлами, полный проект также доступен на 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

Решение

Весь доступ к классам пользовательского интерфейса должен проходить по основному потоку. Ваш таймер повышения не работает в главном потоке.

Таким образом, возможно, имеет смысл запустить таймер в основном потоке. Вы можете использовать стандартный API libdispatch, даже с чистым кодом C ++ (необязательно должен быть .mm ObjC ++).

Обязательно добавлю #include <dispatch/dispatch.h> в ваш файл реализации CPP.

Следующее изменение кода гарантирует, что таймер всегда работает в главном потоке Какао.

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();
});
}
}
}

Я предполагаю, что остальная часть этого кода работает. Все, что я сделал, это изменил способ обратного вызова. Обратите внимание, что мы создаем копию listener_ shared_ptr и time_remaining_ значение. Они будут захвачены (и скопированы) блоком, который выполняется в главном потоке.

Если вы можете гарантировать, что this не будет удален до выполнения этого блока, тогда вы можете неявно захват this

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

Или, если вы включите shared-from-this, вы можете создать копию общего указателя на this и захватить это таким образом …

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

Есть несколько способов сделать это, но это может быть самым простым, и теперь вы гарантируете, что все ваши таймеры включаются в основной поток Cocoa.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]