AppVersion Model
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