使用大量CALayer的NSView嵌套在层级结构中(WindowServer - CPU 100%)

5

我有一个有趣的案例需要你帮忙解决。

我的Mac应用程序有一个 layer-hosted NSView,里面有很多层和子层以及子子层。可以想象成一个Finder窗口里的项目(图标、文本标签)等等。每个项目都有一个按钮、选择背景层。

一切都很好,直到你拥有了1000个这样的项目。

现在,当我尝试与此视图交互时,我的应用程序变得不响应。棘手的是,消耗CPU的不是我的应用程序,而是WindowServer。它达到了100%,系统会冻结一段时间。

重要说明:

我的视图是应用程序界面的一部分。有一个带有其他视图的主窗口。它们也有自己的结构。

我注意到,如果我将这个 layer-hosted 视图放在一个单独的窗口中,即使拥有相同的1000个项目,它也能正常工作。但如果把它放回主窗口,就会开始阻塞系统。

问题:

  1. 我的应用程序中的哪些视图/层会导致 WindowServer 出问题?
  2. 为什么把这个视图放在一个单独的窗口里会有帮助?

我进行了一些测试,删除了每个项目的所有子层,只留下一两个子层。这减轻了系统的负担,但仍然不好。我禁用了所有绘图 - 只为图标使用了小图片。也没有帮助。


2
给我们相关的代码。 - brianLikeApple
嗯,视图相当复杂。因此,它来自不同地方的大量代码。我不知道如何以简单的方式在这里发布它。 - UJey
2个回答

3
WindowServer的主要工作是为macOS绘制与图形相关的事物,这意味着从整个屏幕可以看到的所有东西都必须经过WindowServer并让它为您绘制。
有很多原因导致WindowServer使用高CPU。
  1. 您的应用程序中有复杂的绘图方法。--尝试简化您的绘图方法。
  2. 您的桌面上有很多图形,因此WindowServer不仅绘制您的应用程序,还绘制桌面图形。--尝试清理您的桌面。
  3. 您打开了一些需要复杂图形绘制的应用程序吗?--尝试关闭该应用程序。
在看到您的确切代码之前,我无法告诉您为什么将元素放入单独的窗口可以帮助解决问题。也许窗口本身隐藏了某些内容,使绘制更容易?
我的一个软件有很多图形元素和动画,但我从未遇到过您的问题。创建一个测试项目并尝试类似的事情如何?有时,在更清晰的项目结构上看到问题可能会有所帮助。
我创建了一个简单的演示,其中窗口中有100 * 100个子层。似乎完全没有问题。
import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

@IBOutlet weak var window: NSWindow!

func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Insert code here to initialize your application
    self.window.contentView?.wantsLayer = true

    let size = 10
    for i in 0..<10000 {
        let x = i%100
        let y = i/100

        let layer = CALayer()
        layer.frame = NSRect(x: x*size, y: y*size, width: size, height: size)
        layer.backgroundColor = .random()
        self.window.contentView?.layer?.addSublayer(layer)
    }

}



   func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
}

extension CGFloat {
    static func random() -> CGFloat {
        return CGFloat(arc4random()) / CGFloat(UInt32.max)
    }
}

extension CGColor {
    static func random() -> CGColor {
        return CGColor(red:   .random(),
                       green: .random(),
                       blue:  .random(),
                       alpha: 1.0)
    }
}

enter image description here


测试项目方面的观点很好。我会尝试并带回结果。谢谢! - UJey
#1看起来不错,但我仍然没有看到任何复杂的图形。它主要是由图标和文本组成的。图标是通过层内容绘制的,文本是使用层的委托和drawLayer:inContext:绘制的。我尝试禁用(并删除)所有其他图层,只留下图标。但仍然存在CPU和WindowServer的问题,仍然会出现冻结和延迟。 - UJey
@UJey,你能否尝试测试应用程序并仅使用图标而不使用你的代码吗?你还能看到问题吗? - brianLikeApple
嗨!我刚回到这个问题,发现当有很多项目时,我的图层托管视图仅通过将鼠标放在其上方就会消耗25-30%的WindowServer进程CPU。我的意思是,我用鼠标悬停在我的“复杂”视图上并保持不动。结果是WindowServer进程的25-30%CPU。什么都没有发生。只是鼠标悬停在我的视图上。 - UJey
1
如果测试项目能够正常工作,那对我来说就很好!因为你可以重新启动一个没有任何问题的新项目。这不会花费很长时间,所以最好继续新项目,而不是被卡在原地。 - brianLikeApple
显示剩余7条评论

0

我刚刚发现如果窗口是无边框的,你会遇到麻烦。但是将styleMask设置为NSWindowStyleMaskTitled可以完全解决这个问题。

看来无边框窗口的处理方式不同。

现在我需要找到一种方法来隐藏所有标题栏控件,同时保留mask。我已经完成了这个操作:

  • NSFullSizeContentViewWindowMask 添加到窗口掩码中
  • 添加了

    self.titlebarAppearsTransparent = YES;
    self.titleVisibility = NSWindowTitleHidden;
    

但窗口仍然显示在左上角的3个按钮。

有什么想法可以隐藏它们吗?


额外信息:窗口还应该有NSWindowStyleMaskResizable以消除WindowServer过载。 - UJey
好的。看起来[[self standardWindowButton:NSWindowCloseButton] removeFromSuperview];是可行的方法。 - UJey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接