wantsLayer 的 NSView 性能

NSView performance of wantsLayer

如果我创建一个空白 Mac XCode 项目并在主 window 中并排布置 500 个简单 NSView 对象,它加载速度非常快。如果我在每个子视图上设置 wantsLayer=YES,性能会急剧下降几秒钟。为什么在概念上会出现这种情况?看起来层会比常规的旧 NSViews 更快而不是更慢。

您通过对如此多的视图进行分层支持,给了系统更多的工作要做。 Layer-backing 允许图形加速(用于绘图),但它会增加一些开销,比如布局,更不用说 只是创建它们并将它们放在屏幕上 。如果使用得当,问题不大。

通常,如果您同时在屏幕上管理这么多 "things",您将拥有一个管理其自己的子层树的层支持的托管视图。 "But what about view-based table views?" 你问。诡计,诡计,我说! Table 视图实际上并没有保留它们管理的所有单元格视图;他们有效地重复使用它们,只保留足以代表屏幕上的内容 and/or 动画。

所以我想说这不是一个真正的问题,因为它不是一个特别好的方法来抛出 500 多个图层支持的视图来开始布局和绘图。 :-)

截至 2021 年,Joshua Nozzi 的回答只对了一半。

当你想使用那么多层时,你应该利用 CALayers 和子层的强大功能,而不是一遍又一遍地使用 NSViews,每个层都有自己的 NSCell 和可能的 CALayer,因为你强迫它使用 -wantsLayer:.

500个子层没有问题。子层在结构合理的情况下可以让你更快。

如果你想加快速度,在不需要的每个图层上强制关闭自动动画。是的,很多例子表明,每次调用 属性 进行更改时,您都可以关闭自动动画,这会进一步减慢您的绘图速度,因为它涉及更多的对象消息传递。

请记住以下示例通过按键名称关闭自动动画按键并踢出整个动作,并且不会故意应用于所有子层。如果你的 Sublayers of Sublayers 结构需要它,你可以根据 ParentLayer 调用它来消除 CPU eating Autoanimation。最好让自动动画在您真正想要的图层上完成它的工作。 这改变了 Apple 的默认范例,使所有内容都具有动画效果。 如果关闭了大多数操作键,请注意您的层确实显示得有点像状态机。在此处的示例中 @"frame" 动画未被清除..

void turnOffAutoAnimationsForLayerAndSublayers(CALayer* layer) {
    NSNull *no = [NSNull null]; //(id)kCFNull
    NSDictionary *dict =  [NSDictionary dictionaryWithObjects:@[no,no,no,no,no,no,no,no]
                   forKeys:@[@"bounds",@"position",@"contents",@"hidden",@"backgroundColor",@"foregroundColor",@"borderWidth",@"borderColor"]];
    for (CALayer *layertochange in layer.sublayers ) {
        if (layertochange.sublayers.count) {
            for (CALayer *sublayertochange in layertochange.sublayers) {
                sublayertochange.actions = dict;
            }
        }
        layertochange.actions = dict;
    }
}

并在 CALayer 支持的 NSView -init 中使用它,例如..

CALayer *layer = [CALayer layer];
layer.frame = self.frame;
//...backgroundColor, contents, etc etc..

// here you could even do a for loop adding as much sublayers you want..  
for (int y=0; y<100; y++) {
    CALayer *sub = [CALayer layer];
    //...frame, backgroundColor, contents, of sublayers etc etc..
    layer.frame = CGRectMake(0, 0, 10, y*10);
    [layer addSublayer:sub];
}
// structure is ready, add it to the base layer
self.layer = layer;
// now tell NSView you really want to make use this layer structure
self.wantsLayer = YES;
// turn off AutoAnimations
turnOffAutoAnimationsForLayerAndSublayers(self.layer);

对于基本相同的层,甚至还有一个专门的子类,称为 CAReplicatorLayer。允许您使用更少的内部绘制调用添加更多内容。

你还应该知道,隐藏 的图层根本不会计算(意思是在这里绘制)。即使在隐藏层上,您仍然可以更改属性。在需要时取消隐藏它们,而不是更早。因此,在示例中,自定义 CATextLayer 将其 字符串 属性 更改为 @"",也就是什么都不会绘制。但是,如果您将 CATextLayer 子类化并将其字符串实现更改为让我们说..

@interface YourTextAutoHideLayer : CATextLayer
-(void)setString:(id _Nullable )string;
@end

@implementation YourTextAutoHideLayer
-(void)setString:(id _Nullable)string {
    self.hidden = [string isEqual:@""];
    super.string = string;
}
@end

你的绘图速度更快了。 A) 因为更少的对象消息来隐藏没有文本的图层,并且 B) 一旦隐藏它就不是内部绘制调用的一部分有效加速你的主要 CALayers 绘图。

在基于 NSCell 的 NSTableView 上,您通常从不使用 CALayers。没有必要,NSTableViews 有意管理可见单元格以保持示例的平滑滚动。而且它们 (NSTableViewCells) 仍然有一个重用机制来加快速度。无论如何,你很可能不会用 CALayers 重新发明一个 tableview。

如果这还不够快,则值得考虑使用 GPU 强大功能的 MetalView。