您可能不知道的iOS性能技巧(來自前Apple工程師)
原地址:iOS Performance tips you probably didn't know (from an ex-Apple engineer)
? ios
? macos
? 可可
? 開發(fā)
? 性能
如果您想了解有關(guān)Cocoa開發(fā)和引導(dǎo)軟件業(yè)務(wù)的最新文章焕刮,請在Twitter上關(guān)注我或注冊郵件列表*同规。
作為開發(fā)人員,良好的性能對于使我們的用戶感到驚喜和喜悅是無價的。iOS用戶具有很高的標(biāo)準(zhǔn)偏竟,如果您的應(yīng)用程序呆滯或在內(nèi)存壓力下崩潰,他們將停止使用該應(yīng)用程序癣诱,或者更糟糕的是匿沛,您的評論將很糟糕。
在過去的6年中须板,我在Apple從事Cocoa框架和第一方應(yīng)用程序的開發(fā)工作碰镜。我曾經(jīng)從事Spotlight,iCloud习瑰,應(yīng)用程序擴(kuò)展的工作绪颖,最近從事過Files的工作。
我注意到有一種低垂的結(jié)果甜奄,您可以在20%的時間內(nèi)獲得80%的性能提升柠横。
這是一份性能提示清單,希望能給您帶來最大的收益:
1)UILabel
成本超出您的想象
一個UILabel
在野外
我們很容易認(rèn)為標(biāo)簽在內(nèi)存使用方面是輕量級的课兄。最后牍氛,它們只顯示文本。UILabels
實際上存儲為位圖烟阐,這很容易消耗兆字節(jié)的內(nèi)存搬俊。
值得慶幸的是,該UILabel
實現(xiàn)很聰明,并且僅消耗其所需的資源:
- 如果您的標(biāo)簽是單色的悠抹,
UILabel
將選擇CALayerContentsFormat
的kCAContentsFormatGray8Uint
(每像素1個字節(jié))珠月,而非單色的標(biāo)簽(例如,顯示“參加聚會的時間”或彩色NSAttributedString
)則需要使用kCAContentsFormatRGBA8Uint
(每像素4個字節(jié))楔敌。
單色標(biāo)簽最多消耗width * height * contentsScale^2 * (1 byte per pixel)
字節(jié)啤挎,而非單色標(biāo)簽最多消耗4倍:width * height * contentsScale^2 * (4 bytes per pixel)
。
例如卵凑,在iPhone 11 Pro Max上庆聘,大小414 * 100
點標(biāo)簽最多可消耗:
-
414 * 100 * 3^2 * 1 = **372.6kB**
(單色) -
414 * 100 * 3^2 * 4 = **~1.49MB**
(非單色)
編輯:
在與UIKit工程師在Twitter上討論之后,我要提請注意勺卢。
確保始終先進(jìn)行測量伙判,如果性能問題確實是由標(biāo)簽引起的內(nèi)存壓力,請僅考慮以下更改黑忱。
從UIKit的@Inferis中:
就目前的情況而言:假設(shè)將來對UILabel的更新可以優(yōu)化其(重新)使用后備存儲的方式宴抚,那么您的優(yōu)化現(xiàn)在會使事情(可能很多)變得更糟。
當(dāng)這些單元格進(jìn)入重用隊列時甫煞,一種常見的反模式是使UITableView/UICollectionView
單元格標(biāo)簽填充文本內(nèi)容菇曲。一旦單元被回收,標(biāo)簽的文本值很可能會有所不同抚吠,因此存儲它們是浪費的常潮。
要釋放潛在的兆字節(jié)內(nèi)存:
-
text
如果將標(biāo)簽設(shè)置為隱藏并且僅偶爾顯示,則取消標(biāo)簽楷力。 - 如果標(biāo)簽
text
顯示在UITableView/UICollectionView
單元格中喊式,則取消標(biāo)簽:
tableView(_:didEndDisplaying:forRowAt:)
collectionView(_:didEndDisplaying:forItemAt:)
2)始終從串行隊列開始,并且僅將并發(fā)隊列用作最后的選擇
常見的反模式是將不會影響UI的塊從主隊列分配到全局并發(fā)隊列之一萧朝。
例如:
func textDidChange(_ notification: Notification) {
let text = myTextView.text
myLabel.text = text
DispatchQueue.global(qos: .utility).async {
self.processText(text)
}
}
如果我們暫停我們的申請:
CDGCD為我們提交的每個塊創(chuàng)建了一個線程
當(dāng)您將dispatch_async
一個塊放入并發(fā)隊列時岔留,GCD會嘗試在其線程池中找到一個空閑線程來運行該塊。如果找不到空閑線程剪勿,則必須為工作項創(chuàng)建一個新線程贸诚》酵ィ快速將塊分配到并發(fā)隊列可能導(dǎo)致快速創(chuàng)建新線程厕吉。
請記住:
- 創(chuàng)建線程不是免費的械念。如果您要提交的工作塊很型分臁(<1毫秒),則在切換執(zhí)行上下文龄减,CPU周期和內(nèi)存弄臟方面創(chuàng)建新線程會很浪費项钮。
- GCD會很樂意繼續(xù)為您創(chuàng)建線程,可能導(dǎo)致線程爆炸。
通常烁巫,您應(yīng)該始終從數(shù)量有限的串行隊列開始署隘,每個串行隊列代表應(yīng)用程序的子組件(數(shù)據(jù)庫隊列,文本處理隊列等)亚隙。對于具有自己的串行分派隊列的較小對象磁餐,請使用來定位子組件隊列之一dispatch_set_target_queue
。
僅當(dāng)遇到瓶頸可以通過其他并發(fā)解決時阿弃,才使用您自己創(chuàng)建的并發(fā)隊列(不使用dispatch_get_global_queue
)诊霹,并考慮使用dispatch_apply
。
關(guān)于的注釋dispatch_get_global_queue
:
您從中獲得的并發(fā)隊列dispatch_get_global_queue
不利于將QoS信息轉(zhuǎn)發(fā)到系統(tǒng)渣淳,因此應(yīng)避免脾还。
一個報價由libdispatch”皮埃爾Habouzit:
dispatch_get_global_queue()
實際上,這是調(diào)度API提供的最糟糕的事情之一入愧,因為盡管在運行時做出了所有最大的努力鄙漏,但是在運行時沒有足夠的有關(guān)您的操作/參與者/…的信息來了解您的意圖并對其進(jìn)行優(yōu)化。 棺蛛。
有關(guān)libdispatch效率提示的更詳細(xì)概述泥张,請查看此出色的匯編。
3)可能沒有看起來那么糟糕
因此鞠值,您嘗試了盡可能多地優(yōu)化內(nèi)存使用率媚创,但是即使那樣,使用您的應(yīng)用程序一段時間后彤恶,內(nèi)存使用率仍然很高钞钙。
不用擔(dān)心,某些系統(tǒng)組件只有在收到內(nèi)存警告時才會釋放內(nèi)存声离。
例如芒炼,在低內(nèi)存情況下,UICollectionView
對-didReceiveMemoryWarning
(從iOS 13開始)做出反應(yīng)术徊,從內(nèi)存中清除其重用隊列本刽。
模擬內(nèi)存警告:
- 在iOS模擬器中,使用
Simulate Memory Warning
菜單項赠涮。 - 在測試設(shè)備上子寓,調(diào)用私有API(請勿與此一起提交到App Store):
[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)];
4)避免dispatch_semaphore_t
用于等待異步工作
這是一個常見的反模式:
let sem = DispatchSemaphore(value: 0)
makeAsyncCall {
sem.signal()
}
sem.wait()
問題在于,優(yōu)先級信息不會傳播到將由其makeAsyncCall
完成工作的其他線程/進(jìn)程笋除,并且可能導(dǎo)致優(yōu)先級倒置:
- 假設(shè)
makeAsyncCall
從主隊列進(jìn)行調(diào)用會將工作負(fù)載分派到QoS的數(shù)據(jù)庫隊列中QOS_CLASS_UTILITY
斜友。 -
QOS_CLASS_USER_INITIATED
由于來自主隊列的makeAsyncCall
調(diào)用dispatch_async
,DB隊列的QoS將得到提高垃它。 - 用信號量阻塞主隊列意味著它被困在等待正在運行的工作
QOS_CLASS_USER_INITIATED
(低于主隊列的工作QOS_CLASS_USER_INTERACTIVE
)鲜屏,因此優(yōu)先級反轉(zhuǎn)烹看。
附注XPC
:
如果您已經(jīng)使用過XPC
(在macOS上,或者您正在使用NSFileProviderService
)洛史,并且想要進(jìn)行同步調(diào)用惯殊,請避免使用信號量,而是使用以下命令將消息發(fā)送到同步代理:
- [NSXPCConnection synchronousRemoteObjectProxyWithErrorHandler:].
5)不要使用UIView
標(biāo)簽
這是一種不好的做法也殖,并表明有代碼異味靠胜。這也不利于性能。
我最近使用過代碼毕源,一旦點擊一個視圖浪漠,便會根據(jù)其標(biāo)簽值更改其子視圖的顏色。
UIKit使用來實現(xiàn)標(biāo)簽objc_get/setAssociatedObject()
霎褐,這意味著每次設(shè)置或獲取標(biāo)簽時址愿,您都在進(jìn)行字典查找,這可能會在熱循環(huán)中顯示在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>
編輯:充其量是微優(yōu)化响谓。我的收獲是:1)令人驚訝的-[UIView tag]
是基于關(guān)聯(lián)的對象,而2)僅在性能敏感的代碼中大量使用它才有任何影響省艳。
離別的想法
希望您今天閱讀這些提示后能學(xué)到新的知識娘纷。與往常一樣,請確保在進(jìn)行性能調(diào)整之前先進(jìn)行測量跋炕。
有問題嗎赖晶?有更多性能提示要分享嗎?在評論中讓我知道辐烂!
插頭
您可以在這里查看我整潔的Mac實用程序遏插。
編輯
- 感謝Paul Hudson在
UICollectionView
/中使用時,糾正了使標(biāo)簽內(nèi)容無效的位置UITableView
纠修。