作者:imyzf本文將為大家介紹自動化控制 iOS 模擬器的原理,為開發基於 iOS 模擬器的前端除錯方案提供幫助。
我們在開發 iOS App 內的前端頁面時,有一個很大的痛點,頁面無法使用 Safari Inspector 等工具除錯。遇到了問題,我們只能想辦法加 vConsole,或者注入 Weinre,或者盲改,實在不行就找客戶端同學手動打包除錯,總之排查問題的路途非常艱難。
在參考了 RN 和 Weex 等跨平臺框架的開發工具後,我們發現使用模擬器除錯是解決該問題的很好方法,我們將前端頁面放到模擬器的 App 中執行,蘋果就不會對其有限制,允許我們使用 Safari Inspector 除錯了。
Safari Inspector 是和 Chrome Devtools 類似的除錯工具,由 Safari 瀏覽器自帶,支援以下功能:
- 檢查頁面元素
- 檢視網路請求
- 斷點除錯
- 儲存管理(Local Storage,Cookies 等)
- ……
這些功能是 vConsole、Weinre 等工具無法比擬的,可以幫助我們快速定位問題。
基於這些原理,我們內部已經開發了一款工具,部分功能視訊可以點此預覽。但由於該工具和內部業務耦合較深,目前暫無開源計劃。
前提條件
介紹這套方案之前,我們需要了解一下方案的前提條件:
- 裝有 macOS 和 Xcode 的電腦:由於蘋果的限制,模擬器和 Xcode 只能在 macOS 上執行。Xcode 直接在 App Store 中安裝即可,十分簡單,無需其他操作。
- 為模擬器構建的 App 包:由於模擬器是基於 x86 架構的,需要客戶端開發同學提供為模擬器構建的包,和在手機上安裝的包會有所不同。
- 支援 URL Scheme 喚起的 App:承載前端頁面的 App 必須支援用協議喚起並開啟頁面,才能用工具實現自動化,否則只能在 App 內手動點選相關鏈路開啟頁面。
總體流程
我們的模擬器除錯方案整體流程如上圖所示:
- 獲取裝置列表,提供給使用者選擇
- 檢查模擬器狀態,如果沒有啟動,就啟動該模擬器
- 檢查是否安裝對應的 App,如果沒有安裝,就下載安裝包進行安裝
- 啟動 App,並開啟需要除錯的頁面
- 根據頁面型別,使用對應的工具進行除錯(例如 Safari Inspector)
核心工具
我們在實現本方案時,主要基於以下工具:
-
xcrun:Xcode 提供了一個命令列工具
xcrun
對開發相關的功能進行控制,是一系列工具的集合。 -
simctl:
xcrun
提供了一個子命令simctl
用於控制模擬器,提供了模擬器的啟動、關閉、安裝應用、開啟 URL 等功能。可以通過直接執行xcrun simctl
檢視幫助文件。 -
node-simctl:由 Appium 提供的
simctl
工具的 JS 封裝。由於前端的方案一般都是基於 node.js 開發的,所以可以使用 node-simctl 包更方便地控制模擬器。不過由於node-simctl
只提供了部分功能的封裝,我們依然需要手動呼叫xcrun
命令來實現更多功能。
模擬器控制
在本方案中,最重要的部分就是對模擬器的控制。
前期準備
使用者通過 App Store 安裝完 Xcode 後,第一次執行需要同意蘋果的許可協議,然後自動安裝一些元件,之後才可以正常使用。為了提高易用性,我們希望自動處理這個過程,而不是告訴使用者,安裝 Xcode 後要採取一些操作。
首先我們可以嘗試執行一次 xcrun simctl
命令,如果使用者第一次執行,錯誤資訊中會提醒使用者手動執行xcodebuild -license
接受許可,所以我們可以在錯誤資訊中搜尋xcodebuild -license
字串,如果有找到,就自動動執行xcodebuild -license accept
命令,幫助使用者自動接受許可。這裡要注意的是,執行該命令需要 root 許可權,可以使用sudo-prompt
等包提權執行命令。
獲取裝置列表
我們可以直接使用 node-simctl 的getDevices()
函式獲取本地安裝的所有裝置列表,比呼叫命令列更方便,可以直接獲取到一個物件,不需要自己解析,物件部分結構如下:
{
'13.4': [
{
sdk: '13.4',
dataPath: '/Users/xx/Library/Developer/CoreSimulator/Devices/xxx/data',
logPath: '/Users/xx/Library/Logs/xxx',
udid: 'C1AA9736-XXX-YYY-ZZZ-2A4A674B6B21',
isAvailable: true,
deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro-Max',
state: 'Shutdown',
name: 'iPhone 11 Pro Max',
platform: 'iOS'
}
]
]
這裡不僅包含了 iPhone,還有 Apple Watch 和 Apple TV 等裝置,我們可以遍歷返回結果,通過name
欄位進行過濾,因為一般我們只需要在 iPhone 中進行除錯。
啟動裝置
首先我們要判斷裝置是否已經啟動,我們可以通過 xcrun simctl bootstatus ${deviceId}
命令獲取裝置狀態(這裡的 deviceId 即上面獲取裝置列表得到的udid
),但是如果裝置沒有啟動,這個命令會一直等待,不會退出,所以我們可以通過這個特徵,基於命令是否超時(例如 1000ms 未返回結果)來判斷裝置是否啟動。
接下來,就可以直接用xcrun instruments -w ${deviceId}
命令,啟動對應的裝置了。
程式碼示例:
let status = '';
try {
status = execSync(
`xcrun simctl bootstatus ${deviceId}`,
{ timeout: 1000 }
);
} catch (error) {
// 如果模擬器未啟動,會一直等待,然後超時 kill,丟擲一個 ETIMEDOUT 異常
if (error.code !== 'ETIMEDOUT') {
throw error
}
}
// 檢查是否啟動
if (status.indexOf('Device already booted') < 0) {
console.log('正在啟動模擬器……')
execSync(`xcrun instruments -w ${deviceId}`)
}
安裝 App
模擬器的安裝包是一個以.app
為結尾命名的資料夾,和 macOS 應用類似,而不是 iPhone 真機上安裝使用的.ipa
包。所以安裝包需要先用zip
等工具進行打包上傳到伺服器,安裝前下載到本地解壓,使用 node-simctl 的installApp()
方法進行安裝。
App 檢查和啟動
對於使用者是否安裝了 App,其實是在通過分析喚起 App 的錯誤資訊來判斷的。如果 App 未安裝,會在喚起的時候會報錯,錯誤資訊中包含了domain=NSOSStatusErrorDomain
字串,表示 App 沒有安裝,這個時候我們去呼叫上面的安裝流程即可。
整個流程中最重要的一步是如何將我們的頁面在 App 中開啟,實際上很簡單,只需要 App 本身支援類似 cloudmusic://open?url=xxx
這樣的 URL Scheme 即可。我們通過 node-simctl 的openUrl()
方法直接呼叫 scheme,模擬器便會幫我們啟動關聯的 App,然後需要 App 根據接收到的 Scheme 引數,幫我們開啟需要除錯的頁面。
程式碼示例:
try {
await simctl.openUrl(deviceId, url)
} catch (error) {
// 沒有安裝 App,開啟協議會報 NSOSStatusErrorDomain
if (error.message.indexOf('domain=NSOSStatusErrorDomain') >= 0) {
await simctl.installApp(deviceId, appPath)
await simctl.openUrl(deviceId, url)
} else {
throw error
}
}
啟動偵錯程式
在模擬器中開啟除錯頁面以後,對於 RN 頁面,我們可以用 React Native Debugger 等工具除錯。對於 H5 頁面,我們可以從 Safari 選單中開啟 Inspector除錯(如果沒有“開發”選單,請在 Safari 偏好設定 - 高階 - 選中在選單欄中線顯示“開發”選單
)。
當然這一步也可以實現自動化,需要藉助 Apple Script 搜尋 Safari 選單中的關鍵字並模擬點選,有點複雜,並且隨著系統升級可能會失效,可以參考網上的一些討論。
方案擴充套件
至此,我們已經瞭解瞭如何控制模擬器,實現最基本的功能,但是我們還可以對方案進行擴充套件實現,提高易用性。
接入 CI 服務
客戶端會定期釋出新版本,加入新的功能,所以我們也需要保持除錯用的包為較新版本。一般客戶端團隊都會搭建自己的 CI 服務(例如 Jenkins)進行打包,所以我們可以進行接入,自動下載和安裝最新的包。甚至我們可以拉取 CI 伺服器上的包列表,實現安裝歷史版本,迴歸除錯一些功能。
需要注意的是,客戶端團隊一般只針對 ARM 架構打包,所以需要在 CI 上新增 x86 構建目標,構建產物才能成功在模擬器上執行。
多 App 支援
隨著公司業務範圍的擴充,我們可能需要在多個 App 內除錯頁面,通過指定以下兩點,可以實現多 App 的適配:
- URL Scheme:通過指定不同的 Scheme,可以在不同的 App 中開啟頁面
- Bundle ID:類似
com.netease.cloudmusic
這樣的字串,是 App 的唯一標識,可以通過這個 ID 來進行 App 的啟動、終止、解除安裝等操作
總結
到此為止,我們介紹了構建一套基於 iOS 模擬器的前端除錯方案的基本原理,基於以上內容,我們可以結合 commander 和 inquirer 開發出一套 CLI 工具,也可以結合 Electron 開發一套 GUI 工具,為開發提效。如果你有更多的想法或者相關經驗,也歡迎在評論區與我們交流~
本文釋出自 網易雲音樂前端團隊,文章未經授權禁止任何形式的轉載。我們一直在招人,如果你恰好準備換工作,又恰好喜歡雲音樂,那就 加入我們!