今天在推特上看到一篇關于性能優化不錯的文章,是前蘋果開發人員寫的,翻譯了一下與大家分享
作為開發人員,良好的性能對于使我們的用戶感到驚喜和喜悅是無價的,iOS用戶具有很高的標準,如果你的應用程式反應很慢或在記憶體壓力下崩潰,他們將停止使用它,或者更糟糕的是,你的評論會很糟糕,
在過去的6年中,我在Apple從事Cocoa框架和第一方應用程式的開發作業,我從事Spotlight,iCloud,應用程式擴展程式的作業,最近從事過Files的作業,
我注意到有一種很容易實作的目標,你可以在20%的時間內獲得80%的性能提升,
這是一份性能提示清單,希望能給你帶來最大的收益:
1. UILabel的成本超出你的想象
在記憶體使用方面,我們傾向于將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:
??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中:

-[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
推薦??:
如果你想一起進階,不妨添加一下交流群1012951431
面試題資料或者相關學習資料都在群檔案中 進群即可下載!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/4177.html
標籤:iOS
上一篇:類(元類)物件方法快取原理
下一篇:實作iOS中的多語言切換
