【问题标题】:How to make [NsApp run] not block?如何让 [NsApp 运行] 不阻塞?
【发布时间】:2017-12-29 09:20:32
【问题描述】:

我是一个完整的 Cocoa 新手。

现在我的简单 Hello World 应用程序在从 main() 调用 [NsApp run] 后阻塞。

我只需要创建一个窗口而不是阻塞 main()。

我希望我的应用程序表现得像 glfw:

https://github.com/glfw/glfw/blob/master/src/cocoa_window.m#L1022

由于某种原因,它不会在那里阻塞。实际上,您可以删除此行,它仍然可以工作。

我一直在使用 glfw 源来弄清楚它们的不同之处。例如,如果我删除 [NSApp setDelegate:_glfw.ns.delegate];[NsApp run] 会阻塞

但事实并非如此。

根据Apple docs

NSApplication 类在 初始化和事件循环内部——特别是在它的 初始化(或共享)和 run() 方法。

通常,应用程序会在事件循环执行时创建对象 运行或通过从 ni​​b 文件加载对象,因此缺乏访问权限 通常不是问题。但是,如果您确实需要使用 Cocoa 类 在 main() 函数本身内(除了加载 nib 文件或 实例化 NSApplication),你应该创建一个 @autorelease 块来 包含使用类的代码。

我想这就是我需要的,但我不知道如何使用 @autorelease 块。

感谢您的帮助。

【问题讨论】:

  • 而且你甚至不会告诉你为什么要调用 [NsApp run] 以及在哪里调用?
  • 根据Apple docs,应用程序会一直运行,直到您告诉它退出为止。
  • @ElTomato 我已经更新了这个问题。我只需要创建一个窗口而不是阻塞 main()。
  • @Willeke 它不会在 glfw 中阻塞。我想知道他们是怎么做到的。
  • 探索链接代码,看看GLFWApplicationDelegate做了什么。

标签: objective-c macos cocoa


【解决方案1】:

我想通了。

GLFW 实现了自己的事件循环,因此不需要调用 [NSApp run]:

NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
                                        untilDate:[NSDate distantFuture]
                                           inMode:NSDefaultRunLoopMode
                                          dequeue:YES];
    [NSApp sendEvent:event];

【讨论】:

    【解决方案2】:

    我自己一直在研究这个问题,并决定将大家所说的一切整理成一个答案。

    您可能应该注意到 run 阻塞的原因是因为它在功能上类似于其中的 while(1) 循环,它更新窗口并处理事件等。调用 [NSApp stop] 会停止此循环,因此它不会再阻塞(好),但我们也没有更多事件(坏)。

    总的来说,GLFW 的工作如下:

    1. 创建一个 NSApplication,创建一个委托,并将委托设置给应用程序。

    2. 在 NSApp 上致电 runstopstop 在委托的 applicationDidFinishLaunching 内部被调用。

    3. 在每一帧上,从队列中获取下一个事件并将其分派到应用程序中。

    更深入的代码: PS。所有代码(为简单起见已更改)来自GLFW github

    // src/cocoa_init.m
    int _glfwPlatformInit(void)
    {
      ...
      //setup NSApplication, then init and set delegate
      [NSApplication sharedApplication];
      GLFWApplicationDelegate* del = [[GLFWApplicationDelegate alloc] init]; 
      [NSApp setDelegate:del];
    
      //This is a guard to make sure run is only called once
      if (![[NSRunningApplication currentApplication] isFinishedLaunching])
            [NSApp run];
    }
    

    注意 isFinishedLaunching 守卫存在是因为代理中的applicationDidFinishLaunching 仅在第一次run 调用之后才被调用。如果由于某种原因,用户再次调用了glfwInit(),则没有保护run 将被调用,导致它再次阻塞。

    // src/cocoa_init.m
    @interface GLFWApplicationDelegate : NSObject <NSApplicationDelegate>
    @end
    
    @implementation GLFWApplicationDelegate
    
      ...
    
    - (void)applicationDidFinishLaunching:(NSNotification *)notification
    {
        _glfwPlatformPostEmptyEvent();
        [NSApp stop:nil];
    }
    @end
    
    // src/cocoa_window.m
    void _glfwPlatformPollEvents(void)
    {
        @autoreleasepool {
    
        for (;;)
        {
            NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
                                                untilDate:[NSDate distantPast]
                                                   inMode:NSDefaultRunLoopMode
                                                  dequeue:YES];
            if (event == nil)
                break;
    
            [NSApp sendEvent:event];
        }
    
        } // autoreleasepool
    }
    

    注意 glfwPollEvents 从队列中检索下一个事件,然后将事件发送到应用程序以通过响应者传播。它充当documentation about event architecture 中解释的主循环。

    在主事件循环中,应用程序对象 (NSApp) 不断获取事件队列中的下一个(最顶层)事件,将其转换为 NSEvent 对象,并将其分派到最终目的地

    【讨论】:

      【解决方案3】:

      我刚刚经历了同样的场景。 我找到了一些不同的解决方案。

      只需调用[[NSApplication sharedApplication] run];,当您的窗口创建后,调用[[NSApplication sharedApplication] stop:nil];run 调用现在将退出。然后你仍然可以调用 sendEvent 方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-03
        • 2022-01-16
        • 1970-01-01
        • 2021-06-14
        相关资源
        最近更新 更多