How to handle URLSession.shared.dataTask properly

Hi developers im having some issue or misplaced tasks


so i had to develop a framework with some functions


this is my main FUNCTION where the issue began

    func saveDataDevice(idPos: String, macAddress: String) {
        // this first function obtains the MAIN token for further tasks == 'tokenForTransaction'
        let value = FirstInitDevice.tokentransaccional(username: idPos)

//now i'd 2 seconds timer after tokentransaccional is launched 
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0, execute: {

            //after obtain tokenForTransaction i can do any WS, deviceInitWsCall gets call now it returns 'PosId' 
//after finish the task so i'd save it for further fuctions 
           let value2 = FirstInitDevice.deviceInitWsCall(serialNumber: idPos)
            let posidd = value2.PosId
            let convert = String(posidd)
            UserDefaults.standard.set(convert, forKey: "PosId")
            
            //now i can do the third function using PosId parameter and obtain  'rsa'
            //this is where the trouble shows for the first time, because the application hasn't finish the past task
//FirstInitDevice.deviceInitWsCall is not done yet that's why my if == "" just in case ill show the error immediately
        let value3 = FirstInitDevice.getKeyWS(posId: value2.PosId)
            LoginSingleton.shared.pos_id = value2.PosId
            
            if value3.rsa == "" {
                let alert = UIAlertController(title: "alert", message: "Cannot init KEYS", preferredStyle: UIAlertController.Style.alert)
                       let action = UIAlertAction(title: "OK", style: UIAlertAction.Style.destructive, handler: nil)
                       alert.addAction(action)
                self.present(alert, animated: true, completion: nil)
            }else {
// if not continue
//decrypt rsa data 
            let rsaData = RSA().getRSAData(publicKeyHex: value3.rsa)
                   
                 
                   keysSingleton.shared.tk = rsaData.tk
        
                //func 4 runs after decrypt is done BUT is not, because third function is not over yet 
            let value4 = FirstInitDevice.callInitDukptKeys(pos_id: value2.PosId, rsa: rsaData.rsa, check_value: rsaData.checkValue, crc32: rsaData.crc32)


            //same IF just in case == ""
            if value4.ksnString == ""{
                 let alert = UIAlertController(title: "Alert", message: "Cannot init KEYS", preferredStyle: UIAlertController.Style.alert)
                       let action = UIAlertAction(title: "OK", style: UIAlertAction.Style.destructive, handler: nil)
                       alert.addAction(action)
                self.present(alert, animated: true, completion: nil)
            } else{
                keysSingleton.shared.ksn = value4.ksnString
                keysSingleton.shared.new_key = value4.newKey
                keysSingleton.shared.tk = value4.ksnString
                self.doTransaction()
            }
            }
        })
        

        
    }

as you can see one functions leads to another and so and so, it doesn't work without getting the previous parameter

for some reason the applications saves the LAST obtained parameter INSTED the current one, this apply to any other function in the library

i have tried


1.- increasing the timer for

