/** * https://gist.github.com/tadija/c48177e26b76b541ee6ee620e15d407a * Revision 4 * Copyright © 2020-2021 Marko Tadić * Licensed under the MIT license */ import Foundation public struct AEVersion: CustomStringConvertible { public struct Version: Equatable, Comparable, Codable, CustomStringConvertible { public let major: Int public let minor: Int public let patch: Int public var description: String { "\(major).\(minor).\(patch)" } public init(_ major: Int, _ minor: Int, _ patch: Int = 0) { self.major = major self.minor = minor self.patch = patch } public init(_ value: String) { let components = value.components(separatedBy: ".") let major = components.indices ~= 0 ? Int(components[0]) ?? 0 : 0 let minor = components.indices ~= 1 ? Int(components[1]) ?? 0 : 0 let patch = components.indices ~= 2 ? Int(components[2]) ?? 0 : 0 self = .init(major, minor, patch) } public static func < (lhs: Version, rhs: Version) -> Bool { guard lhs != rhs else { return false } let lhsComparators = [lhs.major, lhs.minor, lhs.patch] let rhsComparators = [rhs.major, rhs.minor, rhs.patch] return lhsComparators.lexicographicallyPrecedes(rhsComparators) } } public enum State { case new case equal case update(from: Version, to: Version) case rollback(from: Version, to: Version) init(old: Version?, new: Version) { guard let old = old else { self = .new; return } if old == new { self = .equal } else if old < new { self = .update(from: old, to: new) } else { self = .rollback(from: old, to: new) } } } public let current: Version public let history: [Version] public var state: State { State(old: history.last, new: current) } public var description: String { "Version: \(current) | State: \(state) | History: \(history)" } public init(current: Version, history: [Version] = []) { self.current = current self.history = history } } public extension AEVersion { init(using defaults: UserDefaults = .standard) { let current = Version(Self.getBundleVersion() ?? "") var history = [Version]() if let existingData = defaults.object(forKey: Key.history) as? Data, let savedHistory = try? JSONDecoder() .decode([Version].self, from: existingData) { history.append(contentsOf: savedHistory) } let result = AEVersion(current: current, history: history) switch result.state { case .new, .update, .rollback: history.append(current) Self.replaceHistory(with: history, using: defaults) case .equal: break } self = result } static func getBundleVersion(from bundle: Bundle = .main) -> String? { bundle.infoDictionary?["CFBundleShortVersionString"] as? String } static func replaceHistory(with history: [Version], using defaults: UserDefaults = .standard) { if let newData = try? JSONEncoder().encode(history) { defaults.set(newData, forKey: Key.history) defaults.synchronize() } } private struct Key { static let history = "AEVersion.History" } }