今天在推特上看到一篇關於性能優化不錯的文章,是前蘋果開發人員寫的,翻譯了一下與你們分享,原地址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:
🙀GCD爲咱們提交的每一個塊建立了一個線程當你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