メインスレッドNSApprun()イベントループをstop()する方法は?(C ++に埋め込まれています)

Dec 21 2020

これは最小限の再現可能な例です。読むには長すぎる場合は、問題のある次のセクションに進み、必要に応じてコードを調べてください。

最小限の例:

単純なC ++コマンドラインを考えてみましょう。

main.cpp

#include <iostream>
#include "Wrapper.h"
int main()
{
    Wrapper wrapper;
    wrapper.run();
    std::cout << "Exiting" << std::endl;
}

Objective-Cラッパーヘッダー:Wrapper.h

struct OCWrapper;
class Wrapper
{
public:
    Wrapper() noexcept;
    virtual ~Wrapper() noexcept;
    void run();
private:
    OCWrapper* impl=nullptr;
};

そしてそれの実装:Wrapper.mm

#import "Wrapper.h"
#import "MyOCApp.h"

struct OCWrapper
{
    MyOCApp* wrapped=nullptr;
};

Wrapper::Wrapper() noexcept: impl(new OCWrapper)
{
    impl->wrapped = [[ MyOCApp alloc] init];
}

Wrapper::~Wrapper() noexcept
{
    [impl->wrapped release];
    delete impl;
}

void Wrapper::run()
{
    [impl->wrapped run];
}

そして最後に、Objective-CのMyOCApp.hの興味深い部分は次のとおりです。

#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>

@interface MyOCApp: NSObject
@end

@implementation MyOCApp
- (id)init 
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                selector:@selector(applicationDidFinishLaunching:)
                name:NSApplicationDidFinishLaunchingNotification object:nil];
    
    return self;
}

- (void)run
{
    [self performSelector:@selector(shutdown:) withObject:nil afterDelay: 2];
    //CFRunLoopRun();
    
    [NSApplication sharedApplication];
    [NSApp run];
}

- (void) shutdown:(NSNotification *) notif
{
    NSLog(@"Stopping");
    //CFRunLoopStop(CFRunLoopGetCurrent());
    [NSApp stop:self];
}

- (void) applicationDidFinishLaunching:(NSNotification *) notif
{
    NSLog(@"Application ready");
}
@end

CMakeLists.txt

cmake_minimum_required (VERSION 3.10.0)
cmake_policy( SET CMP0076 NEW)

set(CMAKE_CXX_STANDARD 17)

project(ocapp)

add_executable(${PROJECT_NAME}) find_library(APP_KIT AppKit) find_library(CORE_FOUNDATION CoreFoundation) target_link_libraries( ${PROJECT_NAME} ${APP_KIT} ${CORE_FOUNDATION} )

target_sources( ${PROJECT_NAME} PRIVATE "main.cpp" "Wrapper.mm" PUBLIC "Wrapper.h" "MyOCApp.h" )

プロジェクトは、次のコマンドでビルドできます。

$ cmake -GXcode。
$ open ocapp.xcodeproj

問題:

とを使用する[NSApp run][NSApp stop:self]、イベントループを停止できないため、無期限に実行され続けます。

アプリケーションの起動が終了しました
停止中
.....
強制終了:9

とを使用するCFRunLoopRun()CFRunLoopStop(CFRunLoopGetCurrent())、正しく開始/停止しますが、applicationDidFinishLaunchingトリガーされることはありません。


終了の停止

質問:

どうしてこれなの?両方の機能を機能させる方法は?

回答

2 Kamil.S Jan 06 2021 at 03:56

問題は添付のコードにはありません。既存のバリアント

[NSApplication sharedApplication];
[NSApp run];

そして

[NSApp stop:self];

正しい。

犯人はあなたCMakeLists.txtです。含めたものは実行可能バイナリを作成します。これはコンソールアプリには問題ありませんが、AppName.appフォルダーと他のファイルの束で構成される有効なMacOSアプリではありません。MacOSアプリの適切なスキャフォールドなしでAppKitAPIを使用しているため、機能しません。

最低限の修正、あなたの中CMakeLists.txtです。

add_executable(
    ${PROJECT_NAME}
    MACOSX_BUNDLE
)

これで、Xcodeに正しいアプリターゲットが作成されます。CMakeLists.txtインターネット上のMacOSアプリに適したより高度な例を調べることができます。

更新
それで私はそれをさらに調査し、で終了ルーチンを調べました
-[NSApplication run]+[NSApp run]互換性のために残された同義語ですが、実際の実装はにあります-[NSApplication run])。lldbを介して、次のようにシンボリックブレークポイントを設定できます。対象b "-[NSApplication run]"のスニペット(X86-64の場合)は次のとおりです。

->  0x7fff4f5f96ff <+1074>: add    rsp, 0x98
    0x7fff4f5f9706 <+1081>: pop    rbx
    0x7fff4f5f9707 <+1082>: pop    r12
    0x7fff4f5f9709 <+1084>: pop    r13
    0x7fff4f5f970b <+1086>: pop    r14
    0x7fff4f5f970d <+1088>: pop    r15
    0x7fff4f5f970f <+1090>: pop    rbp
    0x7fff4f5f9710 <+1091>: ret  

矢印が指すブレークポイントは、バンドルされたバリアントでのみヒットし、「ネイキッド」実行可能バリアントではヒットしないことを確認できます。さらに調査した後、私はこの答えを見つけましたhttps://stackoverflow.com/a/48064763/5329717これはとても役に立ちます。@Remkoによる重要な引用は次のとおりです。

UIループ停止要求は、UIイベントの後でのみ処理されるようです(メインループイベントの直後ではありません)。

そしてそれは確かに事実です。「裸の」実行可能バリアントに追加する場合

- (void) shutdown:(NSNotification *) notif
{
    NSLog(@"Stopping");
    //CFRunLoopStop(CFRunLoopGetCurrent());
    [NSApp stop:self];
    [NSApp abortModal]; //this is used for generating a UI NSEvent
}

目的の動作が得られ、アプリは正常に終了します。したがって、「裸の」アプリバリアントは正しいMacOSアプリではないため、UIイベントを受信しません(そのrunloopは関係なく正しく機能します)。

一方、Info.plistMacOSがアプリウィンドウやDockアイコンなどを設定するには、適切なMacOSアプリバンドルなどが必要です。

長期的には、AppKitがまったく必要ない場合は、純粋なコンソールアプリを使用するか、本で何かを行うことをお勧めします。そうでなければ、あなたはそのような異常に遭遇するでしょう。