給 iOS 模擬器 “安裝”app 檔案

一縷殤流化隱半邊冰霜發表於2019-03-04

前言

剛剛接觸iOS的時候,我就一直很好奇,模擬器上面能不能直接安裝app呢?如果可以,我們就直接在模擬器上面聊QQ和微信了。直到昨天和朋友們聊到了這個話題,沒有想到還真的可以給模擬器“安裝”app!

一.應用場景

先來談談是什麼情況下,會有在模擬器上安裝app的需求。

在一個大公司裡,對原始碼的管理有嚴格的制度,非開發人員是沒有許可權接觸到原始碼的。對蘋果的開發證書管理也非常嚴格,甚至連開發人員也沒有釋出證書,證書只在持續整合環境或者Appstore產線裡面,或者只在最後打包上架的人手上。

那麼現在就有這樣的需求,開發人員搭建好UI以後,要把開發完成的Alapha版給到UI設計師那邊去評審,看看是否完全達到要求,達不到要求就需要打回來重做。

一般做法就是直接拿手機去安裝一遍了。直接真機看效果。不過要是設計師和開發不在同一個地方的公司,一個在北京一個在上海,這種就沒法安裝了。原始碼又無法匯出給設計師,讓他執行一下Xcode跑一下模擬器。打release的ipa通過掃碼安裝,如果公司大了,UDID全部都用完了,也沒法安裝。這個時候就比較麻煩了。(一般也沒人遇到這麼蛋疼的事情吧)

那麼現在就有給模擬器安裝app的需求了,那開發人員如何能把開發版的app給打包出來給其他模擬器安裝呢?

二.解決辦法

解決思路,想要別人的模擬器執行起我們開發的app,最簡單的辦法就是把我們DerivedData的資料直接拷貝到別人模擬器上面,就可以了。當然還要考慮到設計師也許並不會一些命令列命令,我們的操作越傻瓜越好。

1.拷貝本地的DerivedData裡面的debug包

Mac的拷貝命令有cp和ditto,建議用ditto進行拷貝工作。


Usage: ditto [  ] src [ ... src ] dst

     are any of:
    -h                         print full usage
    -v                         print a line of status for each source copied
    -V                         print a line of status for every file copied
    -X                         do not descend into directories with a different device ID

    -c                         create an archive at dst (by default CPIO format)
    -x                         src(s) are archives
    -z                         gzip compress CPIO archive
    -j                         bzip2 compress CPIO archive
    -k                         archives are PKZip
    --keepParent               parent directory name src is embedded in dst_archive
    --arch archVal             fat files will be thinned to archVal
                               multiple -arch options can be specified
                               archVal should be one of "ppc", "i386", etc
    --bom bomFile              only objects present in bomFile are copied
    --norsrc                   don`t preserve resource data
    --noextattr                don`t preserve extended attributes
    --noqtn                    don`t preserve quarantine information
    --noacl                    don`t preserve ACLs
    --sequesterRsrc            copy resources via polite directory (PKZip only)
    --nocache                  don`t use filesystem cache for reads/writes
    --hfsCompression           compress files at destination if appropriate
    --nopreserveHFSCompression don`t preserve HFS+ compression when copying files
    --zlibCompressionLevel num use compression level `num` when creating a PKZip archive
    --password                 request password for reading from encrypted PKZip archive複製程式碼

Ditto比cp命令更好的地方在於:

  1. 它在複製過程中不僅能保留原始檔或者資料夾的屬性與許可權,還能保留原始檔的資源分支結構和資料夾的源結構。
  2. 此命令能確保檔案或者資料夾被如實複製。
  3. 如果目標檔案或者資料夾不存在,ditto將直接複製過去或建立新的檔案和資料夾,相反,對於已經存在的檔案,命令將與目標檔案(夾)合併。
  4. ditto還能提供完整符號連結。

那麼我們就拷貝出本地的debug包

