Bagaimana cara menghentikan () thread utama NSApp run () event loop? (disematkan di C ++)
Berikut adalah contoh minimal yang dapat direproduksi, jika terlalu panjang untuk dibaca, lanjutkan ke bagian berikutnya dengan masalah tersebut, dan kemudian jelajahi kodenya jika diperlukan.
Contoh minimal:
Misalkan baris perintah C ++ sederhana:
main.cpp
#include <iostream>
#include "Wrapper.h"
int main()
{
Wrapper wrapper;
wrapper.run();
std::cout << "Exiting" << std::endl;
}
Header pembungkus Objective-C: Wrapper.h
struct OCWrapper;
class Wrapper
{
public:
Wrapper() noexcept;
virtual ~Wrapper() noexcept;
void run();
private:
OCWrapper* impl=nullptr;
};
Dan implementasinya: 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];
}
Dan akhirnya bagian yang menarik, di 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" )
Proyek dapat dibangun dengan perintah berikut:
$ cmake -G Xcode.
$ open ocapp.xcodeproj
Masalah:
Saat menggunakan [NSApp run]
dan [NSApp stop:self]
, saya tidak dapat menghentikan perulangan peristiwa, sehingga terus berjalan tanpa batas.
Aplikasi selesai meluncurkan
Berhenti
.....
Dibunuh: 9
Saat menggunakan CFRunLoopRun()
dan CFRunLoopStop(CFRunLoopGetCurrent())
, itu mulai / berhenti dengan benar, tetapi applicationDidFinishLaunching
tidak pernah dipicu.
Berhenti
Mengakhiri
Pertanyaan:
Kenapa ini? dan bagaimana cara agar kedua fitur tersebut berfungsi?
Jawaban
Masalahnya bukan pada kode terlampir. Varian Anda yang sudah ada
[NSApplication sharedApplication];
[NSApp run];
dan
[NSApp stop:self];
benar.
Pelakunya adalah milik Anda CMakeLists.txt
. Yang Anda sertakan membuat biner yang dapat dieksekusi. Itu bagus untuk aplikasi konsol tetapi ini bukan aplikasi MacOS yang valid yang terdiri dari folder AppName.app dan banyak file lainnya. Karena Anda menggunakan AppKit API tanpa perancah yang tepat dari aplikasi MacOS, itu tidak berfungsi.
Sebuah memperbaiki minimal di Anda CMakeLists.txt
yaitu:
add_executable(
${PROJECT_NAME}
MACOSX_BUNDLE
)
Sekarang Anda akan memiliki target Aplikasi yang benar di Xcode. Anda dapat mencari contoh lebih lanjut yang CMakeLists.txt
sesuai untuk aplikasi MacOS di Internet.
Perbarui
Jadi saya menyelidikinya lebih lanjut dan memeriksa rutinitas keluar di
-[NSApplication run]
( +[NSApp run]
adalah sinonim kiri untuk kompatibilitas tetapi implementasi sebenarnya ada -[NSApplication run]
). Kita dapat mengatur breakpoint simbolik melalui lldb seperti ini: b "-[NSApplication run]"
potongan yang menarik (untuk X86-64) adalah:
-> 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
Kami dapat memverifikasi bahwa breakpoint di mana titik panah hanya dicapai dalam varian yang dipaketkan, tetapi tidak pada varian yang dapat dieksekusi "polos". Setelah penelitian lebih lanjut, saya menemukan jawaban inihttps://stackoverflow.com/a/48064763/5329717yang sangat membantu. Kutipan utama oleh @Remko adalah:
tampaknya permintaan penghentian loop UI hanya diproses setelah peristiwa UI (jadi tidak hanya setelah peristiwa loop utama).
Dan memang itulah masalahnya. Jika dalam varian eksekusi "telanjang" kami menambahkan
- (void) shutdown:(NSNotification *) notif
{
NSLog(@"Stopping");
//CFRunLoopStop(CFRunLoopGetCurrent());
[NSApp stop:self];
[NSApp abortModal]; //this is used for generating a UI NSEvent
}
Kami mendapatkan perilaku yang diinginkan dan aplikasi berhenti secara normal. Jadi, varian aplikasi "polos" Anda bukanlah aplikasi MacOS yang benar, sehingga tidak menerima kejadian UI (runloop-nya bekerja dengan benar).
Di sisi lain, memiliki bundel aplikasi MacOS yang tepat dengan Info.plist
dll diperlukan untuk MacOS untuk mengatur jendela aplikasi, ikon Dock, dll.
Dalam jangka panjang saya merekomendasikan baik menggunakan aplikasi konsol murni jika Anda tidak membutuhkan AppKit sama sekali atau melakukan sesuatu berdasarkan buku. Jika tidak, Anda akan mengalami anomali seperti itu.