在這篇文章中,我會帶大家利用 Xcode Debugger,對 Apple 的 Maps iOS App 進行逆向工程 (reverse engineer),看看它是如何構建而成的!讀完這篇文章之後,你會學到:
- 如何為視圖階層 (view hierarchy) 除錯 (debug),以獲取當前的 UI 狀態和佈局 (layout) 信息。
- 如何為 Memory Graph 除錯,以獲取物件的階層,並了解實作特定 API 的步驟。
廢話少說,讓我們開始吧!
開始吧
在為 App 除錯之前,我們先要在 Mac 禁用系統完整保護 (System Integrity Protection)。簡單來說,這個設定在電腦上預設為啟用,以阻止我們在 Xcode 中為自己以外的 iOS App 進行除錯。
- 在修復模式 (Recovery Mode) 下重新啟動 Mac:Mac 開機後立即按下 Command (⌘) + R,當螢幕上出現 Apple 標誌時鬆開按鍵。
- 然後,選擇 “Utility”,然後打開 Terminal。
- 輸入
csrutil disable;reboot
,如此一來,就可以禁用系統完整保護,並重新啟動 Mac。
現在,我們已經準備好在 Xcode 對 Apple Maps App 進行逆向工程了。
備註:讀完整篇教學文章之後,記得要以修復模式重新啟動 Mac,然後在 Terminal 執行 csrutil enable; reboot
,來重新啟用系統完整保護,並重新啟動 Mac。
首先,打開 Xcode,建立一個新專案或是打開一個現有專案:
在 Xcode 模擬器或裝置上,打開 Apple Maps App:
接著,到 “Debug” tab 並選擇 Attach to Process by PID or Name
:
讓我們搜尋 “Maps” process 並點擊 “Attach”:
進度會從 “Attaching to Maps …” 變成 “Running Maps …”,這代表除錯器已經連接到 App 了。現在,讓我們看看下一個步驟吧。
為視圖階層除錯
打開了 Maps 又連接好除錯器後,點擊 Debug View Hierarchy
按鈕來顯示 UI:
我們來了解 Bottom Sheet 的架構,以及 UI 和 Behavior 是如何實作的:
讓我們揭示 UI 階層,並展開一些視圖:
在這裡,我們可以看到 Apple 用了容器 (container) 和子視圖控制器 (child view controller),來把一個螢幕分成更小、而且可重用的部分。
而且,我們可以像其他自己的 App 一樣,偵測自動佈局 (Auto Layout) 的警告:
更重要的是,當我們在專案的程式碼中搜尋需要的類別時,可以找到每個視圖或視圖控制器的名字,這一點非常有用:
接著,讓我們來看看 Memory Graph,以了解 Apple 用了什麼 API 來建立前文所說的 Bottom Sheet Behavior。
為 Memory Graph 除錯
首先,點擊 Debug Memory Graph
按鈕:
然後,我們會看到當前存在的物件:
我們可以從視圖階層中,看到 Bottom Sheet 視圖控制器的名稱是 SearchViewController
。讓我們在 Debug Navigator 中搜尋它:
點擊 SearchViewController
,就會在右邊看到 Dependency Graph:
如此一來,我們會知道有一個名為 UIFormSheetPresentationController
的類別,它負責顯示 Botton Sheet。點擊這個類別來顯示一個 Memory inspector
,我們就會看到詳細的階層。
如你所見,負責 Bottom Sheet 的物件是 UISheetPresentationController。這是從 iOS 15.0 和 Swift 5.5 新增的 API。我們查閱文檔,就可以發現它使用 detents
陣列來控制 Bottom Sheet 尺寸的設定。
我們也會發現,現在只有兩個可用的 detents
,就是 .large()
和 .medium()
:
然而,Apple 的 Maps App 在不同位置明明有 .medium()
和 .large()
以外的 Bottom Sheet,例如以下這個截圖,Bottom Sheet 可以顯示在螢幕上更低的位置:
為什麼會這樣呢?讓我們來拆解原因吧!在 Debug Navigator 中搜尋 “Detent” 關鍵字,會找到 detents
陣列:
如此印出陣列的 Description:
我們會看到 Apple 用了 3 個 .custom
detent:
我們就會發現 UISheetPresentationController
的部分是 Private 的,而且開發者尚未可用這個控制器。不過,相信這個 API 很快會更新,讓我可以建立客製化的 Detent。
我們已經成功透過視圖階層來獲取 UI 信息,以及透過 Memory Graph 了解物件的關係。另外,我們也可以在任何 App 上使用 Instruments
,來監察網絡請求 (network request)、偵測記憶體洩漏 (memory leak) 等等。
總結
如果你想在 iOS 15 建立 Bottom Sheet,可以參考我這篇關於 UISheetPresentationController 的文章。謝謝你的閱讀。