Как остановить () цикл событий run () основного потока NSApp? (встроен в C ++)
Вот минимальный воспроизводимый пример: если он слишком длинный для чтения, перейдите к следующему разделу с проблемой, а затем при необходимости изучите код.
Минимальный пример:
Допустим, простая командная строка 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 -G Xcode.
$ открыть ocapp.xcodeproj
Проблема:
При использовании [NSApp run]
и [NSApp stop:self]
я не могу остановить цикл событий, поэтому он продолжает работать бесконечно.
Приложение завершило запуск
Остановка
.....
Убито: 9
При использовании CFRunLoopRun()
и CFRunLoopStop(CFRunLoopGetCurrent())
он запускается / останавливается правильно, но applicationDidFinishLaunching
никогда не срабатывает.
Остановка
Прекращение
Вопрос:
Почему это? и как заставить работать обе функции?
Ответы
Проблема не в прилагаемом коде. Ваш существующий вариант
[NSApplication sharedApplication];
[NSApp run];
а также
[NSApp stop:self];
верно.
Виновник твой CMakeLists.txt
. Тот, который вы включили, создает исполняемый двоичный файл. Это нормально для консольного приложения, но это недопустимое приложение для MacOS, состоящее из папки AppName.app и множества других файлов. Поскольку вы используете API AppKit без надлежащего каркаса приложения MacOS, он не работает.
Голый минимум исправить в ваших 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:
кажется, что запрос остановки цикла пользовательского интерфейса обрабатывается только после события пользовательского интерфейса (а не сразу после события основного цикла).
И это действительно так. Если в «голом» исполняемом варианте добавим
- (void) shutdown:(NSNotification *) notif
{
NSLog(@"Stopping");
//CFRunLoopStop(CFRunLoopGetCurrent());
[NSApp stop:self];
[NSApp abortModal]; //this is used for generating a UI NSEvent
}
Мы получаем желаемое поведение, и приложение завершается нормально. Таким образом, ваш «голый» вариант приложения не является правильным приложением для MacOS, следовательно, он не получает событий пользовательского интерфейса (его цикл выполнения работает правильно, несмотря на это).
С другой стороны, наличие соответствующего пакета приложений MacOS с и Info.plist
т. Д. Необходимо для MacOS для настройки окна приложения, значка док-станции и т. Д.
В конечном итоге я рекомендую либо перейти на чистое консольное приложение, если вам вообще не нужен AppKit, либо делать что-то по инструкции. Иначе вы столкнетесь с такими аномалиями.