使用图层和 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 的最快方法?的主要内容,如果未能解决你的问题,请参考以下文章
NSOperation 与 Grand Central Dispatch
核心数据和线程/ Grand Central Dispatch
暂停和恢复 Grand Central Dispatch 线程
iOS 中的 JSON 请求 - 使用 Grand Central Dispatch 或 NSURLConnection