DispatchQueue.main.asyncAfter(deadline: .now() + 8.0, execute: {


2.- set for every function deadline 8s


3.- set DispatchGroup() .enter .leave to every function



but it didn't work, so the question leads to How can i handle URLSession.shared.dataTask after they finish properly



this are the functions on my library


FIRST FUNCTION tokentransaccional

public static func tokentransaccional(username: String){
    let baseUrl = "http://bla bla"

     let headers = [
         "Authorization": "Basic bla bla"
     ]
        let url = URL(string: "bla bla")!
        var request = URLRequest(url: url)
        request.allHTTPHeaderFields = headers
        request.httpMethod = "POST"
        request.timeoutInterval = 60
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        let password = username+"bla bla"
        let postString = "bla bla"
        request.httpBody = postString.data(using: .utf8)
        
     let task = URLSession.shared.dataTask(with: request) { data, response, error in
         guard let data = data, error == nil else {
            // check for fundamental networking error
             print("error=\(error)")
             return
         }

         if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
            // check for http errors
             print("statusCode should be 200, but is \(httpStatus.statusCode)")
             print("response = \(response)")
         }

             do {
                 if let convertedJsonIntoDict = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {

                     let responseError = convertedJsonIntoDict["error"] as? String
                     let errorDescription = convertedJsonIntoDict["error_description"] as? String
                     
                     
                     if responseError?.count == nil {

                         let tokenAcceso = convertedJsonIntoDict["access_token"] as? String
                         LoginSingleton.shared.tokenForTransaction = tokenAcceso ?? ""
                         UserDefaults.standard.set(tokenAcceso, forKey: "AccessTokenLogin")
                     } else {
                         print("error")
                     }
                 }
             }
         catch let error as NSError {
             print("catch let error")
             print(error.localizedDescription)
         }

     }
     task.resume()

     }


second function deviceInitWsCall

public static func deviceInitWsCall(serialNumber: String) -> (PosId: Int, transaction_profile: String) {
        let token = UserDefaults.standard.string(forKey: "AccessTokenLogin") ?? ""
            let headers = [
                "Authorization": "bla bla"
            ]
            let baseUrl = "bla bla"
            let portBPC = "bla bla"
            let deviceInitWsPoint = "bla bla"
        
            let Url = String(format: "bla bla")
             let serviceUrl = URL(string: Url)
            let parameterDictionary = ["bla bla": serialNumber]
            var request = URLRequest(url: serviceUrl!)
            request.httpMethod = "POST"
            request.allHTTPHeaderFields = headers
            request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
            request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        
            let httpBody = try? JSONSerialization.data(withJSONObject: parameterDictionary, options: [])
            request.httpBody = httpBody
            let task = URLSession.shared.dataTask(with: request) { data, response, error in
                
                guard let data = data, error == nil else {
                    print("error=\(error)")
                    return
                }
                
                if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
                    print("statusCode should be 200, but is \(httpStatus.statusCode)")
                    print("response = \(response)")
                }
                
                do {
                    if let convertedJsonIntoDict = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
                        print(convertedJsonIntoDict)
                        let response = convertedJsonIntoDict
                        let status = response["status"] as? Bool
                        if status == true {
                        UserDefaults.standard.set(response, forKey: "deviceInitDictionary")
                       
                         let mainResponse = response["dataResponse"] as! NSDictionary
                         let getPosId = mainResponse["posId"] as! Int
                            UserDefaults.standard.set(getPosId, forKey: "getPosId")
                         let getQps = mainResponse["qps"] as! Bool
                         let getTransactionProfile = mainResponse["transactionProfile"] as! String
                            UserDefaults.standard.set(getTransactionProfile, forKey: "getTransactionProfile")
                         
                         let dataContact = mainResponse["contact"] as! NSDictionary
                         let indicatorPartialCancellation = mainResponse["partialCancellation"] as? Bool
                         LoginSingleton.shared.qps = getQps
                            UserDefaults.standard.set(getQps, forKey: "getQps")

                         LoginSingleton.getInstance().pos_id = getPosId
                         dataPresentReatailPresent.shared.posId = getPosId
                         LoginSingleton.getInstance().qps = getQps
                         LoginSingleton.getInstance().transaction_profile = getTransactionProfile
                         LoginSingleton.getInstance().partialCancellation = indicatorPartialCancellation ?? false


                        }
                        else{
                            print("error")
                        }


                    }
                } catch let error as NSError {
                    print("catch let error")
                    print(error.localizedDescription)
                }
                }.resume()
        let PosId = UserDefaults.standard.integer(forKey: "getPosId")
        let transaction_profile = UserDefaults.standard.string(forKey: "getTransactionProfile") ?? ""
        return (PosId, transaction_profile)
        }

third function getKeyWS

public static func getKeyWS(posId: Int) -> (rsa: String, rsa_id: Int) {

            let token = UserDefaults.standard.string(forKey: "AccessTokenLogin") ?? ""
            let headers = [
                "Authorization": "bla bla"
            ]
        
            let baseUrl = "bla bla"
            let portBPC = ":bla bla"
            let deviceGetKeyUrl = "bla bla"
        
            let Url = String(format: "bla bla")
            let serviceUrl = URL(string: Url)
            let parameterDictionary = ["bla bla": posId]
            var request = URLRequest(url: serviceUrl!)
            request.httpMethod = "POST"
            request.allHTTPHeaderFields = headers
            request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
            request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")

            let httpBody = try? JSONSerialization.data(withJSONObject: parameterDictionary, options: [])
            request.httpBody = httpBody
        
            let task = URLSession.shared.dataTask(with: request) { data, response, error in
                
                guard let data = data, error == nil else {
                    print("error=\(error)")
                    return
                }
                
                if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
                    print("statusCode should be 200, but is \(httpStatus.statusCode)")
                    print("response = \(response)")
                }
                
                do {
                    if let convertedJsonIntoDict = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
                        // Print out dictionary
                        print(convertedJsonIntoDict)
                        let response = convertedJsonIntoDict
                              guard let MainDataResponse = response["dataResponse"] as? NSDictionary else { return }
                              let rsa_id = MainDataResponse["rsaId"] as? Int ?? 0
                              let rsa = MainDataResponse["rsa"] as? String ?? ""

                         
                        UserDefaults.standard.set(rsa_id, forKey: "rsa_id")
                        UserDefaults.standard.set(rsa, forKey: "rsa")
                              

                    



                    }
                } catch let error as NSError {
                    print("catch let error")
                    print(error.localizedDescription)
                }
                }.resume()
        let rsa = UserDefaults.standard.string(forKey: "rsa") ?? ""
        let rsa_id = UserDefaults.standard.integer(forKey: "rsa_id")
        return (rsa, rsa_id)
        }


