objective-c - 为什么我的应用程序会失败 "unhide"(显示)

标签 objective-c macos cocoa

我正在调查 Mac OS X 10.8 上的一个问题,但我已不知所措。我不知道接下来要做什么。

该应用程序是 32 位的,其中包含一些 Carbon 调用。

问题来了:当我右键单击 Dock 中的应用程序图标时,选择菜单项“隐藏”,然后,在应用程序隐藏后,我从 Dock 中选择“显示”菜单项,出现问题:主文档窗口不会出现(调色板和菜单会出现)。

此时,即使调色板变得可见,“显示”菜单项也不会更改为“隐藏”。

当我从应用程序停靠菜单中选择“显示”时,我希望主文档窗口变得可见。就像其他 Mac 应用程序一样。

当它失败时,如果我在触控板上使用 App Exposé 手势来显示文档窗口并选择主文档窗口,我可以使主文档窗口再次可见。

如果我从终端或从 Xcode 启动应用程序,它工作正常。文档窗口显示,我的应用程序的停靠菜单项按预期更改为“隐藏”。我通过导航到 *.app 的父目录并键入 ./MyApp.app/Contents/MacOS/MyApp 从终端启动应用程序.

当我通过双击 Finder 中的应用程序图标启动时,它失败了。

当应用程序从终端和 Xcode 启动时,我的应用程序委托(delegate)取消隐藏功能的日志消息会出现,但从 Finder 启动时不会出现。

– applicationWillUnhide:
– applicationDidUnhide:

我在 Console.app 中查看了抛出的任何异常(或任何其他消息)。没有了。

更新 :

为了尝试调试这个,我从 Finder 启动,并使用 Xcode 附加到进程。

我怀疑抛出了一个异常,当我使用 Xcode 的“异常断点”测试它并在 objc_exception_throw 上放置一个断点(以防万一)时,当我隐藏或“显示”应用程序时,它不会中断。

然后我认为我需要证明 NSApplicationWillUnhideNotificationNSApplicationDidUnhideNotification正在被发送出去。它们是当我从 Xcode 或从终端启动时,但如果我从 Finder 启动,它们就不是。

在将 Xcode 附加到应用程序后,我通过“添加符号断点”放置一个断点来验证这一点:
-[NSNotificationCenter postNotificationName:object:userInfo] 

然后,我添加了一个调试器命令:“po * (id*) ($esp+12)”来打印出该选择器的第一个参数(通知名称)。

我在 answer posted here, 中找到的在 StackOverflow 中。

使用它,我可以看到在我选择“显示”菜单项后发布的通知。当我从 Xcode/终端启动时,我看到发布了以下通知:
NSApplicationWillUpdateNotification, NSWindowDidUpdateNotification, NSApplicationDidUpdateNotification, ** NSApplicationWillUnhideNotification **, ..., ** NSApplicationDidUnhideNotification **, ..., NSApplicationWillBecomeActiveNotification, ...
NSApplicationWillUnhideNotification在这种情况下发布。

当我从 Finder 启动时,我看到发布了以下通知:
NSApplicationWillUpdateNotification, NSWindowDidUpdateNotification, NSApplicationDidUpdateNotification, NSApplicationWillBecomeActiveNotification, ...

它不发送 NSApplicationWillUnhideNotification .此外,当我从 Xcode 启动的版本中选择“显示”时,我看到 -[NSApplication _doUnhideWithoutActivation]在回溯中。当我附加到 Finder 启动的版本时,为该函数设置断点不会在我选择“显示”时导致中断。

然后,我心里想,也许应用程序认为它没有隐藏。

我有一个空闲事件处理程序,所以从那里我打印出 [[NSApplication sharedApplicaton] isHidden] 的值而我隐藏和“显示”应用程序。

对于问题情况,当应用程序没有隐藏时,打印出NOisHidden .当应用程序隐藏时,它会打印出 YESisHidden .当我从停靠栏菜单中选择“显示”时,它会继续打印 NOisHidden .它知道它是隐藏的,但部分应用程序已被激活:NSPanelsNSMenuBar出现。

进入应用程序Exposé模式可以看到文档窗口,点击文档窗口会出现该窗口,但是dock菜单项还是“显示”和isHidden还在YES .

取消隐藏机制适用于示例应用程序,所以我很确定我们的代码正在做一些事情来关闭它。