ditto -ck --sequesterRsrc --keepParent `ls -1 -d -t ~/Library/Developer/Xcode/DerivedData/*/Build/Products/*-iphonesimulator/*.app | head -n 1` /Users/YDZ/Desktop/app.zip複製程式碼

有幾點需要說明的:

  1. 上面命令最後一個路徑(/Users/YDZ/Desktop/app.zip),這個是自定義的,我這裡舉的例子是直接放在桌面。除了這裡改一下路徑,前面的都不需要改,包括 * 也都不用改。

  2. 再來說一下命令裡面的 * 的問題。當我們開啟自己本地的~/Library/Developer/Xcode/DerivedData/ ,這個路徑下,會發現裡面裝的都是在我們本地模擬器上執行過的app程式。前面是app的Bundle Identifier,橫線後面是一堆字串。上面的ditto裡面帶 * 的那個路徑是為了動態匹配一個地址的,* 在這裡也是一個萬用字元。後面的head說明了匹配的規則。head其實是找出最近一次我們執行模擬器的app的路徑。

為了保證我們打包是正確的,建議先執行一下我們要打包的app,一般我們Scheme裡面的Run都是debug product(如果這裡有更改,那就改成對應debug的Scheme),確保是我們要給設計師稽核的app,之後再執行這個ditto命令。

2.把debug包拷貝到另一個模擬器中

我們執行完上面的ditto命令會產生一個zip檔案,解壓出來,會得到一個app檔案,這個就是debug包了。debug包就是我們要給設計師的app包了。

如何能讓設計師傻瓜式的安裝這個app呢?

這裡介紹一個命令列工具,ios-sim命令列工具。

ios-sim 是一個可以在命令控制iOS模擬器的工具。利用這個命令,我們可以啟動一個模擬器,安裝app,啟動app,查詢iOS SDK。它可以使我們像自動化測試一樣不用開啟Xcode。

不過 ios-sim 只支援Xcode 6 以後的版本。

安裝ios-sim

    $ npm install ios-sim -g複製程式碼

說明文件:


    Usage: ios-sim   [--args ...]

    Commands:
      showsdks                        List the available iOS SDK versions
      showdevicetypes                 List the available device types
      launch        Launch the application at the specified path on the iOS Simulator
      start                           Launch iOS Simulator without an app
      install       Install the application at the specified path on the iOS Simulator without launching the app

    Options:
      --version                       Print the version of ios-sim
      --help                          Show this help text
      --exit                          Exit after startup
      --log            The path where log of the app running in the Simulator will be redirected to
      --devicetypeid     The id of the device type that should be simulated (Xcode6+). Use `showdevicetypes` to list devices.
                                      e.g "com.apple.CoreSimulator.SimDeviceType.Resizable-iPhone6, 8.0"

    Removed in version 4.x:
      --stdout      The path where stdout of the simulator will be redirected to (defaults to stdout of ios-sim)
      --stderr      The path where stderr of the simulator will be redirected to (defaults to stderr of ios-sim)
      --sdk               The iOS SDK version to run the application on (defaults to the latest)
      --family         The device type that should be simulated (defaults to `iphone`)
      --retina                        Start a retina device
      --tall                          In combination with --retina flag, start the tall version of the retina device (e.g. iPhone 5 (4-inch))
      --64bit                         In combination with --retina flag and the --tall flag, start the 64bit version of the tall retina device (e.g. iPhone 5S (4-inch 64bit))

    Unimplemented in this version:
      --verbose                       Set the output level to verbose
      --timeout              The timeout time to wait for a response from the Simulator. Default value: 30 seconds
      --args <...>                    All following arguments will be passed on to the application
      --env    A plist file containing environment key-value pairs that should be set
      --setenv NAME=VALUE             Set an environment variable複製程式碼

用法不難

ios-sim launch /Users/YDZ/Desktop/app.app --devicetypeid iPhone-6s複製程式碼

其中,/Users/YDZ/Desktop/app.app這個是設計師收到app之後的路徑。–devicetypeid引數後面是給定一個模擬器的版本。

只需要把上面的命令發給設計師,無腦貼上到命令列,裝好app的模擬器就會自動啟動,開啟app了。

三.額外的嘗試

好奇的同學肯定不會滿足只給模擬器安裝debug包吧,既然可以不用程式碼就可以給模擬器安裝app,那我們能安裝release包麼?我好奇的嘗試了一下。

先從Appstore上面下載最新的微信,把ipa字尾改成zip,解壓,把Payload資料夾裡面的“WeChat”取出來,然後執行ios-sim命令。

結果微信確實是安裝到了模擬器了。不過一點選app,看見了月亮介面就退出了。控制檯列印了一堆資訊。

An error was encountered processing the command (domain=FBSOpenApplicationErrorDomain, code=1):
The operation couldn’t be completed. (FBSOpenApplicationErrorDomain error 1.)
Aug 18 16:29:17 YDZdeMacBook-Pro nsurlsessiond[19213]: Task 1 for client {contents = "com.apple.mobileassetd"} completed with error - code: -999
Aug 18 16:29:17 YDZdeMacBook-Pro com.apple.CoreSimulator.SimDevice.D6BD3967-9BC4-4A8D-9AD0-23176B22B12A.launchd_sim[19096] (UIKitApplication:com.tencent.xin[0xdf6d][19774]): Program specified by service does not contain one of the requested architectures:
Aug 18 16:29:17 YDZdeMacBook-Pro SpringBoard[19181]: Unable to get pid for `UIKitApplication:com.tencent.xin[0xdf6d]`: No such process (err 3)
Aug 18 16:29:17 YDZdeMacBook-Pro SpringBoard[19181]: Bootstrapping failed for 
Aug 18 16:29:17 YDZdeMacBook-Pro SpringBoard[19181]: Unable to delete job with label UIKitApplication:com.tencent.xin[0xdf6d]. Error: Operation now in progress
Aug 18 16:29:17 YDZdeMacBook-Pro SpringBoard[19181]: Application `UIKitApplication:com.tencent.xin[0xdf6d]` exited for an unknown reason.
Aug 18 16:29:17 YDZdeMacBook-Pro com.apple.CoreSimulator.SimDevice.D6BD3967-9BC4-4A8D-9AD0-23176B22B12A.launchd_sim[19096] (UIKitApplication:com.tencent.xin[0xdf6d][19774]): Trampoline was terminated before jumping to service: Killed: 9
Aug 18 16:29:18 YDZdeMacBook-Pro fileproviderd[19169]: (Note ) FileProvider: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/PrivateFrameworks/FileProvider.framework/Support/fileproviderd starting.
Aug 18 16:29:20 YDZdeMacBook-Pro pkd[19238]: assigning plug-in com.apple.ServerDocuments.ServerFileProvider(1.0) to plugin sandbox
Aug 18 16:29:20 YDZdeMacBook-Pro pkd[19238]: enabling pid=19169 for plug-in com.apple.ServerDocuments.ServerFileProvider(1.0) D12B6280-6DF1-434C-9BAA-BD9B0D0FB756 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/Applications/ServerDocuments.app/PlugIns/ServerFileProvider.appex
Aug 18 16:29:22 YDZdeMacBook-Pro SpringBoard[19181]: Weekly asset update check did fire (force=NO)
Aug 18 16:29:22 YDZdeMacBook-Pro SpringBoard[19181]: Beginning check for asset updates (force: 0
Aug 18 16:29:22 YDZdeMacBook-Pro SpringBoard[19181]: Did not complete check for asset updates (force: 0, isVoiceOverRunning: 0
Aug 18 16:29:23 YDZdeMacBook-Pro mstreamd[19171]: (Note ) mstreamd: mstreamd starting up.
Aug 18 16:29:23 YDZdeMacBook-Pro DTServiceHub[19191]: DTServiceHub(19191) [error]: `mach_msg_send` failed: (ipc/send) invalid destination port (268435459)
Aug 18 16:29:25 YDZdeMacBook-Pro itunesstored[19744]: iTunes Store environment is: MR22
Aug 18 16:29:25 YDZdeMacBook-Pro itunesstored[19744]: Normal message received by listener connection. Ignoring.
Aug 18 16:29:25 --- last message repeated 1 time ---
Aug 18 16:29:25 YDZdeMacBook-Pro mstreamd[19171]: (Note ) PS: The subscription plugin class does not support push notification refreshing.
Aug 18 16:29:25 YDZdeMacBook-Pro itunesstored[19744]: libMobileGestalt MGIOKitSupport.c:387: value for udid-version property of IODeviceTree:/product is invalid ((null))
Aug 18 16:29:25 YDZdeMacBook-Pro itunesstored[19744]: Normal message received by listener connection. Ignoring.
Aug 18 16:29:25 YDZdeMacBook-Pro itunesstored[19744]: libMobileGestalt MGBasebandSupport.c:60: _CTServerConnectionCopyMobileEquipmentInfo: CommCenter error: 1:45 (Operation not supported)
Aug 18 16:29:25 YDZdeMacBook-Pro itunesstored[19744]: libMobileGestalt MGBasebandSupport.c:189: No CT mobile equipment info dictionary while fetching kCTMobileEquipmentInfoIMEI
Aug 18 16:29:26 YDZdeMacBook-Pro mstreamd[19171]: (Note ) PS: Media stream daemon starting...
Aug 18 16:29:27 YDZdeMacBook-Pro itunesstored[19744]: UpdateAssetsOperation: Error downloading manifest from URL https://apps.itunes.com/files/ios-music-app/: Error Domain=SSErrorDomain Code=109 "無法連線到 iTunes Store" UserInfo={NSLocalizedDescription=無法連線到 iTunes Store, SSErrorHTTPStatusCodeKey=503}
Aug 18 16:29:31 YDZdeMacBook-Pro healthd[19174]: (Error) MC: MobileContainerManager gave us a path we weren`t expecting; file a radar against them
           Expected: /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
           Actual: /Users/YDZ/Library/Developer/CoreSimulator/Devices/D6BD3967-9BC4-4A8D-9AD0-23176B22B12A/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
           Overriding MCM with the one true path
Aug 18 16:29:31 YDZdeMacBook-Pro healthd[19174]: PairedSync, Debugging at level 0 for console and level 0 for log files
Aug 18 16:29:31 YDZdeMacBook-Pro healthd[19174]: Error: Could not create service from plist at path: file:///Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/PairedSyncServices/com.apple.pairedsync.healthd.plist. Returning nil PSYSyncCoordinator for service name com.apple.pairedsync.healthd.  Please check that your plist exists and is in the correct format.
Aug 18 16:29:31 YDZdeMacBook-Pro healthd[19174]: Error: failed to load bundle "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Health/Plugins/CompanionHealth.bundle": Error Domain=NSCocoaErrorDomain Code=4 "未能載入軟體包“CompanionHealth.bundle”,因為未能找到其可執行檔案的位置。" UserInfo={NSLocalizedFailureReason=未能找到該軟體包可執行檔案的位置。, NSLocalizedRecoverySuggestion=請嘗試重新安裝軟體包。, NSBundlePath=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Health/Plugins/CompanionHealth.bundle, NSLocalizedDescription=未能載入軟體包“CompanionHealth.bundle”,因為未能找到其可執行檔案的位置。}
Aug 18 16:29:33 YDZdeMacBook-Pro wcd[19180]: libMobileGestalt MobileGestalt.c:2584: Failed to get battery level
Aug 18 16:29:34 --- last message repeated 1 time ---
Aug 18 16:29:34 YDZdeMacBook-Pro assertiond[19185]: assertion failed: 15G31 13E230: assertiond + 15801 [3C808658-78EC-3950-A264-79A64E0E463B]: 0x1
Aug 18 16:29:34 --- last message repeated 1 time ---
Aug 18 16:29:34 YDZdeMacBook-Pro SpringBoard[19181]: [MPUSystemMediaControls] Updating supported commands for now playing application.
Aug 18 16:29:34 YDZdeMacBook-Pro assertiond[19185]: assertion failed: 15G31 13E230: assertiond + 15801 [3C808658-78EC-3950-A264-79A64E0E463B]: 0x1
Aug 18 16:29:34 --- last message repeated 1 time ---
Aug 18 16:29:34 YDZdeMacBook-Pro fileproviderd[19169]: plugin com.apple.ServerDocuments.ServerFileProvider invalidated
Aug 18 16:29:34 YDZdeMacBook-Pro ServerFileProvider[19775]: host connection  connection from pid 19169 invalidated
Aug 18 16:30:08 YDZdeMacBook-Pro mstreamd[19171]: (Note ) PS: Media stream daemon stopping.
Aug 18 16:30:09 YDZdeMacBook-Pro mstreamd[19171]: (Note ) AS: : Shared Streams daemon has shut down.
Aug 18 16:30:09 YDZdeMacBook-Pro mstreamd[19171]: (Warn ) mstreamd: mstreamd shutting down.
Aug 18 16:30:09 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: Switching to keyboard: zh-Hans
Aug 18 16:30:09 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: KEYMAP: Failed to determine iOS keyboard layout for language zh-Hans.
Aug 18 16:30:09 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: Switching to keyboard: zh-Hans
Aug 18 16:30:09 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: KEYMAP: Failed to determine iOS keyboard layout for language zh-Hans.
Aug 18 16:30:10 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: Switching to keyboard: zh-Hans
Aug 18 16:30:10 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: KEYMAP: Failed to determine iOS keyboard layout for language zh-Hans.
Aug 18 16:30:10 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: Switching to keyboard: zh-Hans
Aug 18 16:30:10 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: KEYMAP: Failed to determine iOS keyboard layout for language zh-Hans.
Aug 18 16:30:16 YDZdeMacBook-Pro sharingd[19183]: 16:30:16.190 : Failed to send SDURLSessionProxy startup message, error Error Domain=com.apple.identityservices.error Code=23 "Timed out" UserInfo={NSLocalizedDescription=Timed out, NSUnderlyingError=0x7ff088e005a0 {Error Domain=com.apple.ids.idssenderrordomain Code=12 "(null)"}}
Aug 18 16:30:38 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: Switching to keyboard: zh-Hans
Aug 18 16:30:38 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: KEYMAP: Failed to determine iOS keyboard layout for language zh-Hans.
Aug 18 16:30:38 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: Switching to keyboard: zh-Hans
Aug 18 16:30:38 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: KEYMAP: Failed to determine iOS keyboard layout for language zh-Hans.
Aug 18 16:30:41 YDZdeMacBook-Pro CoreSimulatorBridge[19190]: Switching to keyboard: zh-Hans複製程式碼

仔細看了一下log,根本原因還是因為

com.apple.CoreSimulator.SimDevice.D6BD3967-9BC4-4A8D-9AD0-23176B22B12A.launchd_sim[19096] (UIKitApplication:com.tencent.xin[0xdf6d][19774]): Program specified by service does not contain one of the requested architectures:

Unable to get pid for `UIKitApplication:com.tencent.xin[0xdf6d]`: No such process (err 3)複製程式碼

因為release包裡面architectures打包的時候不包含模擬器的architectures。debug包裡面就有。所以release就沒法安裝到模擬器了。

由於筆者逆向方面的東西沒有研究,所以也無法繼續下去了。不知道逆向技術能不能把release包破殼之後能不能轉成debug包呢?如果能轉成debug包,通過ios-sim命令應該也是可以直接安裝到模擬器的。

至此,ios-sim給模擬器安裝app就嘗試到此了。因為只能給模擬器安裝debug包,所以在題目上額外給安裝加了雙引號,並不是所有的app檔案都可以安裝到模擬器。

請大家多多指教。

相關文章