Wie stoppe ich die NSApp run () -Ereignisschleife des Hauptthreads ()? (eingebettet in C ++)
Hier ist ein minimal reproduzierbares Beispiel. Wenn das Lesen zu lang ist, fahren Sie mit dem nächsten Abschnitt mit dem Problem fort und untersuchen Sie den Code bei Bedarf.
Minimales Beispiel:
Nehmen wir eine einfache C ++ - Befehlszeile an:
main.cpp
#include <iostream>
#include "Wrapper.h"
int main()
{
Wrapper wrapper;
wrapper.run();
std::cout << "Exiting" << std::endl;
}
Der Objective-C-Wrapper-Header: Wrapper.h
struct OCWrapper;
class Wrapper
{
public:
Wrapper() noexcept;
virtual ~Wrapper() noexcept;
void run();
private:
OCWrapper* impl=nullptr;
};
Und es Implementierung: 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];
}
Und schließlich der interessante Teil in 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" )
Das Projekt kann mit folgenden Befehlen erstellt werden:
$ cmake -G Xcode.
$ open ocapp.xcodeproj
Das Problem:
Bei Verwendung von [NSApp run]
und [NSApp stop:self]
kann ich die Ereignisschleife nicht stoppen, sodass sie unbegrenzt weiter ausgeführt wird.
Anwendung beendet Starten
Stoppen
.....
Getötet: 9
Bei Verwendung von CFRunLoopRun()
und CFRunLoopStop(CFRunLoopGetCurrent())
wird es korrekt gestartet / gestoppt, jedoch applicationDidFinishLaunching
nie ausgelöst.
Stoppen
Terminating
Die Frage:
Warum ist das? und wie funktionieren beide Funktionen?
Antworten
Das Problem liegt nicht im angehängten Code. Ihre bestehende Variante
[NSApplication sharedApplication];
[NSApp run];
und
[NSApp stop:self];
ist richtig.
Der Schuldige ist dein CMakeLists.txt
. Die von Ihnen eingeschlossene Datei erstellt eine ausführbare Binärdatei. Das ist in Ordnung für eine Konsolen-App, aber keine gültige MacOS-App, die aus dem Ordner AppName.app und einer Reihe anderer Dateien besteht. Da Sie die AppKit-API ohne das richtige Gerüst einer MacOS-App verwenden, funktioniert dies nicht.
Eine minimale Lösung in Ihrem CMakeLists.txt
ist:
add_executable(
${PROJECT_NAME}
MACOSX_BUNDLE
)
Jetzt haben Sie ein korrektes App-Ziel in Xcode. Weitere Beispiele für CMakeLists.txt
für MacOS geeignete Apps finden Sie im Internet.
Update
Also habe ich es weiter untersucht und die Exit-Routine in überprüft
-[NSApplication run]
( +[NSApp run]
ist ein Synonym für Kompatibilität, aber die eigentliche Implementierung ist in -[NSApplication run]
). Wir können einen symbolischen Haltepunkt durch lldb wie folgt setzen: b "-[NSApplication run]"
Das Snippet von Interesse (für X86-64) ist:
-> 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
Wir können überprüfen, ob ein Haltepunkt, an dem Pfeilspitzen getroffen werden, nur in der gebündelten Variante, nicht jedoch in der "nackten" ausführbaren Variante getroffen wird. Nach weiteren Recherchen fand ich diese Antworthttps://stackoverflow.com/a/48064763/5329717das ist sehr hilfreich. Das Schlüsselzitat von @Remko lautet:
Es scheint, dass die Anforderung zum Stoppen der UI-Schleife nur nach einem UI-Ereignis verarbeitet wird (also nicht nur nach einem Hauptschleifenereignis).
Und das ist tatsächlich der Fall. Wenn in der "nackten" ausführbaren Variante wir hinzufügen
- (void) shutdown:(NSNotification *) notif
{
NSLog(@"Stopping");
//CFRunLoopStop(CFRunLoopGetCurrent());
[NSApp stop:self];
[NSApp abortModal]; //this is used for generating a UI NSEvent
}
Wir bekommen das gewünschte Verhalten und die App wird normal beendet. Ihre "nackte" App-Variante ist also keine korrekte MacOS-App und empfängt daher keine UI-Ereignisse (der Runloop funktioniert trotzdem korrekt).
Auf der anderen Seite ist ein korrektes MacOS-App-Bundle mit Info.plist
usw. erforderlich, damit MacOS ein App-Fenster, ein Dock-Symbol usw. einrichten kann.
Auf lange Sicht empfehle ich, entweder eine reine Konsolen-App zu wählen, wenn Sie AppKit überhaupt nicht benötigen, oder Dinge nach dem Buch zu tun. Andernfalls werden Sie auf solche Anomalien stoßen.