我想知道从终端启动的应用程序与从 Finder 启动的应用程序有什么不同?

我让应用程序使用 [[NSProcessInfo processInfo] environment] 记录环境变量我能看到的唯一真正的区别是 残疾人 存在于终端应用程序的变量中:我在我们的代码中看不到任何使用它的东西。

我让应用程序通过 [[NSProcessInfo processInfo] arguments] 记录命令行参数,而且我确实在 Finder 推出的版本中看到了一些不同之处。终端和 Finder 启动版本都将二进制文件的路径列为第一个参数; Finder 还列出了第二个参数“-psn_0_89445704”。我在网上读到它是 Mac OS X 添加到 GUI 应用程序命令行参数的内容,我看到它添加到其他应用程序的命令行参数中,这些应用程序从 Dock 菜单正确隐藏和显示。

你还有什么其他想法可以引导我进一步解开这个谜团吗?感谢您的任何帮助或建议!

最佳答案

在 AppKit 中与 Apple 工程师合作后,找到了解决方案。

在我们的应用程序中,由于各种原因,我们通过此方法“刷新”事件队列:

NSEvent* lastEvent = [NSEvent otherEventWithType:NSPeriodic
                                            location:NSMakePoint(0.0, 0.0)
                                            modifierFlags:0
                                           timestamp:[NSDate timeIntervalSinceReferenceDate]
                                        windowNumber:1
                                             context:NULL
                                             subtype:0
                                               data1:0
                                               data2:0];

    [[NSApplication sharedApplication] discardEventsMatchingMask:NSAnyEventMask beforeEvent:lastEvent];

Mac OS X 系统在启动时向应用程序发送“显示”事件。我们在启动时调用的刷新函数有效地从队列中删除该事件,但 Mac OS X 的核心进程部分有自己的内部队列,用于跟踪显示和隐藏以及其他类型的事件类型,因此它不会“t 发送重复的消息。 (我会调查这个冲洗是否真的有必要)

问题是当 discardEventsMatchingMask:NSAnyEventMask在每个事件上调用,它清除应用程序的事件,但不响应核心进程的显示事件,因此核心进程认为它不需要再次发送显示事件。

这个特定问题的解决方案是更有选择性地清除事件。在我的新实现中,我没有清除核心进程将发送的事件。
/* a bug in Apple's Core Process group forces me to isolate which events should be cleared as
        show|hide|activate|deactivate messages get sent by Core Process, but are not _marked_ as
     handled and so Core Process thinks that the "Show" event is still pending and will not send
     another */

    NSEvent* lastEvent = [NSEvent otherEventWithType:NSPeriodic
                                            location:NSMakePoint(0.0, 0.0)
                                            modifierFlags:0
                                           timestamp:[NSDate timeIntervalSinceReferenceDate]
                                        windowNumber:1
                                             context:NULL
                                             subtype:0
                                               data1:0
                                               data2:0];


    NSEventMask maskForEventsToDiscard = (NSPeriodic |
                                          NSLeftMouseDown |
                                          NSLeftMouseUp |
                                          NSMouseMoved |
                                          NSLeftMouseDragged |
                                          NSRightMouseDragged |
                                          NSMouseEntered |
                                          NSMouseExited |
                                          NSKeyDown |
                                          NSOtherMouseDown |
                                          NSOtherMouseUp |
                                          NSOtherMouseDragged);

    [[NSApplication sharedApplication] discardEventsMatchingMask:maskForEventsToDiscard
                                                     beforeEvent:lastEvent];

由于“显示”事件在启动时未清除,现在显示和隐藏工作!

特别感谢 Apple 的 KF!

关于objective-c - 为什么我的应用程序会失败 "unhide"(显示),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15300472/

相关文章:

ios - 参数化 UIView 类属性不显示 View

objective-c - id 类型为 NSString

ios - 隐藏状态栏iOS 7时 View 框架发生变化

macos - 使用 curl 通过共享链接(非公共(public)链接)下载 Dropbox 文件夹

ios - 在 Objective C 中将数字转换为数字

objective-c - Interface Builder 中的 IBOutletCollection 集排序

macos - svn 要求在更新到 MacOS Sierra 后升级工作副本

objective-c - 我的框架将利用其他框架,但我希望这对最终用户是透明的

objective-c - 如何在插入/拔出耳机时收到通知?苹果

cocoa - 带有建议下拉菜单的 NSTextField