메인 스레드 NSApp run () 이벤트 루프를 중지 ()하는 방법은 무엇입니까? (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
. 포함시킨 파일은 실행 가능한 바이너리를 생성합니다. 콘솔 앱에서는 괜찮지 만 AppName.app 폴더와 다른 파일로 구성된 유효한 MacOS 앱은 아닙니다. MacOS 앱의 적절한 스캐 폴드없이 AppKit API를 사용하고 있으므로 작동하지 않습니다.
최소한의 수정 귀하의 CMakeLists.txt
IS :
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의 핵심 인용문은 다음과 같습니다.
UI 루프 중지 요청은 UI 이벤트 후에 만 처리되는 것 같습니다 (메인 루프 이벤트 후에 만 처리되지 않음).
그리고 그것이 사실입니다. "네이 키드"실행 가능 변종에 추가하면
- (void) shutdown:(NSNotification *) notif
{
NSLog(@"Stopping");
//CFRunLoopStop(CFRunLoopGetCurrent());
[NSApp stop:self];
[NSApp abortModal]; //this is used for generating a UI NSEvent
}
원하는 동작을 얻고 앱이 정상적으로 종료됩니다. 따라서 "네이 키드"앱 변형은 올바른 MacOS 앱이 아니므로 UI 이벤트를 수신하지 않습니다 (runloop는 상관없이 올바르게 작동합니다).
반면 Info.plist
에 MacOS가 앱 창, Dock 아이콘 등을 설정하려면 적절한 MacOS 앱 번들 이 필요합니다.
장기적으로는 AppKit이 전혀 필요하지 않은 경우 순수한 콘솔 앱을 사용하거나 책에 따라 작업을 수행하는 것이 좋습니다. 그렇지 않으면 그러한 이상 현상이 발생할 것입니다.