Run iOS Simulator from the Command Line
I like to be able to run apps in the iOS Simulator from outside Xcode—it’s simpler and faster a lot of the time. This page shows how to do it under recent versions of OS X (Lion and Mountain Lion). It should be useful to those trying out OCaml iOS Simulator apps built with OCamlXSim or to anybody who wants to start up simulated iOS apps quickly and/or programmatically.
If you search the web, you’ll find a few descriptions of the
undocumented command-line options for the iOS Simulator. (A good keyword
to start with is "SimulateApplication"
—include the quotes.) These
options let you start up a single app in the simulator with a specified
device type and SDK. However, apps started in this way sometimes run
freakishly slow. It seems that some supporting parts of the simulated
environment are not always initialized properly when using this method.
Another problem with this approach is that it doesn’t simulate the full iOS environment. When you exit your app, for example, the simulation is terminated. So you can’t test interactions between your app and others.
Things work better if you install your iOS app as a package in the
simulator’s file system, and then start the simulator as an ordinary Mac
app. I’ve written a script named runsim
that does this. The latest
version of runsim
is 2.0. You can download it from the following
link:
The full text of the script also appears at the end of this page.
Keep in mind that the iOS Simulator’s file system is undocumented, so
runsim
is using an unsupported interface. I can’t promise that it
will work for you, but it works very well for me.
For this latest release of runsim
I spent a few days finding a good
way to actually launch an app in the simulator. After tracing launchd
and its daemonical helpers, looking for a low-level way to do it, I
decided it would be better to use a supported Apple tool.
A commenter, Shail Choksi, pointed out that the command-line version of
Instruments will start an iOS app for you programmatically. That’s what
I ended up using. So, runsim
now lets you start up an iOS app in the
simulator without any human intervention, and doesn’t have to stray
too far from supported methods to do it.
To use the script, download runsim
from the above link or copy and
paste the text at the end of this page onto a file named runsim
. Mark
it as a script with chmod:
$ chmod +x runsim
To demonstrate how runsim
works, I’ll use Psi
, an iOS app I wrote to
be as small as possible. You can read more about Psi
in Tiny iOS App
in One Source File.
The script will perform one or more of five actions, selected by five
options -i
, -s
, -r
, -d
and -l
. I’ll go through them in turn.
$ runsim -iphone Psi
file …
$
(Technically, this is the -i
option with the value phone
.)
Install Psi (or any given executable) as an iPhone app, with the specified supporting files. Psi doesn’t require any supporting files, but most apps will require at least a few.
To install an app as an iPad app, use -ipad
rather than -iphone
.
$ runsim -s
$
Start up the iPhone simulator. It will contain a standard set of default apps (such as Mobile Safari) and whatever apps you’ve installed. Your apps will generally be on the second screenful; click and swipe to the left to see them. You can start them as usual, by clicking on their icons.
$ runsim -r Psi
$
Run Psi (or any installed app) in the simulator. The app opens and
starts immediately, avoiding the need for human interaction at startup.
If the simulator isn’t already running, it will be started first as with
the -s
option.
As mentioned above, this option uses Instruments, which causes a few complexities. See the Instruments section below for more information.
$ runsim -d Psi
$
Uninstall Psi (or any installed app). Be careful with this. I’ve never tried uninstalling an app while it’s running in the simulator, but I wouldn’t expect it to work out well. I would also avoid uninstalling apps that have been installed through Xcode while Xcode is running.
$ runsim -l
Psi
$
List the installed apps. If you’ve installed apps through Xcode, they’ll be included in the list.
If you don’t specify one of the options, runsim
2.0 assumes the
-iphone
and -s
options. That is, it installs an executable as an
iPhone app and starts up the simulator. This is compatible with the way
it worked in version 1.0.
The first figure below shows what you see when you say runsim Psi
and then swipe to the left in the simulator. The second figure shows
how Psi looks when it’s running. It just draws the Greek letter psi
(upper case).
Instruments
The -r
option of runsim
runs instruments
, the command-line version
of Instruments. This introduces some extra complexity. If you haven’t
yet agreed to the license terms of Xcode, instruments
writes a message
telling you how to agree to the terms, then exits without doing
anything. You can follow the instructions to agree to the terms from the
command line. You can also just start up Xcode, which will guide you
through the acceptance with a GUI.
Although instruments
doesn’t really need to take control of the iOS
app that it starts up, it doesn’t know this. The first time you use the
-r
option in each login session, instruments
will prompt for your
password (if you’re a developer) or for the name and password of a
developer (if OS X doesn’t consider you to be a developer).
To be considered a developer by OS X, you must be a member of the
_developer
group. I suspect (but haven’t verified) that all OS X
admins are automatically made a member of this group. To add a user to
this group, use dscl
:
$ sudo dscl . append /Groups/_developer GroupMembership
username
Other Details
An installation of Xcode can have simulators for different versions of
iOS. runsim
2.0 installs apps into the iOS 6.0 simulator. If you have
trouble locating your apps, you may need to change the simulated version
of iOS. Change the version to 6.0 in the Hardware -> Version menu
of the simulator.
Every app in iOS must contain an Info.plist
file describing the
properties of the app. If there is one in the current directory,
runsim
uses it. Otherwise it fabricates a reasonable one for you.
Since iOS 5, you can have a nib file for your app, or you can have a storyboard file, or you can have neither. I’ve personally always wanted the “neither” option—it makes it a lot easier to create a small example to test or demonstrate something about iOS (which I seem to want to do quite often).
When you install an app with the -i
option, runsim
assumes that the
first specified file ending with a .nib
or .storyboard
suffix is the
startup file. If no such file is specified, runsim
assumes your main
nib or storyboard file is named the same as the executable with a .nib
or .storyboard
suffix. If it doesn’t find any of these files, it
assumes that you don’t have a startup file.
Be aware that the script copies files into the simulator’s space in your
home directory. You’ll find them in Library/Application Support
under iPhone Simulator/6.0/Applications
. You may want to clear them
out periodically, though they shouldn’t do much harm.
If your Xcode is installed in a non-standard place (not in the
/Applications
folder), create a file named runsim.xcloc
with the
full path of Xcode.app
.
To make other changes, you’ll need to edit the script. One thing that
might need changing is the version number of the simulator that you want
to run—as I mentioned, runsim
as given installs apps in the iOS 6.0
Simulator. runsim
copies some associated images (Icon.png
and
Default.png
) if they’re present in the current directory. There may be
other files like this that you want to treat specially.
I got help on runsim
from my OCaml-on-iOS colleagues at
Sakhalin. You can basically figure out everything by just
looking at the file structure that Xcode creates for you, and doing some
guessing. But it’s always great to have help with the guessing. Many
thanks to Sakhalin.
If you have any comments, corrections, or suggestions, leave them below or email me at jeffsco@psellos.com. I’d be especially interested if someone can verify that the script works properly with storyboard files.
Posted by: Jeffrey
Appendix
Finally, here is the text of runsim
:
#!/bin/bash
#
# runsim Install and run apps in the iOS Simulator
#
# Copyright (c) 2012 Psellos http://psellos.com/
# Licensed under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
#
USAGE='usage: runsim [ -i { phone | pad } ] [ -srdl ] executable file ...'
#
# -iphone Install as iPhone app
# -ipad Install as iPad app
# -s Start iOS Simulator
# -r Run the app in the simulator
# -d Delete the installed app
# -l List names of installed apps
#
# file ... Additional files to install with the executable
#
# Default flags are -iphone -s (install as iPhone app and start simulator).
#
# Currently the -r flag uses Instruments and thus requires
# authentication as a member of the _developer group.
#
VERSION=2.0.0
INSTALL=n
START=n
RUN=n
DELETE=n
LIST=n
while getopts i:srdl opt; do
case "$opt" in
i)
INSTALL=y
case "$OPTARG" in
phone) FAMILY=1 ;;
pad) FAMILY=2 ;;
*)
echo "runsim: unrecognized device family: $OPTARG" >&2
echo "$USAGE" >&2
exit 1
;;
esac
;;
s) START=y ;;
r) RUN=y ;;
d) DELETE=y ;;
l) LIST=y ;;
?) echo "$USAGE" >&2; exit 1 ;;
esac
done
shift $(($OPTIND - 1))
case "$INSTALL$START$RUN$DELETE$LIST" in nnnnn)
INSTALL=y
FAMILY=1
START=y
esac
if [ "$INSTALL$RUN$DELETE" != nnn -a $# -lt 1 ]; then
echo 'runsim: need an executable name for -i -r or -d' >&2
echo "$USAGE" >&2
exit 1
fi
EXEC="$1"
shift
APPDIR="$HOME/Library/Application Support/\
iPhone Simulator/6.0/Applications"
TRCSUB=Contents/Applications/Instruments.app\
/Contents/PlugIns/AutomationInstrument.bundle\
/Contents/Resources/Automation.tracetemplate
xcodeloc() {
# Get location of Xcode, otherwise use default
if [ -f runsim.xcloc ]; then
cat runsim.xcloc
else
echo /Applications/Xcode.app
fi
}
appuuid() {
# Get UUID for an app. If installed, re-use existing one. Otherwise
# create a new one and return it.
#
for f in "$APPDIR"/*/"$1.app" ; do
if [ -d "$f" ]; then
basename "$(dirname "$f")"
return 0
fi
done
uuidgen
}
install() {
# Install executable $EXEC and associated files into simulator's
# file system.
#
# Figure out startup file, if any. If a nibfile or storyboard file
# is given, the first one is the startup file. Otherwise if there's
# a file $EXEC.nib or $EXEC.storyboard, that is the startup file.
# Otherwise there is no startup file.
#
NIBFILE=
STORYFILE=
if [ -f "$EXEC.nib" ]; then
NIBFILE="$EXEC"
elif [ -f "$EXEC.storyboard" ]; then
STORYFILE="$EXEC"
fi
for f ; do
case "$f" in
*.nib)
STORYFILE=; NIBFILE="$(basename "$f" .nib)"; break ;;
*.storyboard)
NIBFILE=; STORYFILE="$(basename "$f" .storyboard)"; break ;;
esac
done
UUID=$(appuuid "$EXEC")
# Install app and associated files.
#
TOPDIR="$APPDIR/$UUID"
mkdir -p "$TOPDIR"
mkdir -p "$TOPDIR/Documents"
mkdir -p "$TOPDIR/Library"
mkdir -p "$TOPDIR/tmp"
mkdir -p "$TOPDIR/$EXEC.app"
cp "$EXEC" "$TOPDIR/$EXEC.app"
if [ "$NIBFILE" != "" ]; then
cp "$NIBFILE.nib" "$TOPDIR/$EXEC.app"
elif [ "$STORYFILE" != "" ]; then
cp "$STORYFILE.storyboard" "$TOPDIR/$EXEC.app"
fi
# If an Info.plist exists, use it. Otherwise make one.
if [ -f Info.plist ] ; then
plutil -convert xml1 -o "$TOPDIR/$EXEC.app/Info.plist" Info.plist
else
cat > "$TOPDIR/$EXEC.app/Info.plist" <<HERE1
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"\
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>$EXEC</string>
<key>CFBundleExecutable</key>
<string>$EXEC</string>
<key>CFBundleIconFile</key>
<string>Icon.png</string>
<key>CFBundleIdentifier</key>
<string>com.example.$EXEC</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$EXEC</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleBlackOpaque</string>
<key>LSRequiresIPhoneOS</key>
<true/>
HERE1
if [ "$NIBFILE" != "" ]; then
cat >> "$TOPDIR/$EXEC.app/Info.plist" << HERE2
<key>NSMainNibFile</key>
<string>$NIBFILE</string>
HERE2
elif [ "$STORYFILE" != "" ]; then
cat >> "$TOPDIR/$EXEC.app/Info.plist" << HERE3
<key>NSMainStoryboardFile</key>
<string>$STORYFILE</string>
HERE3
fi
cat >> "$TOPDIR/$EXEC.app/Info.plist" <<HERE4
</dict>
</plist>
HERE4
fi
# Add device specifications to Info.plist (normally done by Xcode).
# Without these, Instruments reports the app as AWOL.
#
python -c '
import plistlib
import sys
p = plistlib.readPlist(sys.argv[1])
p["CFBundleSupportedPlatforms"] = ["iPhoneSimulator"]
p["DTPlatformName"] = "iphonesimulator"
p["DTSDKName"] = "iphonesimulator6.0"
p["UIDeviceFamily"] = ['$FAMILY']
plistlib.writePlist(p, sys.argv[1])
' "$TOPDIR/$EXEC.app/Info.plist"
echo -n 'AAPL????' > "$TOPDIR/$EXEC.app/PkgInfo"
# Install conventional image files if they exist.
#
if [ -f Icon.png ]; then
cp Icon.png "$TOPDIR/$EXEC.app"
fi
if [ -f Default.png ]; then
cp Default.png "$TOPDIR/$EXEC.app"
fi
# Install any other given files.
#
for f; do
if [ "$f" = "$NIBFILE.nib" ]; then continue; fi
if [ "$f" = "$STORYFILE.storyboard" ]; then continue; fi
cp "$f" "$TOPDIR/$EXEC.app"
done
}
start() {
# Start the iOS Simulator
#
open "$(xcodeloc)"/Contents/\
Developer/Platforms/iPhoneSimulator.platform/\
Developer/Applications/iPhone\ Simulator.app
}
run() {
# Run the app inside iOS Simulator by asking Instruments to trace it
# with null trace. If you haven't agreed to the licensing terms of
# Xcode, this will fail until you do. The first time in each login
# session, this will ask for authentication as an admin or
# developer.
#
TOPDIR="$APPDIR/$(appuuid "$EXEC")"
if [ ! -d "$TOPDIR/$EXEC.app" ]; then
echo "runsim: app \"$EXEC\" not installed" >&2
exit 1
fi
(instruments -D /tmp/runsim$$.trace -t "$(xcodeloc)/$TRCSUB" \
"$TOPDIR/$EXEC.app" < /dev/null 2>&1 > /dev/null | \
grep 'xcodebuild -license' >&2 ; \
rm -rf /tmp/runsim$$.trace) &
}
delete() {
# Delete an installed app.
#
TOPDIR="$APPDIR/$(appuuid "$EXEC")"
if [ ! -d "$TOPDIR" ]; then
echo "runsim: app \"$EXEC\" not installed" >&2
exit 1
fi
rm -rf "$TOPDIR"
}
list() {
# List installed apps.
#
for f in "$APPDIR"/*/*.app ; do
if [ -d "$f" ]; then
basename "$f" .app
fi
done
}
case $INSTALL in y) install "$@" ;; esac
case $START in y) start ;; esac
case $RUN in y) run ;; esac
case $DELETE in y) delete ;; esac
case $LIST in y) list ;; esac