【问题标题】:Modifying the autolayout engine from a background thread error, from C++从后台线程错误修改自动布局引擎,来自 C++
【发布时间】:2015-11-11 17:21:07
【问题描述】:

当通过双向 djinni 架构从 C++ 进行 UI 调用时,我在 Xcode 7.1 中收到以下错误:

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 中的问题:

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

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

我的问题是,有没有一种方法可以在 C++ 中以某种方式完成此任务,而不必在每次调用 UI 时都在 Objective-C 中调用 dispatch_async?还是就 Xcode 而言,来自 C++ 的每个调用都被视为后台线程?


发布相关代码,省略自动生成的源文件,完整项目也可在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

【问题讨论】:

  • dispatch_async不是objective-c;如果您包含正确的 GCD 标头,则不应有任何理由无法从 C++ 代码中调用它。另外,作为后台线程与 C++ 或任何其他语言有关。您正在做的事情是在 main 以外的线程上运行有问题的代码。
  • @BradAllred 是有道理的。所以看起来罪魁祸首很可能是boost::thread,它启动了一个不同于主线程的新线程。
  • 主要问题已经得到解答,但请注意,虽然 dispatch_async 不是 ObjC,但它是特定于 Apple 平台的。如果您的目标是让您的 C++ 代码与平台无关,您可以使用另一个 Djinni 接口来公开 callOnUIThread() 方法,您可以在 ObjC 中使用 dispatch_async for iOS 来实现该方法,然后在 Java 中使用 new Handler(Looper.getMainLooper()).post()。 Djinni 有一个示例 platform_threads 接口,它不这样做,但会创建线程,这与每个平台代码的需求非常相似,必须在特定线程上回调 C++ 代码。
  • 很高兴知道,谢谢@atwyman!

标签: c++ objective-c xcode boost-asio djinni


【解决方案1】:

对 UI 类的所有访问必须在主线程上进行。您的加速计时器不在主线程上运行。

因此,让你的计时器在主线程上触发可能是有意义的。您可以使用标准的 libdispatch API,即使是纯 C++ 代码(不必是 .mm ObjC++)。

请务必将#include &lt;dispatch/dispatch.h&gt; 添加到您的 CPP 实施文件中。

以下代码更改将确保计时器始终在 Cocoa 主线程上运行。

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_ptrtime_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 主线程。

【讨论】:

    猜你喜欢
    • 2016-04-27
    • 2017-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-20
    • 2018-01-23
    相关资源
    最近更新 更多