構建基於 iOS 模擬器的前端調試方案

封面

做者: imyzf

本文將爲你們介紹自動化控制 iOS 模擬器的原理,爲開發基於 iOS 模擬器的前端調試方案提供幫助。javascript

咱們在開發 iOS App 內的前端頁面時,有一個很大的痛點,頁面沒法使用 Safari Inspector 等工具調試。遇到了問題,咱們只能想辦法加 vConsole,或者注入 Weinre,或者盲改,實在不行就找客戶端同窗手動打包調試,總之排查問題的路途很是艱難。前端

在參考了 RN 和 Weex 等跨平臺框架的開發工具後,咱們發現使用模擬器調試是解決該問題的很好方法,咱們將前端頁面放到模擬器的 App 中運行,蘋果就不會對其有限制,容許咱們使用 Safari Inspector 調試了。java

Safari Inspector 是和 Chrome Devtools 相似的調試工具,由 Safari 瀏覽器自帶,支持如下功能:node

Safari Inspector 功能

  • 檢查頁面元素
  • 查看網絡請求
  • 斷點調試
  • 存儲管理(Local Storage,Cookies 等)
  • ……

這些功能是 vConsole、Weinre 等工具沒法比擬的,能夠幫助咱們快速定位問題。ios

基於這些原理,咱們內部已經開發了一款工具,部分功能視頻能夠點此預覽。但因爲該工具和內部業務耦合較深,目前暫無開源計劃。git

前提條件

介紹這套方案以前,咱們須要瞭解一下方案的前提條件:github

  • 裝有 macOS 和 Xcode 的電腦:因爲蘋果的限制,模擬器和 Xcode 只能在 macOS 上運行。Xcode 直接在 App Store 中安裝便可,十分簡單,無需其餘操做。
  • 爲模擬器構建的 App 包:因爲模擬器是基於 x86 架構的,須要客戶端開發同窗提供爲模擬器構建的包,和在手機上安裝的包會有所不一樣。
  • 支持 URL Scheme 喚起的 App:承載前端頁面的 App 必須支持用協議喚起並打開頁面,才能用工具實現自動化,不然只能在 App 內手動點擊相關鏈路打開頁面。

整體流程

總體流程圖

咱們的模擬器調試方案總體流程如上圖所示:npm

  1. 獲取設備列表,提供給用戶選擇
  2. 檢查模擬器狀態,若是沒有啓動,就啓動該模擬器
  3. 檢查是否安裝對應的 App,若是沒有安裝,就下載安裝包進行安裝
  4. 啓動 App,並打開須要調試的頁面
  5. 根據頁面類型,使用對應的工具進行調試(例如 Safari Inspector)

核心工具

咱們在實現本方案時,主要基於如下工具:xcode

  • xcrun:Xcode 提供了一個命令行工具xcrun對開發相關的功能進行控制,是一系列工具的集合。
  • simctlxcrun提供了一個子命令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 沒有安裝,這個時候咱們去調用上面的安裝流程便可。

NSOSStatusErrorDomain

整個流程中最重要的一步是如何將咱們的頁面在 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 偏好設置 - 高級 - 選中在菜單欄中線顯示「開發」菜單)。

Safari 開發菜單

固然這一步也能夠實現自動化,須要藉助 Apple Script 搜索 Safari 菜單中的關鍵字並模擬點擊,有點複雜,而且隨着系統升級可能會失效,能夠參考網上的一些討論

方案擴展

至此,咱們已經瞭解瞭如何控制模擬器,實現最基本的功能,可是咱們還能夠對方案進行擴展實現,提升易用性。

接入 CI 服務

客戶端會按期發佈新版本,加入新的功能,因此咱們也須要保持調試用的包爲較新版本。通常客戶端團隊都會搭建本身的 CI 服務(例如 Jenkins)進行打包,因此咱們能夠進行接入,自動下載和安裝最新的包。甚至咱們能夠拉取 CI 服務器上的包列表,實現安裝歷史版本,迴歸調試一些功能。

須要注意的是,客戶端團隊通常只針對 ARM 架構打包,因此須要在 CI 上新增 x86 構建目標,構建產物才能成功在模擬器上運行。

多 App 支持

隨着公司業務範圍的拓展,咱們可能須要在多個 App 內調試頁面,經過指定如下兩點,能夠實現多 App 的適配:

  1. URL Scheme:經過指定不一樣的 Scheme,能夠在不一樣的 App 中打開頁面
  2. Bundle ID:相似com.netease.cloudmusic這樣的字符串,是 App 的惟一標識,能夠經過這個 ID 來進行 App 的啓動、終止、卸載等操做

總結

到此爲止,咱們介紹了構建一套基於 iOS 模擬器的前端調試方案的基本原理,基於以上內容,咱們能夠結合 commander 和 inquirer 開發出一套 CLI 工具,也能夠結合 Electron 開發一套 GUI 工具,爲開發提效。若是你有更多的想法或者相關經驗,也歡迎在評論區與咱們交流~

本文發佈自 網易雲音樂前端團隊,文章未經受權禁止任何形式的轉載。咱們一直在招人,若是你剛好準備換工做,又剛好喜歡雲音樂,那就 加入咱們
相關文章
相關標籤/搜索