Instruments 自 2010 年釋出之後,一直不溫不火,去年也沒有任何更新值得去關注。但在一年的沉澱後,Instruments 團隊在今年終於有了個可以令人期待的釋出。本文是針對 Session 410:Creating Custom Instruments 的解讀。
Instruments 10
Instruments 是一款強大且靈活的效能分析工具,整合在 Xcode 的開發者工具集中。我們能夠用不同的 Instrument 來分析測試各種各樣的效能問題,比如 Leaks 來查記憶體洩漏問題,Time Profiler 來分析 App 的頁面卡頓問題等等。那麼今年蘋果對 Instruments 做了哪些更新呢?從官方的 Xcode 10 Release Notes 裡我們可以看到有這幾點:
- 開發者能夠根據不同需求,靈活快速地基於 os_signpost 建立和釋出屬於自己的 Instrument;
- Instruments 能夠自動展示你程式碼裡通過 OSLog signposts 標記的資料;
- System Trace 裡為執行緒分析新增了一個影像模式;
- 由於 Instrument 10 中所有的自定義工具都會基於 os_signpost 來完成,原本基於 dtrace 的自定義 Instrument 將不被繼續支援。
其中最重要的一點便是自定義自己的 Instrument 了,雖然以往的版本中也可以根據自己的需要去建立,但步驟麻煩且圖形化支援簡陋,並且因為資料採集是基於 DTrace 的,導致真機上並不能夠使用,蘋果自身也並不鼓勵大家去做這個自定義。但 Instruments 10 針對這點這次可謂下了苦功——全新基於 os signpost 的架構能夠支援所有平臺,『標準介面(Standard UI)』 和 『分析核心(Analysis Core)』 使得自定義 Instruments 變得更加靈活而且便利。這個 Session 圍繞如何建立一個自定義的元件,分為以下四個大部分展開:
- 為什麼要建立自定義的工具
- Instruments 10 的架構體系
- Instruments 自定義工具的初級、中級以及高階應用
- 自定義工具的最佳實踐
其中將會涉及到一些關於專家系統(Expert System)和 CLIPS 語言的知識,如果有興趣的話,可以先了解下。本文相對於 App 端開發者,可能會更適合一些測試人員,尤其是對效能測試方面比較感興趣的同學。
為什麼要建立自定義的工具
我們都知道 Instruments 中已經內建很多方便好用的工具了,這些工具蘋果已經通過文件模板建立好並整合在 Instruments 的啟動選擇介面裡。比如上文中提到的 Leaks 和 Time Profiler 相信很多人都有使用過,下圖的就是 Instruments 10 的啟動介面,與以前版本差別不大。
但是這些內建工具的使用其實很大程度上是基於我們對自己程式碼的充分了解,那麼如何讓不懂這部分程式碼的人也能夠很快了解 App 的執行資料呢,比如測試 App 的網路層效能資料?這時候,一個良好的 Instruments 自定義工具或許就是那個答案,它能夠出色地描述 App 執行情況。另外,如果想為內建的 Instruments 工具換個介面,或者系統提供的工具不夠用了,這些問題也能夠通過建立自定義 Instruments 工具來得到解決。Instruments 10 的架構體系
架構的演變
介紹 Instrument 10 的架構之前,我們先回顧下以往蘋果是怎麼維護 Instruments 元件的。
最早一個版本的 Instruments 拖拽不同的 Library 到 Instruments 點選 Record 的後便可以執行一系列的效能工具,但這些基礎庫並沒有提供良好的方式來繼續更新維護。早期蘋果通過繼承現有的工具完成迭代,但每個工具都有自己的資料記錄和分析模式,他們不得不設計一個自定義的儲存機制來獲取追蹤到的資料,然後再設計一個自定義的介面來整合其他新增的應用。這種方式隨著不斷地迭代,維護成本越來越高,每新增一個新的功能,就必須要修改之前最原始的那幾個工具庫,這樣在舊的架構上完成自定義 Instrument 的將會變得極其痛苦,最終蘋果放棄了舊的架構,從而也才有了現在的 Instruments 10。 有了前車之鑑,蘋果在新的架構設計上就考慮到了建立新的 Instrument 怎麼才能夠更易維護的問題。全新的 Instruments 架構分為 『標準介面(Standard UI)』 和 『分析核心(Analysis Core)』 兩個標準元件,二者分工不同但卻緊密連線。『標準介面』元件負責使用者互動,而『分析核心』負責資料儲存和統計分析。現在 Instruments 10 中的工具庫已經都是基於這個所建立,我們甚至完全可以自己基於這兩個標準元件做一個與蘋果內建工具一模一樣的東西。介面介紹
從上圖可以看出,Instruments 10 的介面變化並不大,與以往的版本結構差不多。但正如前面提到的,架構變化使得現在的介面資料都將由『分析核心』提供,我們有必要來了解下其資料結構是怎麼設計的。下圖就是一張資料表的格式。 『分析核心』以表的形式將資料傳遞給『標準介面』,而這些表可以通過一個簡單的 Table Schema 定義。Table Schema 就和我們 OC 或者 Swift 中的類一樣,描述了這個 Table 是什麼。也正是出於這種設計,我們接下來才能夠使用 Table Schema 來完成自定義 Instrument 的建立。Instruments 自定義工具的初級、中級以及高階應用
初級應用
有了足夠的理論知識後,我們可以嘗試下簡單的自定義工具建立了。
Instruments 10 建立自定義工具和建立工程的步驟幾乎一樣,只是最後 Project 模板需要切換到 macOS 欄下,選擇 Instruments Package,並填寫好工程名等資訊即可。 工程建立完成後,在左側導航中已經自動生成了一個字尾為.instrpkg
的 XML 檔案,所有的配置都會在這個 XML 檔案中完成,其中預設已經幫我生成了包含一些基礎資訊。接著我們要做的就是按照我們的需要去寫這個配置文件了。
1、匯入需要使用的 Schema
<!-- MARK: 匯入你需要使用的 schema -->
<import-schema>tick</import-schema>
複製程式碼
2、完成 Instrument 的『標準介面』和『分析核心』配置
<!-- MARK: 匯入你需要使用的 schema -->
<import-schema>tick</import-schema>
<instrument>
<!-- MARK: 這個 Instrument 的基本資訊 -->
<id>com.Parsifal.TicksDemo</id>
<title>Ticks</title>
<category>Behavior</category>
<purpose>Instrument drawing ticks every 100ms</purpose>
<icon>Generic</icon>
<!-- MARK: 描述的表資料,將會由 分析核心 最終完成儲存和解析提供給 標準介面 模組 -->
<create-table>
<id>tick-table</id>
<!-- 定義了每列的資料 -->
<schema-ref>tick</schema-ref>
</create-table>
<!-- MARK: 圖形檢視上面展示(可選) -->
<graph>
<title>Ticks</title>
<lane>
<title>Lane</title>
<!-- 這裡就是上面你定義的 table id-->
<table-ref>tick-table</table-ref>
<plot>
<value-from>time</value-from>
</plot>
</lane>
</graph>
<!-- MARK: 這裡描述你需要展示在詳情檢視的資料 -->
<list>
<title>Ticks</title>
<!-- 這裡就是上面你定義的 table id-->
<table-ref>tick-table</table-ref>
<column>time</column>
</list>
</instrument>
複製程式碼
至此我們便已經完成了所有的編碼工作了,在編寫過程中,我們還會發現蘋果為我們準備了很多程式碼片段,來幫助完成這些配置,並且編譯期間還會對程式碼進行檢查,報出的錯誤資訊也很方便我們對其進行除錯。編譯執行後,在彈出的 Instruments 選擇視窗裡選擇 Blank 就可以在測試介面的 Library 中發現我們自己定義的工具了,直接拖入 Instruments 裡就能夠像使用其他內建工具一樣執行。
另外,我們還能夠在 Instruments -> Preferences -> Packages 裡找到我們生成的自定義工具包。中級應用
這一部分我們會介紹一些『標準介面』和『分析中心』裡更詳細的內容。
標準介面
『標準介面』模組裡為我們提供了很多簡單又好用的元素,使用這些元素能夠讓我們建立出非常酷炫又實用的 Instruments 工具。接下來列舉一些常用的元素進行介紹,更多的元素還待蘋果正式釋出 Instruments 10 後大家一起探索。
圖形通道皮膚
- <plot>:為圖形檢視上面劃出一個單獨的資料通道,需要提供一個值來定位縱列,如我們 Ticks 例子中的 time;
- <plot-template>:這個和 plot 差不多,區別在於它會自動為每個 instance-by 值建立一個資料通道;
- <histogram>:為給定的時間片段生成柱狀圖,如 System Trace 元件中使用的那樣;
詳情皮膚
- <list>:建立一個列表,常見的各種內建工具都會有的;
- <aggregation>:建立一個總計檢視,統計總和和平均數等,使用這個元素的時候,縱列就是各種函式了,如
sum
、average
和count
等,另外這個元素還有個hierarchy
屬性,能夠為不同的縱列設定外輪廓,非常適合於大量資料的展示; - <calltree>:這個就顧名思義了,呼叫棧的檢視;
- <narrative>:展示一個描述工程型別(engineering type)的檢視;
分析核心
這一部分我們首先會重點談談『分析核心』是如何收集資料和處理資料的,這一過程主要包含以下三個步驟。然後會介紹一些『分析核心』中的相關概念以助於我們在配置表中使用它們。
1、簡化
在開始測試記錄之前,『分析核心』會先處理我們配置好的各種表並且為其申請儲存空間,有相同的 Schema 、屬性並且定義為相同資料的表將會被對映為同一個 store。2、搜尋
接著每個 store 將會開始嘗試尋找資料的提供者。
有時候能夠資料流直接找到。 但有時候需要使用 Modeler 進行合成,Modeler 可以要求他們自己的輸入訊號,而這些輸入訊號也能夠被作為 Modeler 的輸出訊號或者通過資料流被直接記錄。3、優化
當我們從各個 store 中獲取到資料來源後,在『分析核心』中就開始了一項稱為『Binding Solution』的工作,第三步就是優化這個工作流。
上圖展示了 Instruments 的這一工作流程,其中 Thread Narrative 便是它的『Binding Solution』。一些重要的概念:
Binding Solution:Instruments 裡是通過 Thread Narrative 實現的,它有以下兩個有點
- trace-wide;
- Instruments 會在我們將工具拖入測試介面的時候,開始計算尋找最佳可能的記錄方案來最小化在目標上的影響;
Schemas:我們建立表的時候就必須要指定一個 Schema,比如我們第一個 Demo 中的 tick
- 目前 Instruments 裡已經定義有超過 100 個 Schema 供我們匯入使用了;
- 包含在 Instruments 包中;
- 可以通過編譯設定連結其他的 Instruments 包,在編譯期會進行型別校驗;
- 提供了不同的構建模組;
Modelers:前面提到過,Modeler 可以幫助我們合成不同的資料,關於它的有以下幾個常用的元素可在 XML 配置檔案中使用。
- <modeler>:建立一個 Modeler,幫助我們做資料型別轉換的工作;
- <point-schema>:定義一個可以用來儲存點(無時間片段,即某個時間點的資料)的 schema;
- <interval-schema>:定義一個可以用來儲存定距資料(有時間片段,一段時間內的資料)的 schema;
PS:Modelers 其實是一個由 CLIPS 編寫,非常強大而且高階的小型專家系統(Expert System)。它可以指定自己需要哪些輸入訊號來告知 Binding Solution 怎麼完成剩下資料圖形的填充工作。關於這一部分我們將會在“高階應用”裡詳細說明。
最後,具備定義一個 schema 的能力是很重要的。今年新發布的 OS signpost API 賦予了我們一個很棒的把資料匯入到 Instruments 中的方式,而且蘋果為我們建立了一些快捷方式來使用它,比如在 XML 配置表中敲下 <os-signpost-interval-schema> 元素,就會自動生成如下程式碼片段。
<os-signpost-interval-schema>:定義了一個儲存定距資料的 schema,而且資料由 os_signpost API 提供。這就意味著我們可以程式碼的任意地方使用 os_signpost API 來將我們需要被測試的資料直接匯入到 Instruments 中。在建立這個元素的時候, Xcode 會自動生成相關的程式碼片段以幫助我們來完成 Modeler 的建立。 這個 API 和 os_signpost 結合使用起來就如下:
//使用這個 os_signpost 可以再我們程式碼的任意地方將你的資料傳遞給 Instruments,但必須記得 begin 和 end 成對使用
os_signpost(.begin, log: parsingLog, name: "Parsing", "Parsing started SIZE:%ld", data.count)
// Decode the JSON we just downloaded
let result = try jsonDecoder.decode(Trail.self, from: data)
os_signpost(.end, log: parsingLog, name: "Parsing", "Parsing finished")
複製程式碼
<!-- MARK: 使用這個元素就能建立從 os_signpost 獲取資料的 schema -->
<os-signpost-interval-schema>
<id>json-parse</id>
<title>Image Download</title>
<subsystem>"com.apple.trailblazer"</subsystem>
<category>"Networking"</category>
<name>”Parsing"</name>
<start-pattern>
<!-- MARK: 這裡根據我們設定好的條件輸出資訊 -->
<message>"Parsing started SIZE:" ?data-size</message>
</start-pattern>
<column>
<!-- MARK: Engineering Type 對應的助記詞 -->
<mnemonic>data-size</mnemonic>
<title>JSON Data Size</title>
<!-- MARK: 填寫的是 Engineering Type 裡定義的資料型別,比如這邊看的是記憶體相關的,就會有 size-in-bytes -->
<type>size-in-bytes</type>
<!-- MARK: 由 CLIPS 語言編寫的表示式作為這個列的值 -->
<expression>?data-size</expression>
</column>
</os-signpost-interval-schema>
複製程式碼
在中級這一部分,蘋果還為我們演示了一個 os_signpost API 使用示例。該示例中對一個展示圖片的列表頁做了圖片測試,監控了每一個 cell 圖片的下載情況。由於涉及到的知識點上面都已經描述過了,具體示例的完成過程這邊就不再贅述,相信通過視訊大家能看得更直觀。其中演示過程中有提到幾點比較值得注意的,這裡重點抽出來說明下。
Instrument Inspector:如果有檢視資料儲存、Modeler 和 Schema 需求的話(除錯我們自定義工具的時候可能就會用到),這個功能就能夠滿足你,在 『Instrument-> Instrument Inspector』裡或者 『cmd+I』 就能開啟。高階應用
這一部分將會著重介紹我們怎麼去建立和定義 Modelers,並且簡單介紹下怎麼用 CLIPS 搭建基本的專家系統。
探祕 Modeler 的內部世界
從概念上說,Modeler 是在 Instruments 中是接受一系列輸入資料並轉化產生輸出資料的一個簡單機器。Modeler 的輸入資料往往是按時間排序的。當我們把不同的輸入資料表給 Modeler 時,這些資料將會先被按時間排序然後合併進一個時間排序佇列裡。時間佇列將會依次把資料再傳遞給 Modeler 的工作記憶體,Modeler 就會根據工作記憶體的進展推理要產生什麼樣的資料並寫入到輸出表中。我們通過一個簡單的例子來探祕 Modeler 世界。我們模擬這樣一種情況,我的程式碼裡有部分危險的操作容易觸發程式問題,我們的目標就是在程式出現問題的時候,找到是哪個操作導致的。那麼我們可以定義下圖中的三個 schema,前兩個作為輸入項,最後一個是輸出項。
將這個流程,我們已一個更加形象直觀的時序圖來展示,其中虛線代表著 Modeler 自己的時鐘:
其中,圖上我按照時間順序,標註除了 4 個值得注意的節點:
(1)這時的 App 出於正常執行狀態,沒有任何資料傳輸給 Modeler 的工作記憶體中,Modeler 的時鐘並沒有開始走;
(2)此時虛線到達我們的第一個 input schema 觸發的節點(開始有危險操作的節點),在這裡 Modeler 的工作記憶體中正式開始接收到資料,Modeler 的時鐘從這個點開始計時。
(3)這是第二個 input schema 的觸發節點(我們 App 出現問題的節點)。這裡值得一提的是,Modeler 是很機智的,它有自己的邏輯,它能區分出在這個時間節點之前的危險運算元據意義不大,而這個節點開始到 app-on-fire 這個節點結束前的資料才是我們所需要的。
(4)到這最後一個節點,所有的輸入資料都已經傳輸完畢了,Modeler 的時鐘與這些輸入資料沒有交集, 它推斷出這些輸入資料已經不再被需要了,因而把它們從工作記憶體裡移除並且產生最終的輸出資料。
可以回顧整個過程,可以總結出兩點:
- Modeler 的時鐘起始時間總是第一個輸入資料觸發的時間
- 只有與 Modeler 當前時鐘有交集的輸入資料才會被 Modeler 保留在工作記憶體
這樣一種機制能夠幫助我們更清晰地定位到問題的所在,而不被那些無意義的舊資料干擾。這樣一種機制是怎麼實現的呢?答案就是我們接下來要說明的 『生產系統(Production System)』。
生產系統
Modeler 中對『工作記憶體(Working Memory)』的邏輯支援是來自我們定義的 『生產系統』。『生產系統』可以由一系列的 『規則(Rules)』 來生成,它在『工作記憶體』中為 facts(CLIPS 語言中的專業術語,可以理解為字面意思事實,推理得到的事實) 服務。『規則』由三部分組成—— LHS => RHS
,左邊部分 + 操作符(=>)+ 右邊部分。左邊部分是一個在『工作記憶體』中啟用規則的條件,右邊部分則是啟用後要執行的行為。右邊的行為包含為輸出的資料表建立新的一行,也可以是在建模時推斷出一個新的『fact』到『工作記憶體(Working Memory)』裡。結合之前上面的時序圖,我們可以想到『facts』 來源於兩個地方。一個是我們看到的資料輸入表,這些是根據『規則』自動推理出的。另一個可以是來自於右邊部分的行為,這些是通過 CLIPS 中 assert
命令主動推理的。如果我們打算建立我們自己的『facts』,CLIPS 提供了 fact 模板,模板允許為『fact』提供資料結構和做一些基礎型別的檢查。接下來就是介紹怎麼來定義規則了。
規則和 CLIPS
我們可以使用 CLIPS 語言定義一些規則。先回顧下上面那個例子的時序我們是怎麼通過 CLIPS 設定規則實現的:
(defrule MODELER::found-cause//規則名
//LHS,左邊部分指定規則啟用的條件
(playing-with-matches (start-time ?t1) (who ?object))
(app-on-fire (start-time ?t2))
(test (< ?t1 ?t2))
=>
//RHS,右邊部分為推理產生一個 fact
(assert (cause-of-fire (who ?object)))
)
(defrule RECORDER::record-cause//規則名
//LHS,左邊部分未設定啟用的條件
(app-on-fire (start-time ?start))
(cause-of-fire (who ?object))
(table (table-id ?t) (side append))
(table-attribute (table-id ?t) (has schema started-a-fire))
=>
//RHS,右邊部分為產生輸出資料
(create-row ?t)
(set-column time ?start)
(set-column who ?object)
)
複製程式碼
其中,record-cause
這條規則定義了,如果滿足以下三個條件,則這次生產會推理出一個 『fact』並被壓入『工作記憶體(Working Memory)』裡。
- 一個物件 t1 這個時間在
playing-with-matches
中產生; app-on-fire
在 t2 這個時間被觸發了;- t1 的發生時間早於 t2;
而 record-cause
這條規則定義了,如果滿足了以下四個條件:
- App 在一些起始時間“著火”;
- 知道“著火”的原因和相關的物件(規則 1 裡可以拿到);
- 有一張繫結了 Modeler 輸出側的資料表;
- 這張資料表關聯著我們之前定義的
start-a-fire
schema;
則在輸出資料表中建立一行資料,並且設定時間和設定『左邊部分』捕獲到的中引起“著火”的值。
通過以上兩個簡單的規則,我們便基本上建立了最早的『專家系統(Expert System)』。使用定義好的兩條規則,就可以用來尋找我們 App 記憶體在的一些問題。或許你也已經注意到,這些規則要麼是為 Modeler 預先設定的,要麼是為 Recorder。CLIPS 裡把他們叫做『模組』,並且支援把規則分組和控制規則的執行順序。比如說,如果你所有的規則都定義在了記錄模組裡生產輸出資料表,那麼你將不會在 Modeler 模組推斷的時候寫入任何的輸出資料。因為在 Modeler 模組 裡的規則必須在記錄模組裡的規則之前執行。
邏輯支援
在前面探祕 Modeler 內部世界的時候,我們提到過邏輯支援。邏輯支援一般與純推理規則關聯在一起。比如說,如果 a 和 b,那麼 c。通過我們的『生產系統』中說,就是如果 a 和 b 不存於『工作記憶體』了,那麼 c 就要被自動地被回收。這樣我們就可以說 c 是被 a 和 b 在邏輯上支援了。這樣的一種能力對於『專家系統』將『工作記憶體』維持在較低的水平很重要,因為它能很好地控制資源開銷。同時,把無效的 facts 從『工作記憶體』裡及時移除也是很重要的。如果 a 和 b 失效了,那麼 c 也應該被移除。這樣的需求在 CLIPS 裡實現會很簡單,只要通過 logical
命令就可以,如下面的程式碼。
(defrule MODELER::found-cause
//通過 logical 命令實現邏輯支援
(logical (playing-with-matches (start-time ?t1) (who ?object))
(app-on-fire (start-time ?t2))
)
(test (< ?t1 ?t2))
=>
(assert (cause-of-fire (who ?object)))
)
複製程式碼
最佳實踐
前面洋洋灑灑說了那麼多,最後這一部分我們主要談一下在開發自定義 Instruments 工具時有哪些最佳實踐。
多寫一個 Instrument
這句話不是建議我們去不斷練習寫自定義的 Instrument 工具,它說的是我們應該把 Instrument 功能做得細粒度化。舉個例子,我們已經有了一個自定義的 Instrument 工具,但這個工具的功能並不能滿足現在的需求,我們需要在它的基礎上去增刪一些詳情或者圖形的資料展示。這樣一種場景,我們會有兩個方式去做,第一個在原有的基礎上繼續迭代,第二個是重新再寫一個滿足我們當前需求的 Instrument。如果選擇了第一種方案,這會導致這個 Instrument 元件變得不再純粹,雖然功能是更多了,但相應的 Instruments 也變得更加複雜。所以蘋果會更推薦我們用第二種方案,重新寫一個符合我們當前需求的 Instrument。如果我們需要組合使用不同的 Instrument,在 Instrument 元件庫中拖拽對應的 Instrument 到我們的文件中即可。如果這類組合使用場景很多,我們也可以用 “File -> Save As Template” 儲存為模板以供接下來使用。儲存模板的將會展示在我們 Instruments 的啟動頁面,比如內建的 Leaks、Activity Monitor 和 Time Profiler 等一樣。另外這些模板,也能夠很方便的在我們的包中複用,使用 <template> 元素即可。
實時模式很困難
實時模式指的是資料的獲取、分析、到最終通過 Instruments 的介面能夠實時地被展示出來。蘋果現在很難完成這種實時互動主要有兩個原因。第一個原因是,實時模式的完成需要一些額外的支援,但現在蘋果並沒有足夠充裕的時間去做;第二個更重要的原因便是定距型資料(Interval Data,統計學上的概念,具有間距特徵的資料,可作加減計算)。定距變數只有起始和結束這個階段完成時,才能被加到資料表中和被『分析核心』所獲取。在啟動測試記錄後,Instruments 將會收到一群定距型變數的開區間,當定距變數沒完成閉區間時,Modeler 的時鐘並不會往前走(Modeler 裡的資料是按時間排序的)。這樣的機制就會導致了一個問題,如果某個定距變數的開閉區間拉的很長,那麼 Modeler 就會一直停滯在那兒等待。但如果這個時候使用者點選了停止記錄按鈕,所有的開區間定距變數就會關閉,一切都會恢復正常,資料將會被填充到 Modeler 裡。應該能感覺到,這是一個很不好的使用者體驗。一旦我們遇到這種情況時,我們有兩個選擇。第一個選擇是配置我們的 Instrument 使它不支援這種實時模式,這個可以通過 <limitations> 元素實現。第二個選擇避免資料出現這種長時間的開區間,例如使用 <os-signpost-interval-schema> 替代 <interval-schema>。
使用『最後 5 秒記錄模式』
當建立一個用來測試含有大量輸入資料的 Instrument 時,使用『最後 5 秒記錄模式』則是我們目前最好的選擇。這一選項我們可以在 “File -> Recording Options” 裡找到。如下圖:
選擇這一模式意味著將會有一個快取池來儲存資料,而不是實時地將資料傳輸給 Instruments。快取池機制極大地提高了效率,在 5 秒記錄模式下甚至能把 signpost 型別資料傳輸速度提升 10 倍。這個機制相應的代價是,我們只能看到最後 5 秒的資料。但對於大批量資料的 Instruments 而言,這依然是一個不錯的選擇。總結
Instruments 10 提供了太多建立自定義 Instrument 的可能性了,不過這同樣需要我們花點時間來學習掌握新一套的編寫方式。對於大多數客戶端開發者來說,或許並不會用到上面談的這部分技能,但對於測試團隊來說,這無疑為 iOS App 的效能測試又開啟了一扇窗。相信在未來的一年裡,圈子內會陸陸續續地有高質量自定義 Instrument 的產出,讓我們一起期待。
檢視更多 WWDC 18 相關文章請前往 老司機x知識小集xSwiftGG WWDC 18 專題目錄