今天在推特上看到一篇关于性能优化不错的文章,是前苹果开发人员写的,翻译了一下与你们分享,原地址iOS Performance tips you probably didn't know (from an ex-Apple engineer)html
做为开发人员,良好的性能对于使咱们的用户感到惊喜和喜悦是无价的。iOS用户具备很高的标准,若是你的应用程序反应很慢或在内存压力下崩溃,他们将中止使用它,或者更糟糕的是,你的评论会很糟糕。ios
在过去的6年中,我在Apple从事Cocoa框架和第一方应用程序的开发工做。我从事Spotlight,iCloud,应用程序扩展程序的工做,最近从事过Files的工做。git
我注意到有一种很容易实现的目标,你能够在20%的时间内得到80%的性能提高。github
这是一份性能提示清单,但愿能给你带来最大的收益:数据库
在内存使用方面,咱们倾向于将lables视为轻量级的。最后,它们只是显示文本。UILabel实际上存储为位图,这很容易消耗兆字节的内存。swift
值得庆幸的是,UILabel的实现很聪明,而且只使用它须要的:性能优化
单色标签最多消耗width * height * contentsScale ^ 2 *(每像素1字节)字节,而非单色标签则消耗4倍的:width * height * contentsScale ^ 2 *(每像素4字节) 。bash
例如,在iPhone 11 Pro Max上,大小为414 * 100 points的lable最多可消耗:并发
当这些cells进入重用队列时,一种常见的反模式是使UITableView / UICollectionView cell labels填充文本内容。一旦cells被回收,label的文本值极可能会有所不一样,所以存储它们很浪费。app
要释放潜在的兆字节内存:
tableView(_:didEndDisplaying:forRowAt:)
collectionView(_:didEndDisplaying:forItemAt:)
复制代码
例如:
常见的反模式是将不会影响UI的块从主队列分配到一个全局并发队列中。
func textDidChange(_ notification: Notification) {
let text = myTextView.text
myLabel.text = text
DispatchQueue.global(qos: .utility).async {
self.processText(text)
}
}
复制代码
若是咱们暂停application:
当你dispatch_async
一个块到并发队列时,GCD将尝试在其线程池中找到一个空闲线程来运行该块。 若是找不到空闲线程,则必须为工做项建立一个新线程。将块快速分配到并发队列可能致使快速建立新线程。
记住这些:
一般,你应该始终从数量有限的串行队列开始,每一个串行队列表明应用程序的子组件(数据库队列,文本处理队列等)。对于具备本身的串行调度队列的较小对象,请使用dispatch_set_target_queue定位子组件队列之一。
仅当遇到额外的并发能够解决的瓶颈时,才使用本身建立的并发队列(不使用dispatch_get_global_queue),并考虑使用dispatch_apply。
关于dispatch_get_global_queue的注释:
从dispatch_get_global_queue得到的并发队列不利于将QoS信息转发到系统,所以应避免。
有关libdispatch效率更多详细建议,请查看这个出色的收集。
所以,你尝试过尽量优化内存使用率,可是即便如此,使用应用程序一段时间后,内存使用率仍然很高。
不用担忧,某些系统组件只有在收到内存警告时才会释放内存。
例如,UICollectionView对-didReceiveMemoryWarning(从iOS 13开始)做出反应,在内存不足的状况下从内存中清除其重用队列。
模拟内存警告:
[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)];
复制代码
这是一个常见的反模式:
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:].
复制代码
这是一种很差的作法,并代表有代码异味。 这也不利于性能。
我最近写过这样的代码,一旦点击一个视图,便会根据其标签值更改其子视图的颜色。
UIKit使用objc_get / setAssociatedObject()
实现标签,这意味着每次你设置或获取标签时,你都在进行字典查找,该字典将显示在Instruments中:
-[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工做的线程,所以主队列会受到影响。
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的时间稍长一点。” 请注意,我只是想起这一点,由于我只是在这个领域中工做,试图弄清楚如何避免内存不足的状况而不进行此优化。再次感谢你的帖子。
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