CFBundleVersion: The version of the build that identifies an iteration of the bundle. This key is a machine-readable string composed of one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods.
CFBundleShortVersionString: The release or version number of the bundle. This key is a user-visible string for the version of the bundle. The required format is three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods.
Android similarly has versionName and versionCode.
The problem is that no matter what, someone somewhere is going to parse something that was never intended for non-human consumption. Or code ends up making assumptions that it shouldn't about a machine-readable version.
I have code which parses CFBundleVersion. It assumes the version follows Apple's docs. But guess what? CFBundleVersion is just a string and nothing enforces Apple's guidance so I was surprised (I shouldn't have been) to have to deal with an internally distributed iOS app that had the word "debug" tacked on to the end of its CFBundleVersion.
Android's "versionCode" is an ever-increasing number, you must submit a higher versionCode than your previous build.
CFBundleVersion can be set back to 0 with each CFBundleShortVersionString change.
Normally I just set both to a build number on whatever CI I use (for something like a cordova cross-platform app) and then increase CFBundleShortVersionString & versionName using the tag name (or a custom version name I give a release).
CFBundleShortVersionString must increment too though. So if you join “${CFBundleShortVersionString}.${CFBundleVersion}” the whole thing must increment, and this explains why CFBundleVersion can revert when CFBundleShortVersionString increments.
Thanks for the references. I think Android's versionName / versionCode are closest to what I have in mind. The problem IMHO with CFBundleVersion, CFBundleShortVersionString, et al. is that they need to be parsed before making comparisons, and this is where developers make mistakes or take shortcuts. Whereas with plain integers, direct comparisons are simple and usually correct as well. Take the story in question: I can imagine being lazy and skip over the major version part of the version string. But with integers, I wouldn't go out of the way to mask out the MSBs, no?
Guess what happens with something as simple as versionCode?
Hacks like this, to allow a single "version" of an app to have multiple APKs, but wait, versionCode has to be unique. I know, we'll do this:
> In the following example, the APK for the x86 ABI would get a versionCode of 2004 and the x86_64 ABI would get 3004. Assigning version codes in large increments, such as 1000, allows you to later assign unique version codes if you need to update your app. For example, if defaultConfig.versionCode iterates to 5 in a subsequent update, Gradle would assign a versionCode of 2005 to the x86 APK and 3005 to the x86_64 APK.
Sigh. Nothing is ever easy. We make assumptions when we design our systems without realizing it and then paint ourselves into these silly corners to not break things.
The format is really [major version][train][audience]?[build number][mastering version]?, where major version is numbers, train is a capital letter, audience is a digit, build number is numbers, and mastering version is a lowercase letter (and things with question marks are optional, and of course Apple does its own thing from time to time). So the current beta for Big Sur, which is 20A4300b, has
20: Major version (happens to match the XNU kernel version)
A: Train (First “point” release, although that is not how marketing does it)
4: Audience (Developer seed)
300: Build number (300 builds since the train started)
b: Mastering version (forked from mainline two builds ago for polishing)
CFBundleVersion: The version of the build that identifies an iteration of the bundle. This key is a machine-readable string composed of one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods.
https://developer.apple.com/documentation/bundleresources/in...
CFBundleShortVersionString: The release or version number of the bundle. This key is a user-visible string for the version of the bundle. The required format is three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods.
https://developer.apple.com/documentation/bundleresources/in...
Android similarly has versionName and versionCode.
The problem is that no matter what, someone somewhere is going to parse something that was never intended for non-human consumption. Or code ends up making assumptions that it shouldn't about a machine-readable version.
I have code which parses CFBundleVersion. It assumes the version follows Apple's docs. But guess what? CFBundleVersion is just a string and nothing enforces Apple's guidance so I was surprised (I shouldn't have been) to have to deal with an internally distributed iOS app that had the word "debug" tacked on to the end of its CFBundleVersion.