메인 스레드 NSApp run () 이벤트 루프를 중지 ()하는 방법은 무엇입니까? (C ++에 포함)

Dec 21 2020

다음은 최소한의 재현 가능한 예입니다. 너무 길어서 읽을 수 없으면 문제가있는 다음 섹션으로 이동 한 다음 필요한 경우 코드를 살펴보세요.

최소한의 예 :

간단한 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트리거되지 않습니다.

중지
종단

질문:

왜 이런거야? 두 기능이 작동하도록하는 방법은 무엇입니까?

답변

2 Kamil.S Jan 06 2021 at 03:56

문제는 첨부 된 코드에 없습니다. 기존 변형

[NSApplication sharedApplication];
[NSApp run];

[NSApp stop:self];

맞다.

범인은 CMakeLists.txt. 포함시킨 파일은 실행 가능한 바이너리를 생성합니다. 콘솔 앱에서는 괜찮지 만 AppName.app 폴더와 다른 파일로 구성된 유효한 MacOS 앱은 아닙니다. MacOS 앱의 적절한 스캐 폴드없이 AppKit API를 사용하고 있으므로 작동하지 않습니다.

최소한의 수정 귀하의 CMakeLists.txtIS :

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이 전혀 필요하지 않은 경우 순수한 콘솔 앱을 사용하거나 책에 따라 작업을 수행하는 것이 좋습니다. 그렇지 않으면 그러한 이상 현상이 발생할 것입니다.