reading json wrong when checking app updates

I'm using the following code to check whether an update is available or not. But it gave me wrong version number. So, it seems an update is available when actually there's not.


enum VersionError: Error { case invalidResponse, invalidBundleInfo }


static func isUpdateAvailable() throws -> Bool {

      guard let info = Bundle.main.infoDictionary,

          let currentVersion = info["CFBundleShortVersionString"] as? String,

          let identifier = info["CFBundleIdentifier"] as? String,

          let url = URL(string: "http:/ throw VersionError.invalidBundleInfo

     }

     let data = try Data(contentsOf: url)

     guard let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any]

     else {               

          throw VersionError.invalidResponse

     }

     if let result = (json["results"] as? [Any])?.first as? [String: Any],

        let version = result["version"] as? String {

           print("version: \(version)") //writes 1.1

          print("currentVersion: \(currentVersion)") // writes 1.1.1

          return version != currentVersion

     }

     throw VersionError.invalidResponse

}


I downloaded the file manually and the version number there is 1.1.1. as it should be. But the code gives me 1.1. I couldn't find out what is wrong.

By the way, the update has just released today. I think it shouldn't be related with that. It is an Apple bug or am I making something wrong?

The url: http://itunes.apple.com/tr/lookup?bundleId=com.sahin.lingustica

Replies

It's strange that after I created a new version 1.1.2 but not submitted, then the code gives me 1.1.1. If I download the file and opened in Safari, I get 1.1.1 again, not 1.1.2. Is it an Apple bug? What am I doing wrong? I'm so confused : (

It’s hard to say exactly what’s going on here because your code has been munged somehow (the line starting with

let url =
seems to have got merged with the next line?). It’d be helpful if you could format your code as a code block (using the
<>
button).

My best guess as to what’s going on here is that the client has cached the response from the server. To verify this you should look at the traffic on the wire to see what data the server actually sent back to you. You can do this using the tools in QA1176 Getting a Packet Trace; a debugging HTTP proxy is going to be the simplest option. If the response was cached, you won’t actually see the request go out on the wire.

You’re currently running your network request using this code:

let data = try Data(contentsOf: url)

I recommend against that for all sorts of reasons. Rather, I recommend you do this using

URLSession
, with code like this:
let url = URL(string: "https://www.apple.com")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
    if let error = error as NSError? {
        NSLog("task transport error %@ / %d", error.domain, error.code)
        return
    }
    let response = response as! HTTPURLResponse
    let data = data!
    NSLog("task finished with status %d, bytes %d", response.statusCode, data.count)
}.resume()

There’s a couple of important points here:

  • URLSession
    lets you pass in a
    URLRequest
    , and that lets you specify a caching policy. If this is a caching problem (which you can confirm as I described above), this will be the fix.
  • This code also shows how to check for both transport and server errors (the HTTP status code). Checking both is important for various reasons.

  • This code is async, and you’ll have to adapt accordingly.

IMPORTANT I hope you’re not running your current code on the main thread. If you are, you may well end up getting killed by the watchdog. QA1693 Synchronous Networking On The Main Thread has info on that.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

This is my code.

enum VersionError: Error {
        case invalidResponse, invalidBundleInfo
    }

    static func isUpdateAvailable() throws -> Bool {
        guard let info = Bundle.main.infoDictionary,
            let currentVersion = info["CFBundleShortVersionString"] as? String,
            let identifier = info["CFBundleIdentifier"] as? String,
            let url = URL(string: "http://itunes.apple.com/tr/lookup?bundleId=\(identifier)") else {
                throw VersionError.invalidBundleInfo
        }
        let data = try Data(contentsOf: url)
        print(url)
        guard let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any] else {
            throw VersionError.invalidResponse
        }
        if let result = (json["results"] as? [Any])?.first as? [String: Any], let version = result["version"] as? String {
      
            print("version: \(version)")
            print("currentVersion: \(currentVersion)")
            return (version != currentVersion)
        }
        throw VersionError.invalidResponse
    }


I called it using DispatchQueue.global().async so it's not on the main thread. I also tried the following code using URLSession and again I took the same result.


static internal func isUpdateAv() throws {
    
        guard let info = Bundle.main.infoDictionary,
            let currentVersion = info["CFBundleShortVersionString"] as? String,
            let identifier = info["CFBundleIdentifier"] as? String,
            let url = URL(string: "http://itunes.apple.com/tr/lookup?bundleId=\(identifier)") else {
                throw VersionError.invalidBundleInfo
        }
    
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Content-Type", forHTTPHeaderField: "application/json")
    
    
        let task = URLSession.shared.dataTask(with: request, completionHandler: {
            data, response, error in
            if(error != nil) {
                print("HTTP.getJSON: response has an error; \(error!.localizedDescription)")
            }
        
            do {
                if data != nil {
                    let json = try? JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: Any]
                
                    guard let result = (json?["results"] as? [Any])?.first as? [String: Any], let version = result["version"] as? String else {
                        throw VersionError.invalidResponse
                    }
                
                    print("version: \(version)")
                    print("currentVersion: \(currentVersion)")
                }
            }
            catch {
                print("HTTP.getJSON: cannot form json dictionary; \(error)")
            }
        })
    
        task.resume()
    }


I know it's non-sense but when I created a new version (1.1.2) on itunesconnect (but not submitted yet), I get version 1.1.1 as it should be. 1.1.2 is not live, so it's non-sense but it fixed my problem. Now, because of getting the correct value with creating a new version, I can't test the responses and requests using my code.

I called it using

DispatchQueue.global().async
so it's not on the main thread.

Cool. Be aware, however, that an async approach is still better. When you schedule a closure on a queue, the system has to assign a thread to run that closure. There’s only a limited number of such threads available, and it’s a bad idea to block one of those threads for long periods of time while you wait for I/O.

Still, none of this has anything to do with your actual issue. Coming back to that, I have three things to note:

  • In your

    URLSession
    code, you don’t need to set the method (line 11) because it defaults to
    GET
    .
  • Likewise, you don’t need to set the

    Content-Type
    header because your request contains no body (the
    Content-Type
    header describes the type of the data in the body associates with the message, so it’s only useful on a request if the request contains a body).
  • Still on the

    URLSession
    side of things, try running the request without caching by setting
    request.cachePolicy
    to
    .reloadIgnoringLocalCacheData
    .

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

request.cachePolicy
to
.reloadIgnoringLocalCacheData
.


That doesn't solve my problem. When I changed the url from "http://itunes.apple.com/tr/lookup?bundleId=\(identifier)" to "https://itunes.apple.com/tr/lookup?bundleId=\(identifier)", it started to give the correct version. I couldn't understand why : )

Post not yet marked as solved Up vote reply of D3sT Down vote reply of D3sT

I am using below code for app version check

guard let info = Bundle.main.infoDictionary,

let currentVersion = info["CFBundleShortVersionString"] as? String,

let identifier = info["CFBundleIdentifier"] as? String,

let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(identifier)") else {

throw VersionError.invalidBundleInfo

}

let data = try Data(contentsOf: url)

guard let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any] else {

throw VersionError.invalidResponse

}


URL 1 : let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(identifier)"


URL 2 : let url = URL(string: "https://itunes.apple.com/us/lookup?bundleId=\(identifier)"



When URL 1 & URL 2 run in browser its show correct data

But in swift programmatically its show wrong data


After app upload in App Store URL 1 show wrong data


But URL 2 shows correct data


After 24 hours both show correct data what’s problem here ?..


Solution (for me its working) :

If you want instant update from apple use URL 2 formate

Yes, your solution is the right way to get the current app version from app store. Thanks!!!