0

Sometimes cache cleanup is needed after a big update, so I'd like to check once the user updates the app if the app version is above or below the one that needs cache cleanup.

For example, I currently have this to check when it's a new app update or install:

import BundleInfoVersioning

func displayUpdateNotification() {
    let bundleInfoVersioning = BundleInfoVersioning(bundle: .main)
    
    bundleInfoVersioning.check(forKeyPath: "CFBundleShortVersionString") { (old: String? , newVersion: String?) in
        if old == nil {
            // You can place some analytics here ...
            print(" New App Installed")
        }
        else {
            // You can place some analytics here ...
            print(" New App Update")
            if(showUpdate) {
                self.shouldShowUpdatePopUp = true
            }
        }
    }
}

So if the user does an app update compare the installed version with the version defined by me that needs cache cleanup, how can I compare that string with the new version? Like:

if after update the user is in version 2.5.2 or below, clean cache once

Arturo
  • 1,777
  • 13
  • 42

2 Answers2

1

So, I had a similar issue some time back, we needed to be able to compare the installed version with cloud based information and do "things"

The first thing I did was built a concept of a "version", which is based on "major.minor.patch" and "build" properties.

This allowed the cloud to send us a structure which could understand.

struct Version: Comparable, CustomStringConvertible {
    
    let major: Int
    let minor: Int
    let patch: Int
    
    let build: Int
    
    init(major: Int, minor: Int, patch: Int, build: Int) {
        self.major = major
        self.minor = minor
        self.patch = patch
        self.build = build
    }
    
    init(from: String, build: String = "0")  {
        let buildNumber = Int(build) ?? 0
        let parts = from.split(separator: ".")
        let major = Int(parts[0]) ?? 0
        var minor = 0
        var patch = 0
        if parts.count >= 2 {
            minor = Int(parts[1]) ?? 0
        }
        if parts.count >= 3 {
            patch = Int(parts[2]) ?? 0
        }
        
        self.init(major: major, minor: minor, patch: patch, build: buildNumber)
    }
    
    var description: String {
        return String(format: "%02d.%02d.%02d build %02d", major, minor, patch, build)
    }
    

    static func <(lhs: Version, rhs: Version) -> Bool {
        return lhs.description.compare(rhs.description, options: NSString.CompareOptions.numeric) == ComparisonResult.orderedAscending
    }

    static func ==(lhs: Version, rhs: Version) -> Bool {
        return lhs.description.compare(rhs.description, options: NSString.CompareOptions.numeric) == ComparisonResult.orderedSame
    }

}

Add in a simple extensions to get it from the app itself...

extension Version {

static var app: Version = {
    let version = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String
    let build = Bundle.main.infoDictionary!["CFBundleVersion"] as! String
    return Version(from: version, build: build)
}()

}

And then you can do things like

if Version.app == Version(from: "2.5.2") {
    // And now do some stuff
}

Now, you will need to play around with this, as my needs might not be exactly the same as yours, but this is basically what I was able to use and it worked well, for my needs

But aren't you comparing 2 strings there? What if the user is in version 3.0.0 and I want all users with version 2.5.2 and below to clean cache? Can I do: if Version.app <= Version(from: "2.5.2") { } ?

This is one of those nice little features of Swift (actually ObjC ). Have a look at what NSString.CompareOptions.numeric does for the comparison

For example...

let userVersion = Version(from: "3.0.0")
let targetVersion = Version(from: "2.5.2")