fourth function callInitDukptKeys

public static func callInitDukptKeys(pos_id: Int, rsa: String, check_value: String , crc32: String) -> (newKey: String, ksnString: String, crc32: String, checkValue: String) {

       let token = UserDefaults.standard.string(forKey: "AccessTokenLogin") ?? ""
       let headers = [
           "Authorization": "bla bla"
       ]

       let baseUrl = "bla bla"
       let portBPC = ":bla bla"
       let deviceInitDukptKeysUrll = "bla bla"
    
       let UUrl = String(format: "bla bla")
       let serviceUrlll = URL(string: UUrl)
       let parameterDictionary = ["bla bla": String(pos_id),
       "bla bla": rsa,
       "bla bla": check_value,
       "bla bla": crc32]
       var requests = URLRequest(url: serviceUrlll!)
       requests.httpMethod = "POST"
       requests.allHTTPHeaderFields = headers
       requests.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
       requests.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
    

       let httpBody = try? JSONSerialization.data(withJSONObject: parameterDictionary, options: [])
       requests.httpBody = httpBody

    _ = URLSession.shared.dataTask(with: requests) { data, response, error in
           
           guard let data = data, error == nil else {
            print("error=\(String(describing: error))")
               return
           }
           
           if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
               print("statusCode should be 200, but is \(httpStatus.statusCode)")
            print("response = \(String(describing: response))")
           }
           
           do {
               if let DictConvert = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
                   // Print out dictionary

                   let response = DictConvert
                   let status = response["status"] as? Bool
                   if status == true {
                   let mainResponse = response["dataResponse"] as? NSDictionary
                   let getKey = mainResponse!["key"] as? String
                   let keyCheckValue = mainResponse!["keyCheckValue"] as? String
                   let keyCrc32 = mainResponse!["keyCrc32"] as? String
                   let ksn = mainResponse!["ksn"] as? String
                   
                    print("valor getKey: \(getKey)")
                    print("valor ksn: \(ksn)")
                    print("valor keyCrc32: \(keyCrc32)")
                    print("valor keyCheckValue: \(keyCheckValue)")
                    UserDefaults.standard.set(getKey, forKey: "getKey")
                    UserDefaults.standard.set(ksn, forKey: "ksn")
                    UserDefaults.standard.set(keyCrc32, forKey: "keyCrc32")
                    UserDefaults.standard.set(keyCheckValue, forKey: "keyCheckValue")
                   } else {
                       print("error")
                   }


               }
           } catch let error as NSError {
               print("catch let error")
               print(error.localizedDescription)
           }
           }.resume()
    let newKey = UserDefaults.standard.string(forKey: "getKey") ?? ""
   let ksnString = UserDefaults.standard.string(forKey: "ksn") ?? ""
   let crc32 = UserDefaults.standard.string(forKey: "keyCrc32") ?? ""
   let checkValue = UserDefaults.standard.string(forKey: "keyCheckValue") ?? ""
    
   return (newKey, ksnString, crc32, checkValue)
   }
   

Replies

From a high level it looks like you are running into a race condition where the functions that are executed in saveDataDevice are returning before the network calls have had a chance to respond and process the data properly. How you solve this is more tied to how your framework is built, but what you essentially need to do build a sequential execution strategy where getKeyWS is never called unless deviceInitWsCall has absolutely completed and populated both values in the value2 tuple. Taking that into account, the first recommendation I would look into would be using an OperationQueue to send out a network request, perform the work you need, and then notify a Queue management function that it's time to move on to the next step in your sequence. My second recommendation would be to take a look at using completion blocks in your code. This may require you to restructure what you have, but you will be able to rely on the previous tuples being set before continuing on to a new function.



Matt Eaton

DTS Engineering, CoreOS

meaton3 at apple.com

I'm getting the same problem
Have you solved it?