使用图层和 Grand Central Dispatch 呈现 UIButton 的最快方法?

Posted

技术标签:

【中文标题】使用图层和 Grand Central Dispatch 呈现 UIButton 的最快方法?【英文标题】:Fastest way to render UIButtons using layers and Grand Central Dispatch? 【发布时间】:2011-09-28 07:20:56 【问题描述】:

我有一个由 30 个 UIButtons 组成的网格,甚至可能更多,子类化以使用层进行渲染:一个基础 CALayer、一个CAShapeLayer、一个CAGradientLayer 和一个CATextLayer。我试图在加载相应的 xib 文件时尽量减少渲染/显示按钮所需的总时间。如果我只是在viewDidLoad中依次设置每个按钮,视图出现所需的时间大约为5-6秒,这显然太多了。

为了加快按钮设置,我使用 Grand Central Dispatch 如下。在 viewDidLoad 中,我在全局队列中使用 dispatch_async 设置每个按钮层(将形状和渐变层添加到基础层),以便可以在不同的线程中呈现按钮。块的末尾,CATextLayer被添加到渐变层中。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^

    CGRect base_bounds = CGRectMake(0, 0, self.layer.bounds.size.width, self.layer.bounds.size.height - self.layer.bounds.size.height * 0.10f);
    CGPoint move_point = CGPointMake(0.0f, base_bounds.size.height * 0.10f);
    self.layer.masksToBounds = NO;
    baseLayer = [CALayer layer];

    baseLayer.cornerRadius = 10.0;
    baseLayer.shadowOffset = CGSizeMake(0.0f, 2.0f);
    baseLayer.shadowOpacity = 1.5f;
    baseLayer.shadowColor = [UIColor blackColor].CGColor;
    baseLayer.shadowRadius = 2.5f;
    baseLayer.anchorPoint = CGPointMake(0.5f, 0.5f);
    baseLayer.position    = move_point;

    CAShapeLayer *shape = [CALayer layer];
    shape.bounds = base_bounds;
    shape.cornerRadius = 10.0;
    shape.anchorPoint      = CGPointMake(0.0f, 0.0f);
    shape.position         = move_point;
    shape.backgroundColor = [UIColor darkGrayColor].CGColor;

    gradient = [CAGradientLayer layer];
    gradient.anchorPoint      = CGPointMake(0.0f, 0.0f);
    gradient.position         = CGPointMake(0.0f, 0.0f);
    gradient.bounds           = base_bounds;
    gradient.cornerRadius     = 10.0;
    gradient.borderColor      = [UIColor colorWithRed:0.72f
                                                green:0.72f
                                                 blue:0.72f
                                                alpha:1.0].CGColor;
    gradient.borderWidth      = 0.73;
    gradient.colors = [NSArray arrayWithObjects:
                       (id)[UIColor whiteColor].CGColor,
                       (id)[UIColor whiteColor].CGColor,
                       nil];


    [baseLayer addSublayer:shape];
    [baseLayer addSublayer:gradient];
    [self.layer addSublayer:baseLayer];

    [textLayer setBounds:gradient.bounds];
           [textLayer setPosition:CGPointMake(CGRectGetMidX(textLayer.bounds), CGRectGetMaxY(textLayer.bounds) - 6)];
           [textLayer setString:self.titleLabel.text]; 
           [textLayer setForegroundColor:[UIColor blackColor].CGColor];
           [gradient addSublayer:textLayer];

);

这种方法将总时间减少到大约 2-3 秒。我想知道是否有人可以建议一种更快的方式来呈现按钮。请注意,我对任何放弃使用层的解决方案不感兴趣。

提前谢谢你。

【问题讨论】:

您是否使用 Instruments 和 Timing Profile 确定了瓶颈? @MikeWeller:是的,执行我的问题中显示的源代码的时间是 Instruments 报告的时间。 【参考方案1】:

也许我没有抓住重点,但你最好重写 UIButton drawRect: 方法并在 CoreGraphics (CG) 中绘制东西会比几秒钟快得多,你可以轻松地做渐变、文本, 使用 CG API 的图像。如果我理解正确,每个按钮有 4 层,同一视图中有 30 多个按钮(120 层以上)?如果是这样,我认为您不应该绘制这么多图层(单独渲染/混合所有图层将解释巨大的渲染时间)。另一种可能性是为所有按钮设置 4 个大层。

