Jak zatrzymać () główny wątek pętli zdarzeń NSApp run ()? (osadzone w C ++)
Oto minimalny możliwy do odtworzenia przykład, jeśli jest zbyt długi, aby go przeczytać, przejdź do następnej sekcji z problemem, a następnie zbadaj kod, jeśli to konieczne.
Minimalny przykład:
Załóżmy, że prosta linia poleceń C ++:
main.cpp
#include <iostream>
#include "Wrapper.h"
int main()
{
Wrapper wrapper;
wrapper.run();
std::cout << "Exiting" << std::endl;
}
Nagłówek opakowania Objective-C: Wrapper.h
struct OCWrapper;
class Wrapper
{
public:
Wrapper() noexcept;
virtual ~Wrapper() noexcept;
void run();
private:
OCWrapper* impl=nullptr;
};
I to implementacja: 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];
}
I wreszcie interesująca część, w 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" )
Projekt można zbudować za pomocą następujących poleceń:
$ cmake -G Xcode.
$ open ocapp.xcodeproj
Problem:
Podczas używania [NSApp run]
i [NSApp stop:self]
nie mogę zatrzymać pętli zdarzeń, więc działa ona w nieskończoność.
Zakończono uruchamianie aplikacji
Zatrzymywanie
.....
Zabity: 9
Podczas używania CFRunLoopRun()
i CFRunLoopStop(CFRunLoopGetCurrent())
uruchamia / zatrzymuje się poprawnie, ale applicationDidFinishLaunching
nigdy nie jest wyzwalany.
Zatrzymywanie
Kończenie
Pytanie:
Dlaczego to? i jak sprawić, by obie te funkcje działały?
Odpowiedzi
Problemu nie ma w załączonym kodzie. Twój istniejący wariant
[NSApplication sharedApplication];
[NSApp run];
i
[NSApp stop:self];
jest poprawne.
Winowajcą jest twój CMakeLists.txt
. Ten, który dołączyłeś, tworzy wykonywalny plik binarny. To dobrze dla aplikacji konsolowej, ale nie jest to poprawna aplikacja MacOS składająca się z folderu AppName.app i wielu innych plików. Ponieważ używasz AppKit API bez odpowiedniego szkieletu aplikacji MacOS, nie działa.
Absolutna minimalna poprawka w Twoim CMakeLists.txt
to:
add_executable(
${PROJECT_NAME}
MACOSX_BUNDLE
)
Teraz będziesz mieć poprawny cel aplikacji w Xcode. W CMakeLists.txt
Internecie można znaleźć bardziej zaawansowane przykłady odpowiednich aplikacji dla systemu MacOS.
Aktualizacja
Więc zbadałem to dokładniej i sprawdziłem procedurę wyjścia w
-[NSApplication run]
( +[NSApp run]
jest to synonim pozostawiony dla kompatybilności, ale prawdziwa implementacja jest w -[NSApplication run]
). Możemy ustawić symboliczny punkt przerwania przez lldb w następujący sposób: b "-[NSApplication run]"
interesujący fragment (dla X86-64) to:
-> 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
Możemy zweryfikować, że punkt przerwania, w którym punkty strzałek są trafiane tylko w wariancie pakietowym, ale nie w wariancie „nagiego” pliku wykonywalnego. Po dalszych badaniach znalazłem tę odpowiedźhttps://stackoverflow.com/a/48064763/5329717co jest bardzo pomocne. Kluczowym cytatem @Remko jest:
wydaje się, że żądanie zatrzymania pętli interfejsu użytkownika jest przetwarzane dopiero po zdarzeniu interfejsu użytkownika (a więc nie tylko po zdarzeniu pętli głównej).
I rzeczywiście tak jest. Jeśli w wariancie „nagiego” pliku wykonywalnego dodajemy
- (void) shutdown:(NSNotification *) notif
{
NSLog(@"Stopping");
//CFRunLoopStop(CFRunLoopGetCurrent());
[NSApp stop:self];
[NSApp abortModal]; //this is used for generating a UI NSEvent
}
Otrzymujemy pożądane zachowanie, a aplikacja kończy się normalnie. Tak więc wariant „nagiej” aplikacji nie jest poprawną aplikacją na MacOS, dlatego nie odbiera zdarzeń interfejsu użytkownika (niezależnie od tego, jego runloop działa poprawnie).
Z drugiej strony posiadanie odpowiedniego pakietu aplikacji MacOS z Info.plist
itp. Jest konieczne, aby MacOS mógł skonfigurować okno aplikacji, ikonę Dock itp.
Na dłuższą metę polecam albo wybranie czystej aplikacji konsolowej, jeśli w ogóle nie potrzebujesz AppKit, albo robienie rzeczy według książki. W przeciwnym razie napotkasz takie anomalie.