你可能不知道的iOS性能优化建议(来自前Apple工程师)

今天在推特上看到一篇关于性能优化不错的文章,是前苹果开发人员写的,翻译了一下与大家分享,原地址iOS Performance tips you probably didn't know (from an ex-Apple engineer)

racetrack.jpg

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

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

我注意到有一种很容易实现的目标,你可以在20%的时间内获得80%的性能提升。

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

1. UILabel的成本超出你的想象

uilabel-bordered.png

在内存使用方面,我们倾向于将lables视为轻量级的。最后,它们只是显示文本。UILabel实际上存储为位图,这很容易消耗兆字节的内存。

值得庆幸的是,UILabel的实现很聪明,并且只使用它需要的:

  • 如果label是单色的,UILabel将选择kCAContentsFormatGray8Uint的calayercontents格式(每像素1字节),而非单色标签(例如,要显示"🥳是聚会时间了",或多色NSAttributedString)将需要使用kCAContentsFormatRGBA8Uint(每像素4字节)。

单色标签最多消耗width * height * contentsScale ^ 2 *(每像素1字节)字节,而非单色标签则消耗4倍的:width * height * contentsScale ^ 2 *(每像素4字节) 。

例如,在iPhone 11 Pro Max上,大小为414 * 100 points的lable最多可消耗:

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

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

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

  • 如果将label的文本设置为隐藏,则将label的文本设置为nil,仅偶尔显示它们。
  • 如果label的文本显示在UITableView / UICollectionView cell中,则将label的文本设置为nil,在:
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)
    }
}

如果我们暂停application:

thread-explosion.jpg

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

当你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效率更多详细建议,请查看这个出色的收集

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

因此,你尝试过尽可能优化内存使用率,但是即使如此,使用应用程序一段时间后,内存使用率仍然很高。

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

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

模拟内存警告:

  • 在iOS模拟器中,使用"模拟内存警告"菜单项。
  • 在测试设备上,调用私有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的数据库队列中。
  • 由于makeAsyncCall从主队列调用了dispatch_async,数据库队列的QoS将提高到QOS_CLASS_USER_INITIATED
  • 用信号量阻塞主队列意味着它被困在等待QOS_CLASS_USER_INITIATED下运行的工作(低于主队列的QOS_CLASS_USER_INTERACTIVE),因此优先级反转。

XPC的附带说明:

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

-[NSXPCConnection synchronousRemoteObjectProxyWithErrorHandler:].

5. 不要使用UIView tags

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

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

UIKit使用objc_get / setAssociatedObject()实现标签,这意味着每次你设置或获取标签时,你都在进行字典查找,该字典将显示在Instruments中:

tag_time_profiler.jpg

-[UIView tag]在处理触摸事件时会消耗宝贵的毫秒数。

文章和推特下有意思的讨论

文章和推特下有意思的讨论,我这里摘取一些,可能也有帮助

1

Steven Fisher:我仍然没有找到替代4的好方法。我减少了对该模式的使用,以至于它仅在我的测试工具中使用,但仍然困扰着我。

Xaxxus:PromiseKit,是你的答案。

Rony Fadel:向API提供者索要同步API,使用同步API是你最好的选择,它将确保QoS传播。

Daniel Pourhadi:如果说API提供者是Apple,又要等AVAsset属性填充怎么办?后台线程线程(相对于主线程)中的信号量有害吗?

Rony Fadel:后台线程上的信号量有什么好处?如果你真的认为使用同步API有好处,请提交错误报告。
这是有害的,因为每次你阻塞等待后台工作的信号时,系统都会丢失QoS传播信息。
然后想象一下,主队列在该后台队列上执行dispatch_sync。 boost不会一直传播到执行AVAsset工作的线程,因此主队列会受到影响。

2

Tyler:非常有趣,谢谢你。重新填充cell-我的理解是,collection/table view进入重用池会在大于可见区域的边界上触发-这是一种防止重用池抖动的优化。如果我们 clear/load cell可见性,那么我们是否不进行这种优化? 我了解你的建议是解决内存问题,但这对提高性能有什么作用? 不幸的是,似乎没有一种方法可以知道单元何时真正回到重用池中。

Rony Fadel:cells不在视图中时(通常在滚动时)进入重用队列。它与内存有关(性能的一部分,至少是我们在Apple上的分类方式),但与滚动性能无关。

Tyler:我认为你描述的是在didDisappear时返回重用池的内容与iOS10之前的行为一致。 他们从iOS 10记录中的UICollectionView的新增功能中描述了添加的滚动性能优化- “...现在该cell将要退出CollectionView的可见范围。因此,我们将向其发送期望的didEndDisplayingCell。Peter在谈论iOS 9时,此时该cell进入了重用队列,我们将完成此操作。要再次在此特定cell中显示数据,我们必须经历生命周期的开始 并调用cellForItemAtIndexPath。但是在iOS 10中,我们将保留该cell的时间稍长一点。” 请注意,我只是想起这一点,因为我只是在这个领域中工作,试图弄清楚如何避免内存不足的情况而不进行此优化。再次感谢你的帖子。

3

John Siracusa:当你要等待超时的异步非主线程用户启动的工作时,你建议使用什么而不是DispatchSemaphore?

Yaron Inger:你可以使用dispatch group 和 dispatch_group_wait。

Rafael Cerioli:Dispatch groups 和 semaphores一样,没有方法将async转变成sync。

J Matusevich:Dispatch group 是答案。

NieR: Autoconf:Dispatch group 和 semaphore 性能一样. The API 很棒但行为没有区别。

Bob Godwin:DispatchWorkItem👍🏽它们处理了我必须使用semafores的那些情况。 只是该API尚未为开发人员所广泛了解 dispatchworkitem

pkamb:DispatchGroup! Waiting for multiple blocks to finish

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容