【讨论】:

是的,您理解正确:每个按钮 4 层,开始时至少有 30 个按钮。您放弃层以支持 Core Graphics 调用的想法当然值得一试。我将覆盖 drawRect: 方法并实现它。我会让你知道的。 从 CALayers 切换到 Core Graphics 调用需要半个小时。使用 GCD 的 iPad 2 无法在 2-3 秒内渲染图层,现在即使是不使用 GCD 的普通 iPhone 3GS 也能够立即显示按钮。投票赞成,作为最终答案被接受并授予赏金。【参考方案2】:

这是我的想法,

将您的 CALayers 与 UIButton 分开 - UIKit 不允许后台线程上的任何内容。

当您在按钮网格之前的屏幕中有机会时,使用 [buttonGridViewController performSelectorInBackground:renderCALayers] 在后台渲染您的 CALayers。

当您在 viewDidLoad 中加载按钮网格时,将 UIButtonType 类型为 UIButtonTypeCustom 和 backgroundColor = [UIColor clearColor] 覆盖在“按钮”CALayers 的顶部(确保在 UIButtons 上调用bringSubviewToFront,以便它们获得触摸事件) .

如果您不知道要渲染多少个按钮,您可能希望预渲染您可能显示的最大数量。

如果您有任何问题,请发表评论。

编辑:

一些相关的项目,

What to replace a UIButton to improve frame rate?

UI design - Best way to deal with many UIButtons

How to get touch event on a CALayer?

我相信,从这一点开始,您将获得更快加载的唯一方法是完全替换或删除 UIButton,并使用另一种方法拦截触摸事件。这样一来,所有的渲染都可以在视图呈现之前在后台线程中完成。

【讨论】:

由于各种原因,我无法将 CALAyers 与 UIButton 分开。但是,如 mycode sn-p 所示,CALayers 可以在后台渲染。sn-p 显示我在主线程上仅渲染文本层。我刚刚发现,即使是这个文本层也可能在与主线程不同的线程上呈现。通过此修改,视图现在立即加载,但按钮标签仅在 2-3 秒后出现。目前,我正在使用在呈现按钮的同时出现的 HUD 显示。【参考方案3】:

您是否考虑过使用 NSOperation 来生成按钮的图层?在您的代码中,您将实例化一个操作队列,然后遍历每个按钮,在每个按钮启动时触发一个操作(传递生成该按钮层所需的相应参数)到队列中。

在您的 NSOperation 中,使用 CoreGraphics 生成您的图层,然后通过您的完成处理程序(确保在主线程上调用它)将该图层传递回启动操作的按钮(在本例中为目标按钮)。这应该会为您提供相当不错的性能,因为您的按钮层将使用 CoreGraphics 从主线程渲染,然后仅在渲染层后传递回 UIKit 进行演示。

NSOperation 位于 GCD 之上,具有边际开销,但在您的情况下提供了好处,例如设置相关先决条件和优先级的能力。

另外,我很确定您不应该在您共享的 GCD 块中调用 UIColor。

希望对您有所帮助!

【讨论】:

您建议的方法与我在实际切换到 GCD 之前使用的方法基本相同。它需要几乎相同的时间。仅供参考:您可以如图所示在块中设置 UIColors,没有任何问题。 与您描述的类似,但不完全相同。你提建议,我给了。我肯定不会再来了!任何涉及它的文档都明确禁止从主线程之外的任何地方触摸 UIKit,无论它是否有效。

以上是关于使用图层和 Grand Central Dispatch 呈现 UIButton 的最快方法?的主要内容,如果未能解决你的问题,请参考以下文章

Grand Central Dispatch(GCD)

NSOperation 与 Grand Central Dispatch

核心数据和线程/ Grand Central Dispatch

暂停和恢复 Grand Central Dispatch 线程

iOS 中的 JSON 请求 - 使用 Grand Central Dispatch 或 NSURLConnection

使用 Grand Central Dispatch (GCD) 创建恰好 N 个线程