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

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

作者: 弹吉他的少年 | 来源:发表于2020-11-23 10:10 被阅读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实现很聪明,并且仅消耗其所需的资源:

    • 如果您的标签是单色的UILabel将选择CALayerContentsFormatkCAContentsFormatGray8Uint(每像素1个字节),而非单色的标签(例如,显示“参加聚会的时间”或彩色NSAttributedString)则需要使用kCAContentsFormatRGBA8Uint(每像素4个字节)。

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

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

    • 414 * 100 * 3^2 * 1 = **372.6kB** (单色)
    • 414 * 100 * 3^2 * 4 = **~1.49MB** (非单色)

    编辑:

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

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

    从UIKit的@Inferis中

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

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

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

    • text如果将标签设置为隐藏并且仅偶尔显示,则取消标签。
    • 如果标签text显示在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会尝试在其线程池中找到一个空闲线程来运行该块。如果找不到空闲线程,则必须为工作项创建一个新线程。快速将块分配到并发队列可能导致快速创建新线程。

    请记住:

    • 创建线程不是免费的。如果您要提交的工作块很小(<1毫秒),则在切换执行上下文,CPU周期和内存弄脏方面创建新线程会很浪费。
    • 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开始)做出反应,从内存中清除其重用队列。

    模拟内存警告:

    • 在iOS模拟器中,使用Simulate Memory Warning菜单项。
    • 在测试设备上,调用私有API(请勿与此一起提交到App Store):
    [[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)];
    
    

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

    这是一个常见的反模式:

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

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

    • 假设makeAsyncCall从主队列进行调用会将工作负载分派到QoS的数据库队列中QOS_CLASS_UTILITY
    • QOS_CLASS_USER_INITIATED由于来自主队列的makeAsyncCall调用dispatch_async,DB队列的QoS将得到提高。
    • 用信号量阻塞主队列意味着它被困在等待正在运行的工作QOS_CLASS_USER_INITIATED(低于主队列的工作QOS_CLASS_USER_INTERACTIVE),因此优先级反转。

    附注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实用程序。

    编辑

    • 感谢Paul HudsonUICollectionView/中使用时,纠正了使标签内容无效的位置UITableView

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/hnemiktx.html