2. My First iPhone App
On contract…
for non-developer…
who wants to use his own
developer account.
Presents some challenges.
Only my second Objective-C
program…
what can I learn?
3. Huh?
On app store now (yay!).
Simple idea—audio translation of
silly phrases.
Moderate dataset (4 languages, 202
phrases, 808 audio files).
May expand later with StoreKit.
5. 1. Core Data
Used for language/phrase
reference information for easy
update.
Slightly confusing since I
know SQL well.
Lots of work to set up queries.
Matt Gallagher (Cocoa with
Love) showed code that helps.
6. Core Data: one line fetch
I adopted and extended this
code as a category now on
GitHub:
github.com/halostatue/
coredata-easyfetch
14. MBProgressHUD
Created in April 2009
Matej Bukovinski
http://github.com/matej/
MBProgressHUD/
Other options exist, such as
DSActivityView
http://www.dejal.com/
developer/dsactivityview
15. Beautiful, but…
…the progress HUD blocked
the application logo
…immediately on launch.
Request:
Move the HUD to a
different spot, and
don’t show it unless the
work takes 2 seconds or
more.
16. Easy enough…
Added x- and y-offsets
Added a delay-before-showing
Later changed by Matej to
be a “grace time”
Pushed back to master and
MBProgressHUD is reasonably
active for a little project.
My version is at github.com/
halostatue/MBProgressHUD
17. 3. Save the dSYM
Had crashes on the client’s
iPhone that I couldn’t
reproduce.
Hadn’t been saving my
dSYM bundles…so I
couldn’t symbolicate the
logs.
19. Again, with feeling…
Craig Hockenberry:
http://furbo.org/2008/08/08/
symbolicatifination/
Crash logs have address
offsets; if you have the
dSYM bundles, you can
“symbolicate” to restore
method names.
20. Simple script…
# http://furbo.org/stuff/dSYM_Archive.txt
ARCHIVE_ROOT="$PROJECT_DIR/dSYM_Archive"
if [ $SDK_NAME = "iphoneos2.0" ]; then
echo "Archiving $CONFIGURATION in $ARCHIVE_ROOT at `date`" >> /tmp/dSYM_Archive.out
if [ $CONFIGURATION = "Distribution-Free" -o $CONFIGURATION = "Distribution-Paid" -o $CONFIGURATION =
"Distribution-Beta-Free" ]; then
ARCHIVE_CONFIG_FOLDER="$ARCHIVE_ROOT/$CONFIGURATION"
mkdir -p -v "$ARCHIVE_CONFIG_FOLDER" >> /tmp/dSYM_Archive.out
ARCHIVE_FOLDER="$ARCHIVE_CONFIG_FOLDER/`date "+%Y-%m-%d-%H%M%S"`"
echo "Copying $DWARF_DSYM_FOLDER_PATH to $ARCHIVE_FOLDER" >> /tmp/dSYM_Archive.out
cp -R $DWARF_DSYM_FOLDER_PATH $ARCHIVE_FOLDER >> /tmp/dSYM_Archive.out
fi
fi
21. My script…
#!/bin/bash
function prepare_adhoc_package()
{
if [ ${#} -eq 1 ]; then
BASE_NAME="${1}"
else
BASE_NAME="${PROJECT}"
fi
PAYLOAD_FOLDER="${ARCHIVE_FOLDER}/Payload"
mkdir -p "${PAYLOAD_FOLDER}"
if [ -d "${ARCHIVE_FOLDER}/${BASE_NAME}.app" ]; then
cp -rp "${ARCHIVE_FOLDER}/${BASE_NAME}.app" "${PAYLOAD_FOLDER}"
cp "${ARCHIVE_FOLDER}/${BASE_NAME}.app/iTunesArtwork" "${ARCHIVE_FOLDER}"
(cd "${ARCHIVE_FOLDER}"; zip -qr "${BASE_NAME}.ipa" iTunesArtwork Payload)
else
for app in "${ARCHIVE_FOLDER}/*.app"; do
cp -rp "${app}" "${PAYLOAD_FOLDER}"
cp -rp "${app}/iTunesArtwork" "${ARCHIVE_FOLDER}"
done
(cd "${ARCHIVE_FOLDER}"; zip -qr "${BASE_NAME}.ipa" iTunesArtwork Payload)
fi
rm -rf "${ARCHIVE_FOLDER}/iTunesArtwork" "${PAYLOAD_FOLDER}"
open "${ARCHIVE_FOLDER}"
}
function archive_dsym_iphoneos()
{
case ${CONFIGURATION} in
*Distribution*) : ;;
*) return ;;
esac
ARCHIVE_ROOT="${HOME}/projects/dSYMArchive/${PROJECT_NAME}"
ARCHIVE_DATE=`date +"%Y%m%d-%H%M%S"`
echo "Archiving ${CONFIGURATION} in ${ARCHIVE_ROOT} at ${ARCHIVE_DATE}" >> /tmp/archive_dsym.out
ARCHIVE_CONFIG_FOLDER="${ARCHIVE_ROOT}/${CONFIGURATION}"
mkdir -p -v "${ARCHIVE_CONFIG_FOLDER}" >> /tmp/archive_dsym.out
ARCHIVE_FOLDER="${ARCHIVE_CONFIG_FOLDER}/${ARCHIVE_DATE}"
echo "Copying ${DWARF_DSYM_FOLDER_PATH} to ${ARCHIVE_FOLDER}" >> /tmp/archive_dsym.out
cp -Rp ${DWARF_DSYM_FOLDER_PATH} ${ARCHIVE_FOLDER} >> /tmp/archive_dsym.out
case ${CONFIGURATION} in
*[Bb]eta*) prepare_adhoc_package "${1}" ;;
*) return ;;
esac
}
case @${SDK_NAME} in
@iphoneos*) archive_dsym_iphoneos "${1}" ;;
@*) : ;;
@) echo "Not running under Xcode." ;;
esac
22. My script…
#!/bin/bash
function prepare_adhoc_package()
{
if [ ${#} -eq 1 ]; then
BASE_NAME="${1}"
else
BASE_NAME="${PROJECT}"
fi
PAYLOAD_FOLDER="${ARCHIVE_FOLDER}/Payload"
mkdir -p "${PAYLOAD_FOLDER}"
if [ -d "${ARCHIVE_FOLDER}/${BASE_NAME}.app" ]; then
cp -rp "${ARCHIVE_FOLDER}/${BASE_NAME}.app" "${PAYLOAD_FOLDER}"
cp "${ARCHIVE_FOLDER}/${BASE_NAME}.app/iTunesArtwork" "${ARCHIVE_FOLDER}"
(cd "${ARCHIVE_FOLDER}"; zip -qr "${BASE_NAME}.ipa" iTunesArtwork Payload)
else
for app in "${ARCHIVE_FOLDER}/*.app"; do
cp -rp "${app}" "${PAYLOAD_FOLDER}"
You can read that, can’t you?
cp -rp "${app}/iTunesArtwork" "${ARCHIVE_FOLDER}"
done
(cd "${ARCHIVE_FOLDER}"; zip -qr "${BASE_NAME}.ipa" iTunesArtwork Payload)
fi
rm -rf "${ARCHIVE_FOLDER}/iTunesArtwork" "${PAYLOAD_FOLDER}"
open "${ARCHIVE_FOLDER}"
}
function archive_dsym_iphoneos()
{
case ${CONFIGURATION} in
*Distribution*) : ;;
*) return ;;
esac
ARCHIVE_ROOT="${HOME}/projects/dSYMArchive/${PROJECT_NAME}"
ARCHIVE_DATE=`date +"%Y%m%d-%H%M%S"`
echo "Archiving ${CONFIGURATION} in ${ARCHIVE_ROOT} at ${ARCHIVE_DATE}" >> /tmp/archive_dsym.out
ARCHIVE_CONFIG_FOLDER="${ARCHIVE_ROOT}/${CONFIGURATION}"
mkdir -p -v "${ARCHIVE_CONFIG_FOLDER}" >> /tmp/archive_dsym.out
ARCHIVE_FOLDER="${ARCHIVE_CONFIG_FOLDER}/${ARCHIVE_DATE}"
echo "Copying ${DWARF_DSYM_FOLDER_PATH} to ${ARCHIVE_FOLDER}" >> /tmp/archive_dsym.out
cp -Rp ${DWARF_DSYM_FOLDER_PATH} ${ARCHIVE_FOLDER} >> /tmp/archive_dsym.out
case ${CONFIGURATION} in
*[Bb]eta*) prepare_adhoc_package "${1}" ;;
*) return ;;
esac
}
case @${SDK_NAME} in
@iphoneos*) archive_dsym_iphoneos "${1}" ;;
@*) : ;;
@) echo "Not running under Xcode." ;;
esac
24. One gotcha…
Should be done on every
releasable build.
Needs to run after code signing
for an ad-hoc package to be
usable.
Create a wrapper project; make
the original iPhone target a
dependent project.
Add a custom build step that
runs dsym-archiver at the end.
25. 4. git Build Stamps
Good idea to have build
stamps in CFBundleVersion
for error reporting.
svn uses repository
revision number
Updated on every check-
in.
git doesn’t have a repository
revision number.
26. CIMGF to the Rescue?
Marcus Zarra wrote a Perl
script to use the short git
commit sharef in April
2008.
Uses regex replacement.
Released before the
iPhone SDK.
I prefer Ruby.
27. My Rewrite
Rewrote the basic script to
use Ruby with RubyCocoa.
I’m too lazy to compile
MacRuby.
Info.plist loaded with
dictionaryWithContentsOfFile.
Formalized a version class.
28. iTunes Connect‽
The git SHA reference has
problems on iPhone
projects:
Non-contiguous,
confuses iTunes old/new
for ad hoc builds.
iTunes Connect wants
only incrementing dotted
integers.
29. git Tags
git doesn’t have
incrementing revisions
We can fake it with tags on
the commits.
Downside is that
repeated builds on the
same commit means a lot
of tags.
30. build-number.rb (1/2)
#!/usr/bin/ruby
require 'osx/cocoa'
…
# Full implementation at http://github.com/halostatue/xcode-git-version/
class ProgramVersion
attr_accessor :major, :minor, :patch, :build, :ref, :ref_type
def <=>(other); …; end
def self.parse_version(version_string); …; end
def short_version; …; end
def long_version; …; end
def increment_build!; …; end
end
31. build-number.rb (2/2)
# Load the plist
info_plist = OSX::NSDictionary.dictionaryWithContentsOfFile(path).mutableCopy
# Get the current HEAD
current_head = %x(git describe --always).chomp
# Get the list of build tags and turn them into version numbers.
build_tags = `git tag -l build-* --contains #{current_head}`.chomp.split($/)
build_tags.map! { |tag| ProgramVersion.parse_version(tag.sub(/^build-/, '')) }
# Get the CFBundleVersion from the plist.
old_version = ProgramVersion.parse_version(info_plist['CFBundleVersion'])
# Add the old version to the list of build tags.
build_tags << old_version.dup
# Get the largest version we know about for this head.
new_version = build_tags.max
# Increment the build number
new_version.increment_build!
puts "Version modified: #{old_version} -> #{new_version}"
# Set the long version (M.m.p.b)
info_plist['CFBundleVersion'] = new_version.long_version
# Set the short version (M.m or M.m.p).
info_plist['CFBundleShortVersionString'] = new_version.short_version
# Write the file.
info_plist.writeToFile_atomically(path, 1)
# Tag the version
`git tag build-#{new_version.long_version}`
Editor's Notes
This past October and November, I developed my first production iPhone app. I developed it on contract for a non-developer designer who wanted to use his own developer account.
This obviously presents some challenges. It&#x2019;s also only my second Objective-C program. So, what can I learn while I&#x2019;m doing this?