Adr*_*ire 4 objective-c++ nsapplication
这是一个最小的可重现示例,如果太长而无法阅读,请转到包含问题的下一部分,然后根据需要探索代码。
假设一个简单的 C++ 命令行:
主程序
#include <iostream>
#include "Wrapper.h"
int main()
{
Wrapper wrapper;
wrapper.run();
std::cout << "Exiting" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
Objective-C 包装器头文件:Wrapper.h
struct OCWrapper;
class Wrapper
{
public:
Wrapper() noexcept;
virtual ~Wrapper() noexcept;
void run();
private:
OCWrapper* impl=nullptr;
};
Run Code Online (Sandbox Code Playgroud)
及其实现: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];
}
Run Code Online (Sandbox Code Playgroud)
最后是 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
Run Code Online (Sandbox Code Playgroud)
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" )
Run Code Online (Sandbox Code Playgroud)
可以使用以下命令构建该项目:
$ cmake -G Xcode .
$ 打开 ocapp.xcodeproj
当使用[NSApp run]and时[NSApp stop:self],我无法停止事件循环,因此它会无限期地运行。
应用程序完成启动
停止
.....
杀死:9
当使用CFRunLoopRun()和时CFRunLoopStop(CFRunLoopGetCurrent()),它可以正确启动/停止,但applicationDidFinishLaunching永远不会被触发。
停止
终止
为什么是这样?以及如何让这两个功能发挥作用?
问题不在所附代码中。您现有的变体
[NSApplication sharedApplication];
[NSApp run];
Run Code Online (Sandbox Code Playgroud)
和
[NSApp stop:self];
Run Code Online (Sandbox Code Playgroud)
是正确的。
罪魁祸首是你的CMakeLists.txt。您包含的创建了一个可执行二进制文件。这对于控制台应用程序来说很好,但它不是由 AppName.app 文件夹和一堆其他文件组成的有效 MacOS 应用程序。由于您使用的 AppKit API 没有适当的 MacOS 应用程序支架,因此它无法工作。
您的最低限度修复CMakeLists.txt是:
add_executable(
${PROJECT_NAME}
MACOSX_BUNDLE
)
Run Code Online (Sandbox Code Playgroud)
现在您将在 Xcode 中拥有正确的应用程序目标。CMakeLists.txt您可以在 Internet 上查找适合 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
Run Code Online (Sandbox Code Playgroud)
我们可以验证箭头指向的断点仅在捆绑变体中被击中,而在“裸”可执行变体中则不然。经过进一步研究,我发现这个答案/sf/answers/3364533441/这是非常有帮助的。@Remko 的关键引用是:
似乎 UI 循环停止请求仅在 UI 事件之后处理(因此不仅仅是在主循环事件之后)。
事实确实如此。如果在“裸”可执行变体中我们添加
- (void) shutdown:(NSNotification *) notif
{
NSLog(@"Stopping");
//CFRunLoopStop(CFRunLoopGetCurrent());
[NSApp stop:self];
[NSApp abortModal]; //this is used for generating a UI NSEvent
}
Run Code Online (Sandbox Code Playgroud)
我们得到了期望的行为并且应用程序正常终止。因此,您的“裸”应用程序变体不是正确的 MacOS 应用程序,因此它不会接收 UI 事件(无论如何,其运行循环都能正常工作)。
另一方面,Info.plistMacOS 需要使用适当的 MacOS 应用程序捆绑包来设置应用程序窗口、Dock 图标等。
从长远来看,如果您根本不需要 AppKit,我确实建议您选择纯控制台应用程序,或者按书本操作。否则你会遇到这样的异常情况。
| 归档时间: |
|
| 查看次数: |
1003 次 |
| 最近记录: |