Skip to content

Instantly share code, notes, and snippets.

@insidegui
Created January 30, 2022 13:56
Show Gist options
  • Save insidegui/18efea9465fbcf5b1602d3cca9533a02 to your computer and use it in GitHub Desktop.
Save insidegui/18efea9465fbcf5b1602d3cca9533a02 to your computer and use it in GitHub Desktop.
Simple Swift type for representing Major.Minor.Patch versions, useful for things such as comparing app versions against values from a backend
/*
Example:
let v1 = SemanticVersion(string: "1.0.0")
let v2 = SemanticVersion(string: "2.0.0")
print(v1 > v2) // false
print(v2 > v1) // true
let v201 = SemanticVersion(string: "2.0.1")
print(v201 > v2) // true
Supports codable, so you can fetch versions from a backend represented as a string.
JSON could look something like this:
{"minAppVersion": "2.3"}
Type that can be decoded can declare minAppVersion as a SemanticVersion and it will be decoded:
struct MyVersionedThing: Codable {
var minAppVersion: SemantincVersion
}
*/
import Foundation
public struct SemanticVersion: Hashable {
public let major: Int
public let minor: Int
public let patch: Int
}
public extension SemanticVersion {
init?(string: String) {
let components = string.components(separatedBy: ".")
guard !components.isEmpty else { return nil }
guard let major = Int(components[0]) else { return nil }
self.major = major
if components.count > 1 {
self.minor = Int(components[1]) ?? 0
} else {
self.minor = 0
}
if components.count > 2 {
self.patch = Int(components[2]) ?? 0
} else {
self.patch = 0
}
}
}
extension SemanticVersion: CustomStringConvertible {
public var description: String { stringRepresentation }
private var stringRepresentation: String { String(format: "%d.%d.%d", major, minor, patch) }
}
public extension SemanticVersion {
static let currentSystemVersion: SemanticVersion = {
#if DEBUG
if let overrideVersion = UserDefaults.standard.string(forKey: "AMSimulatedSystemVersion") {
return SemanticVersion(string: overrideVersion)!
}
#endif
let vers = ProcessInfo.processInfo.operatingSystemVersion
return SemanticVersion(
major: vers.majorVersion,
minor: vers.minorVersion,
patch: vers.patchVersion
)
}()
static let currentAppVersion: SemanticVersion? = {
guard let str = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { return nil }
return SemanticVersion(string: str)
}()
}
extension SemanticVersion: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let str = try container.decode(String.self)
guard let vers = SemanticVersion(string: str) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid semantic version string \(str)"))
}
self = vers
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(stringRepresentation)
}
}
extension SemanticVersion {
public static func <(lhs: SemanticVersion, rhs: SemanticVersion) -> Bool {
if lhs.major == rhs.major {
if lhs.minor == rhs.minor {
return lhs.patch < rhs.patch
}
return lhs.minor < rhs.minor
} else {
return lhs.major < rhs.major
}
}
public static func >=(lhs: SemanticVersion, rhs: SemanticVersion) -> Bool {
if lhs.major == rhs.major {
if lhs.minor == rhs.minor {
return lhs.patch >= rhs.patch
}
return lhs.minor >= rhs.minor
} else {
return lhs.major >= rhs.major
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment