Làm thế nào để dừng () vòng lặp sự kiện NSApp run () chính? (nhúng trong C ++)
Đây là một ví dụ có thể tái tạo tối thiểu, nếu quá lâu để đọc, hãy chuyển đến phần tiếp theo với sự cố, sau đó khám phá mã nếu cần.
Ví dụ tối thiểu:
Giả sử một dòng lệnh C ++ đơn giản:
main.cpp
#include <iostream>
#include "Wrapper.h"
int main()
{
Wrapper wrapper;
wrapper.run();
std::cout << "Exiting" << std::endl;
}
Tiêu đề trình bao bọc Objective-C: Wrapper.h
struct OCWrapper;
class Wrapper
{
public:
Wrapper() noexcept;
virtual ~Wrapper() noexcept;
void run();
private:
OCWrapper* impl=nullptr;
};
Và nó thực hiện: 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];
}
Và cuối cùng là phần thú vị, trong 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" )
Dự án có thể được xây dựng bằng các lệnh sau:
$ cmake -G Xcode.
$ mở ocapp.xcodeproj
Vấn đề:
Khi sử dụng [NSApp run]và [NSApp stop:self], tôi không thể dừng vòng lặp sự kiện, vì vậy nó tiếp tục chạy vô thời hạn.
Ứng dụng khởi chạy xong
Dừng
.....
Bị giết: 9
Khi sử dụng CFRunLoopRun()và CFRunLoopStop(CFRunLoopGetCurrent()), nó bắt đầu / dừng một cách chính xác, nhưng applicationDidFinishLaunchingkhông bao giờ được kích hoạt.
Dừng
Chấm dứt
Câu hỏi:
Tại sao thế này? và làm thế nào để cả hai tính năng này hoạt động?
Trả lời
Vấn đề không nằm ở mã đính kèm. Biến thể hiện có của bạn
[NSApplication sharedApplication];
[NSApp run];
và
[NSApp stop:self];
đúng.
Thủ phạm là của bạn CMakeLists.txt. Một trong những bạn đã bao gồm tạo ra một nhị phân thực thi. Điều đó tốt cho một ứng dụng bảng điều khiển nhưng nó không phải là một ứng dụng MacOS hợp lệ bao gồm thư mục AppName.app và nhiều tệp khác. Vì bạn đang sử dụng API AppKit mà không có khung phù hợp của ứng dụng MacOS nên nó không hoạt động.
Một cách khắc phục tối thiểu trần của bạn CMakeLists.txtlà:
add_executable(
${PROJECT_NAME}
MACOSX_BUNDLE
)
Bây giờ bạn sẽ có một mục tiêu Ứng dụng chính xác trong Xcode. Bạn có thể tra cứu thêm các ví dụ nâng cao về CMakeLists.txtứng dụng phù hợp với MacOS trên Internet.
Cập nhật
Vì vậy, tôi đã điều tra nó thêm và kiểm tra quy trình thoát trong
-[NSApplication run]( +[NSApp run]là một từ đồng nghĩa còn lại để tương thích nhưng triển khai thực sự là trong -[NSApplication run]). Chúng ta có thể đặt một điểm ngắt biểu tượng thông qua lldb như sau: b "-[NSApplication run]"đoạn mã quan tâm (cho X86-64) là:
-> 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
Chúng tôi có thể xác minh rằng điểm ngắt mà các mũi tên chỉ được bắn trúng trong biến thể đi kèm chứ không phải trong biến thể thực thi "trần trụi". Sau khi nghiên cứu thêm, tôi đã tìm thấy câu trả lời nàyhttps://stackoverflow.com/a/48064763/5329717đó là rất hữu ích. Trích dẫn chính của @Remko là:
có vẻ như yêu cầu dừng vòng lặp giao diện người dùng chỉ được xử lý sau một sự kiện giao diện người dùng (vì vậy không chỉ sau một sự kiện vòng lặp chính).
Và đó thực sự là trường hợp. Nếu trong biến thể thực thi "trần trụi", chúng tôi thêm
- (void) shutdown:(NSNotification *) notif
{
NSLog(@"Stopping");
//CFRunLoopStop(CFRunLoopGetCurrent());
[NSApp stop:self];
[NSApp abortModal]; //this is used for generating a UI NSEvent
}
Chúng tôi nhận được hành vi mong muốn và ứng dụng kết thúc bình thường. Vì vậy, biến thể ứng dụng "trần trụi" của bạn không phải là ứng dụng MacOS chính xác, do đó nó không nhận được các sự kiện giao diện người dùng (runloop của nó hoạt động chính xác bất kể).
Mặt khác, việc có gói ứng dụng MacOS phù hợp với Info.plistv.v. là cần thiết để MacOS thiết lập cửa sổ ứng dụng, biểu tượng Dock, v.v.
Về lâu dài, tôi khuyên bạn nên sử dụng ứng dụng console thuần túy nếu bạn không cần AppKit hoặc làm những việc theo sách. Nếu không, bạn sẽ gặp phải những điều bất thường như vậy.