性能是客戶端永恆的主題,咱們但願用戶在使用APP時,可以得到極致的性能體驗,關注內存佔用也正是由於這一點。git
(舒適提示:本片內容來源自WWDC21:Detect and diagnose memory issues)github
想了解更多WWDC2021內容的小夥伴,能夠閱讀我如下文章,歡迎多多交流和指正swift
一文帶你讀完WWDC21核心(新)技術點xcode
APP終極性能生存指南markdown
Faster application activationsapp
減小APP的內存佔用,能夠提升APP在後臺存活的機會,從而讓APP更快的被激活。ide
設備的內存空間是有限的,監控APP的內存使用狀況,可以防止系統爲了回收內存而主動終止APP。工具
這樣即便APP在後臺運行,也可以保存當前用戶的使用狀態,從而避免再次從內存加載致使的耗時。oop
Responsive experiencepost
更好的響應速度
策略性的將APP的內容加載到內存裏,可以避免在用戶使用APP時,因系統回收內存增長的等待耗時。
Complex features
讓用戶體驗更豐富的功能
像加載視頻、展現動畫,這些複雜的功能每每須要佔用更多的內存。所以有策略的使用內存,能夠避免在用戶使用APP的複雜功能時,被系統因內存佔用問題而終止。
Wider device compatibility
更好的設備兼容性
讓內存空間不太充足的老設備也能更好的使用APP的功能
Clean Memory
Dirty Memory
Clean Memory被分配,並寫入內容後成爲」髒內存「,髒內存包括:
Compressed Memory
壓縮內存特指髒頁(Dirty Pages)中暫未被訪問的部分(Unaccessed pages),會在訪問後解壓,成爲髒內存。
(下文會詳細介紹髒頁(Dirty Pages)的概念)
注:iOS中沒有內存交換(Memory Swap)的概念,你可能在使用Instrument工具時,見到過Swapped
字段,實際上所指的是Compressed Memory
內存佔用 = Dirty Memory + Compressed Memory
XCTest
經過單元測試和U測試中的XCTest來檢測開發環境的性能指標
XCTest能夠測量如下性能指標:
經過XCTest檢測內存使用狀況
func testSaveMeal() {
let app = XCUIApplication()
let options = XCTMeasuireOptions()
options.invocationOptions = [.manullyStart]
measure(metrics: [XCTMemoryMetric(application: app)],
options:options) {
app.launch()
startMeasureing()
app.cells.firstMatch.buttons["Save meal"].firstMatch.tap()
let savedButton = app.cells.firstMatch.buttons["Saved"].firstMatch
XCTAssertTure(savedButton.waitForExistence(timeout: 30))
}
}
複製代碼
XCTest在Xcode 13中新增的兩項收集性能狀況的診斷產物
開啓:enablePerformanceTestsDiagnostics YES
xcodebuild test
-project MealPannerApp.xcodeproj
-scheme PerformanceTests
-destination platform=iOS,name="iPhone"
-enablePerformanceTestsDiagnostics YES
複製代碼
Ktrace files
Ktrace file可以打開,並展現如下分析報告:
Memory graphs
Memory graph展現APP進程的地址空間的快照
咱們能夠生成任務開始(pre_XXX.memgraph)和結束(post_XXX.memgraph)兩個時機的內存快照,從而查看這一階段內存佔用的變化。
MetricKit & Xcode Organizer
檢測線上環境的內存指標
內存泄漏(Leaks)
最多見內存泄漏的緣由的是循環引用
經過命令行查看memgraph文件
堆大小問題(Heap size issues)
堆是進程地址空間中儲存動態分配的對象的段(section),有策略性的加載和釋放內存,能夠避免內存峯值太高引起OOM。
vmmap -summary
對memgraph文件進行分析咱們主要關心Dirty Size
和Swapped Size
兩列數據
對任務開始(pre_XXX.memgraph)和結束(post_XXX.memgraph)兩個時機的內存快照進行對比
能夠在下方看到按類聚合的,各種對象佔用內存的大小
從圖中能夠看到,non-object
類型的數據,佔用內存約13M。在Swift中,non-object
一般表明原始分配字節(raw malloced bytes)
打印memgraph文件中,類型爲non-object
且佔用內存超過500k的對象
打印對象的引用樹
能夠經過malloc_history -fullStacks
打印對象的分配調用棧
當不肯定是哪一個對象有問題時,能夠經過leaks --referenceTree
,自頂向下打印進程的全部內存中最可疑的對象
--groupByType
參數,按type聚合簡化打印結果。先介紹兩個概念:頁和」髒頁「
- 頁(Pages):是最小的獨立的內存單元
- 一旦頁中寫入任何數據,都會使整頁變成」髒頁「
當內存準備寫入新的數據時,系統會優先嚐試使用髒頁中的空閒內存,而立即將分配的內存過大時,即便空閒內存的總大小足夠,但其並非一段連續的內存空間,仍會開闢一個新的髒頁去寫入這些數據。
這些沒法被使用的空閒內存就是」碎片化內存「
再例如如下這種狀況:咱們分配的對象總共使用了4個頁,但每一個頁的佔用空間只佔50%。
當咱們釋放這些對象後,這些4個髒頁的內存空間仍有50%的碎片化內存。
最理想化的狀態應該將全部的分配的對象放在兩頁,以下圖:
這樣一旦釋放這些對象時,僅會留有兩個髒頁,而且不存在碎片化內存。
咱們的目標:
vmmap -summary xx.memgraph
查看碎片化內存狀況
DefaultMallocZone
平時開發者只須要關注這部分的內存空間,由於這是咱們進行堆分配默認結束的地方
MallocStackLoggingLiteZone
這個空間是全部堆分配結束的地方
使用Instruments工具中的Allocations track查看內存狀況
其中的Destroyed和Persisted分別對應上面所描述的內存釋放後的空閒內存(free memory)和仍未釋放的內存(remaining objects)
回顧下咱們的整體排查流程: