天天看點

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

前言

剛剛接觸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檔案都可以安裝到模拟器。

請大家多多指教。