您可能不知道的iOS性能技巧(来自前Apple工程师)

2020-11-23  本文已影响0人  弹吉他的少年

您可能不知道的iOS性能技巧(来自前Apple工程师)

原地址:iOS Performance tips you probably didn't know (from an ex-Apple engineer)

ios
macos
可可
开发
性能

如果您想了解有关Cocoa开发和引导软件业务的最新文章,请在Twitter上关注我或注册邮件列表*。

作为开发人员,良好的性能对于使我们的用户感到惊喜和喜悦是无价的。iOS用户具有很高的标准,如果您的应用程序呆滞或在内存压力下崩溃,他们将停止使用该应用程序,或者更糟糕的是,您的评论将很糟糕。

在过去的6年中,我在Apple从事Cocoa框架和第一方应用程序的开发工作。我曾经从事SpotlightiCloud应用程序扩展的工作,最近从事过Files的工作

我注意到有一种低垂的结果,您可以在20%的时间内获得80%的性能提升

这是一份性能提示清单,希望能给您带来最大的收益:

1)UILabel成本超出您的想象

UILabel

一个UILabel在野外

我们很容易认为标签在内存使用方面是轻量级的。最后,它们只显示文本。UILabels实际上存储为位图,这很容易消耗兆字节的内存。

值得庆幸的是,该UILabel实现很聪明,并且仅消耗其所需的资源:

单色标签最多消耗width * height * contentsScale^2 * (1 byte per pixel)字节,而非单色标签最多消耗4倍width * height * contentsScale^2 * (4 bytes per pixel)

例如,在iPhone 11 Pro Max上,大小414 * 100点标签最多可消耗:

编辑:

在与UIKit工程师在Twitter上讨论之后,我要提请注意。

确保始终先进行测量,如果性能问题确实是由标签引起的内存压力,请仅考虑以下更改。

从UIKit的@Inferis中

就目前的情况而言:假设将来对UILabel的更新可以优化其(重新)使用后备存储的方式,那么您的优化现在会使事情(可能很多)变得更糟。

当这些单元格进入重用队列时,一种常见的反模式是使UITableView/UICollectionView单元格标签填充文本内容。一旦单元被回收,标签的文本值很可能会有所不同,因此存储它们是浪费的。

要释放潜在的兆字节内存:

tableView(_:didEndDisplaying:forRowAt:)
collectionView(_:didEndDisplaying:forItemAt:)

2)始终从串行队列开始,并且仅将并发队列用作最后的选择

常见的反模式是将不会影响UI的块从主队列分配到全局并发队列之一。

例如:

func textDidChange(_ notification: Notification) {
    let text = myTextView.text
    myLabel.text = text
    DispatchQueue.global(qos: .utility).async {
        self.processText(text)
    }
}

如果我们暂停我们的申请:

螺纹爆炸

CDGCD为我们提交的每个块创建了一个线程

当您将dispatch_async一个块放入并发队列时,GCD会尝试在其线程池中找到一个空闲线程来运行该块。如果找不到空闲线程,则必须为工作项创建一个新线程。快速将块分配到并发队列可能导致快速创建新线程。

请记住:

通常,您应该始终从数量有限的串行队列开始,每个串行队列代表应用程序的子组件(数据库队列,文本处理队列等)。对于具有自己的串行分派队列的较小对象,请使用来定位子组件队列之一dispatch_set_target_queue

仅当遇到瓶颈可以通过其他并发解决时,才使用您自己创建的并发队列(不使用dispatch_get_global_queue),并考虑使用dispatch_apply

关于的注释dispatch_get_global_queue

您从中获得的并发队列dispatch_get_global_queue 不利于将QoS信息转发到系统,因此应避免

一个报价由libdispatch”皮埃尔Habouzit:

dispatch_get_global_queue() 实际上,这是调度API提供的最糟糕的事情之一,因为尽管在运行时做出了所有最大的努力,但是在运行时没有足够的有关您的操作/参与者/…的信息来了解您的意图并对其进行优化。 。

有关libdispatch效率提示的更详细概述,请查看此出色的汇编

3)可能没有看起来那么糟糕

因此,您尝试了尽可能多地优化内存使用率,但是即使那样,使用您的应用程序一段时间后,内存使用率仍然很高。

不用担心,某些系统组件只有在收到内存警告时才会释放内存

例如,在低内存情况下,UICollectionView-didReceiveMemoryWarning(从iOS 13开始)做出反应,从内存中清除其重用队列。

模拟内存警告:

[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)];

4)避免dispatch_semaphore_t用于等待异步工作

这是一个常见的反模式:

let sem = DispatchSemaphore(value: 0)
makeAsyncCall {
    sem.signal()
}
sem.wait()

问题在于,优先级信息不会传播到将由其makeAsyncCall完成工作的其他线程/进程,并且可能导致优先级倒置

附注XPC

如果您已经使用过XPC(在macOS上,或者您正在使用NSFileProviderService),并且想要进行同步调用,请避免使用信号量,而是使用以下命令将消息发送到同步代理:

- [NSXPCConnection synchronousRemoteObjectProxyWithErrorHandler:].

5)不要使用UIView标签

这是一种不好的做法,并表明有代码异味。这也不利于性能。

我最近使用过代码,一旦点击一个视图,便会根据其标签值更改其子视图的颜色。

UIKit使用来实现标签objc_get/setAssociatedObject(),这意味着每次设置或获取标签时,您都在进行字典查找,这可能会在热循环中显示在Instruments中:

[图片上传失败...(image-58e2b0-1606097151280)]

<figcaption class="image-caption" style="box-sizing: inherit; font-style: normal; display: inherit; text-align: center; font-size: 14.4px; color: rgb(86, 86, 86);">-[UIView tag] 处理触摸事件时要花费宝贵的毫秒。</figcaption>

编辑:充其量是微优化。我的收获是:1)令人惊讶的-[UIView tag]是基于关联的对象,而2)仅在性能敏感的代码中大量使用它才有任何影响。

离别的想法

希望您今天阅读这些提示后能学到新的知识。与往常一样,请确保在进行性能调整之前先进行测量

有问题吗?有更多性能提示要分享吗?在评论中让我知道!

插头

您可以在这里查看我整洁的Mac实用程序。

编辑

上一篇下一篇

猜你喜欢

热点阅读