AppVersion Model

Dmitriy Ignatyev
Mac O’Clock
Published in
3 min readMay 9, 2020

There are a variety of features that depends on app version:

  • show app version in “About screen”
  • send it to analytics
  • send it to your backend with network requests
  • send it in the meta information of support chat messages
  • make database migration after the app update
  • show some kind of notifications/reminder to the user after minor version updates
  • and so on…

As you might know, we can get app version from Bundle info dict:

if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
… // version 2.17.1
}
if let builNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
… // builNumber 953
}

There are several inconveniences in this approach:

1. We are forced to use if-let construction every time we need to get app version or use nil coalescing operator

2. Repeat boilerplate code: Bundle.main.infoDictionary?[“CFBundleShortVersionString”] as? String

3. Repeat boilerplate code to extract major / minor / patch components:

If let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
let components = currentVersion.components(separatedBy: ".")
let majorString = components[0] // "2"
let minorString = components[1] // "17"
let patchString = components[2] // "1"
// and after that convert string to Int
let major = Int(majorString)! // force unwrapping or if-let cinstruction once again
}

4. No ability to correctly compare two versions. Let’s see an example:

  let previousVersion = "2.9.0"
let currentVersion = "2.17.0"

previousVersion < currentVersion // false. It isn’t correct result.

This is our improvement plan:

☐ get rid of meaningless if-let constructions or usage of nil coalescing operator

☐ get rid boilerplate code to get app version from Bundle’s userinfo

☐ get rid boilerplate code to extract major / minor / patch components

☐ add an ability to correctly compare two versions

Let’s try to fix all these minuses. First of all, we improve the standard Foundation Api:

First two items are completed:

☑ get rid of meaningless if-let constructions or usage of nil coalescing operator

☑ get rid boilerplate code to get app version from Bundle’s userinfo

Very good, let’s go further.

Now, we create an AppVersion model:

public struct AppVersion: LosslessStringConvertible {
public let major: Int
public let minor: Int
public let patch: Int
}

Next step is to make full implementation:

Next step is to make AppVersion comparable:

One more optional step is to make it Codable. In my case it is needed, but you can omit this.

extension AppVersion: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let components: [Int] = try container.decode([Int].self)
self = AppVersion.fromComponents(components: components)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(components)
}
}

Finally, we add two additional static properties to Bundle extension:

Besides, we can store the app version of the previous launch:

Let’s see the result:

All goals are achieved: 💪🏼

☑ get rid of meaningless if-let constructions or usage of nil coalescing operator

☑ get rid boilerplate code to get app version from Bundle’s userinfo

☑ get rid boilerplate code to extract major / minor / patch components

☑ add an ability to correctly compare two versions

Link to the final code: https://gist.github.com/iDmitriyy/79f288c0bb6a8cbb22c59f8ecb54a9fc

If you are not familiar with Semantic Versioning 2.0.0, I recommend you to read this article:
https://semver.org

--

--