Build numbers and version numbers in iOS (including a script to automatically update build numbers)

 • 

In Xcode, if you look at a project target for an iOS app you will come across a build number and a version number of the target. What do these numbers mean?

Here is a direct quote from the Apple developer docs explaining what the build and version are for:

The version number and the build number values work together to uniquely identify the build and release for a particular App Store submission.

Version Number

The version number of an app is the number that will be visible to the public in the App Store. The number has to be progressively increased when you publish new versions of your app to the store.
You can find the version number in the General tab of your app's target or in the 'Info' tab under ‘Build version string, short’ or ‘Build short version string’. If you open the Info.plist file as raw XML the key of the version number is CFBundleShortVersionString.

Build Number

For each version, every build that you upload to iTunes/Testflight must have a progressively higher build number than all those before.
Once again, you can find the build number in the 'General' tab of your target or the 'Info' tab under the name 'Bundle version'. In the raw Info.plist file it is called CFBundleVersion.

Release Trains

The collection of all the builds supplied for a particular version of the app is called that versions ‘release train’.
For iOS apps, build numbers must be unique within each release train, but they do not need to be unique across different release trains. That is to say, for iOS Apps you can use the same build numbers again in different release trains if you want to. However, for macOS apps, build numbers must monotonically increase even across different versions. In other words, for macOS apps you cannot use the same build numbers again in different release trains.

Versioning Convention

While according to the Apple developer docs your version and build number have up to 18 characters (numbers and dots, starting and ending with a number), you should probably stick to Semantic Versioning unless you have a very compelling reason not to.

Run script for automatic updating

In order to not have to update the build number manually every time you want to upload a new build to the release train, you can use the following run script phase script (put it in after 'Copy Resources' in 'Build Phases').

#!/bin/bash

# exit the script when a command fails or if it tries to use an undeclared variable
set -o errexit
set -o nounset

# = number of commits on the master branch.
VERSION=$(git --git-dir="${PROJECT_DIR}/.git" --work-tree="${PROJECT_DIR}/" rev-list master | wc -l)

# append git dirty flag
if [[ `git status --porcelain` ]]; then
	VERSION="$VERSION+"
fi

# Run Script build phases that operate on product files of the target that defines them should use the value of this build setting [TARGET_BUILD_DIR]. But Run Script build phases that operate on product files of other targets should use “BUILT_PRODUCTS_DIR” instead.
APP_INFO_PLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
DSYM_INFO_PLIST="${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist"

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$APP_INFO_PLIST"
if [ -f "$DSYM_INFO_PLIST" ] ; then
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$DSYM_INFO_PLIST"
fi