userVersion == targetVersion // 3.0.0 == 2.5.2 - false ✅
userVersion > targetVersion // 3.0.0 > 2.5.2 - true ✅
userVersion < targetVersion // 3.0.0 < 2.5.2 - false ✅
userVersion >= targetVersion // 3.0.0 >= 2.5.2 - true ✅
userVersion <= targetVersion // 3.0.0 <= 2.5.2 - false ✅
MadProgrammer
  • 336,120
  • 22
  • 219
  • 344
  • But aren't you comparing 2 strings there? What if the user is in version `3.0.0` and I want all users with version `2.5.2` and below to clean cache? Can I do: `if Version.app <= Version(from: "2.5.2") { }` ? – Arturo Feb 03 '22 at 00:13
  • @Arturo You'd think so wouldn't you, but do some research into `NSString.CompareOptions.numeric` – MadProgrammer Feb 03 '22 at 00:14
  • @Arturo [This](https://stackoverflow.com/questions/27932408/compare-two-version-strings-in-swift/27932531) is probably where I stole ... I mean learnt it from – MadProgrammer Feb 03 '22 at 00:20
  • It looks good, I'm testing it and thanks for the link, I'm looking at it too – Arturo Feb 03 '22 at 00:21
  • We used this concept in production to ensure that the user could only use supported versions of the app. Versions below the supported version, from the cloud, would lock the user out of the app. If a new version was available (but the version was still supported), they'd get an upgrade prompt – MadProgrammer Feb 03 '22 at 00:24
  • That is actually another feature I have to implement so this comes very very handy – Arturo Feb 03 '22 at 00:25
  • 1
    @Arturo For me, the point was to get a "abstract" concept of the version which was independent of the cloud and the app, so I could then bind them together, because the cloud always has a different concept of how these things should be represented – MadProgrammer Feb 03 '22 at 00:28
  • @MadProgrammer no need to implement all the operators when conforming to Comparable. You just need to implement less than and equal to operators. All the rest is automatically synthesized for you. **Conforming to the Comparable Protocol Types with Comparable conformance implement the less-than operator ( – Leo Dabus Feb 03 '22 at 00:34
  • @LeoDabus I think that's what I had originally, I just went overboard – MadProgrammer Feb 03 '22 at 00:36
1

You can simply use Foundation struct OperatingSystemVersion and create a custom initializer:

extension OperatingSystemVersion: LosslessStringConvertible {
    public init?(_ string: String) {
        let comps = string.split { $0 == "." }
        if string.range(of: "^[0-9]+.[0-9]+.[0-9]+$", options: .regularExpression) != nil {
            self.init(majorVersion: Int(comps[0]) ?? 0, minorVersion: Int(comps[1]) ?? 0, patchVersion: Int(comps[2]) ?? 0)
        } else if string.range(of: "^[0-9]+.[0-9]+$", options: .regularExpression) != nil {
            self.init(majorVersion: Int(comps[0]) ?? 0, minorVersion: Int(comps[1]) ?? 0, patchVersion: 0)
        } else if string.range(of: "^[0-9]+$", options: .regularExpression) != nil {
            self.init(majorVersion: Int(comps[0]) ?? 0, minorVersion: 0, patchVersion: 0)
        } else {
            return nil
        }
    }
    public var description: String {
        [majorVersion, minorVersion, patchVersion].map(String.init).joined(separator: ".")
    }
}

Then you can conform OperatingSystemVersion to Comparable:

extension OperatingSystemVersion: Comparable {
    public static func == (lhs: Self, rhs: Self) -> Bool {
        (lhs.majorVersion, lhs.minorVersion, lhs.patchVersion) == (rhs.majorVersion, rhs.minorVersion, rhs.patchVersion)
    }
    
    public static func < (lhs: Self, rhs: Self) -> Bool {
        (lhs.majorVersion, lhs.minorVersion, lhs.patchVersion) < (rhs.majorVersion, rhs.minorVersion, rhs.patchVersion)
    }
}

And just check if it is older than the new version:

if let old = OperatingSystemVersion("2.5.2"),
    let new =  OperatingSystemVersion("2.5.3"),
    old < new {
    print(new.majorVersion) // "2\n"
    print(new.minorVersion) // "5\n"
    print(new.patchVersion) // "3\n"
}
Leo Dabus
  • 216,610
  • 56
  • 458
  • 536
  • Leo can you please take a look at this one https://stackoverflow.com/questions/70979421/app-crashes-on-main-with-thread-1-exc-bad-access-code-1-address and see if you have any idea? I'm out of ideas :/ – Arturo Feb 03 '22 at 22:55
  • @Arturo Check my last edit. There was an issue with the regex which would fail if there were more than one digit at any property. – Leo Dabus Mar 09 '22 at 13:58