Search code examples
swift4alamofirejsonresult

Several co-dependent JSON requests on Swift 4


I am refactoring a previous code I made, where I use Alamofire to download some Json files.
The fisrt request is straight forward. I make the request, I got the response and I parse it and store it on Realm. No problem here. Straight forward stuff. The second request is a little trickier, because I need several ID that was retrieved from the first JSON request.

My solution for that problem was first to create a Completion handler on the function that has the Alamofire request:

func requestData(httpMethod: String, param: Any?, CallType : String, complition: @escaping (Bool, Any?, Error?) -> Void){

My idea was to use the Completion to wait for the Alamofire Response to finish and then start the new request. Turn out that didn't work as well.

I was able to pull this off by adding a delay to the Completion.

DispatchQueue.main.asyncAfter(deadline: .now() + 4)

It does work, but is far from being a good practice for several reasons and I would like to refactor that with something more intelligent.

My questions:

1) How is the best way to make many JSON requests on the same function? A way to correctly wait the first one to start the second on an so on? 2) Right now, I call a function to request the first JSON, and on the middle of the call I make a second request. It seems to me that I am hanging the first request too long, waiting for all requests to finish to then finish the first one. I don't think that is a good practice

Here is the complete code. Appreciate the help

    @IBAction func getDataButtonPressed(_ sender: Any) {
    requestData(httpMethod: "GET", param: nil, CallType: "budgets") { (sucess, response, error) in

    if sucess{
        print("ready")
        DispatchQueue.main.asyncAfter(deadline: .now() + 4){
        accounts = realm.objects(Account.self)
        requestAccounts()
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 4){
        users = realm.objects(User.self)
        requestUser()
            }
        }
    }
}


func requestData(httpMethod: String, param: Any?, CallType : String, complition: @escaping (Bool, Any?, Error?) -> Void){
let url = "https://XPTO.com/v1/\(CallType)"
guard let urlAddress = URL(string: url) else {return}
var request = URLRequest(url: urlAddress)
request.httpMethod = httpMethod
request.addValue("application/json", forHTTPHeaderField: "accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer appKey", forHTTPHeaderField: "Authorization")
if param != nil{
    guard let httpBody = try? JSONSerialization.data(withJSONObject: param!, options:[]) else {return}
    request.httpBody = httpBody
}
Alamofire.request(request).responseJSON { (response) in
        let statusCode = response.response?.statusCode
        print("Status Code \(statusCode!)")
        jsonData = try! JSON(data: response.data!)

    complition(true, jsonData, nil)
    if httpMethod == "GET"{
    saveJsonResponse(jsonData: jsonData, CallType: CallType)
    }
    }
}

func requestAccounts(){
var count = accounts.count

while count != 0{
    let account = accounts[0]
    RealmServices.shared.delete(account)
    count -= 1
}


let numberOfBugdets = budgets.count
for i in 0...numberOfBugdets - 1{

    requestData(httpMethod: "GET", param: nil, CallType: "/budgets/\(budgets[i].id)/accounts") { (sucess, response, error) in
     print("accounts downloaded")

        let numberOfAccounts = jsonData["data"]["accounts"].count
        for j in 0...numberOfAccounts - 1{
            let realm = try! Realm()
            do{
                try realm.write {
                    // Code to save JSON data to Realm
                    realm.add(newAccount)
                }
            } catch {
                print("something")
            }
        }
    }
}

}

func requestUser(){
var count = users.count
while count != 0{
    let user = users[0]
    RealmServices.shared.delete(user)
    count -= 1
}
requestData(httpMethod: "GET", param: nil, CallType: "user") { (success, response, error) in
    print("User data downloaded")
    let realm = try! Realm()
    do{
        try realm.write {
            // Code to save JSON data to Realm
            realm.add(newUser)
            }
    } catch {
        print("something")
    }
}
}

func saveJsonResponse(jsonData: JSON, CallType: String){

case "budgets":
    var count = budgets.count
    while count != 0{
        let budget = budgets[0]
        RealmServices.shared.delete(budget)
        count -= 1
    }

    let numberOfBudgets = jsonData["data"]["budgets"].count
    for i in 0...numberOfBudgets - 1 {

        // Code to save JSON data to Realm
        RealmServices.shared.create(newBudget)

    }

}

Solution

  • I recommend completionHandlers in such Situation.

    This is how your code snippet on how to implement it and use it try to understand it and implement it in your code.

    //CompletionHandlers 
    
    func firstOperation(completionHandler: @escaping (_ id: String) -> Void){
    //preform alamoFire and in .response { }  call completionHandler and pass it the id
    completionHandler("10")
    }
    func buttonClicked () {
        firstOperation { (id) in
            secondFunction(completionHandler: { (data) in
                // your data
            })
        }
    
    }
    func secondFunction(completionHandler: @escaping (_ data: String) -> Void){
        //preform alamoFire and in .response { }  call completionHandler and pass it the id
        completionHandler("some Data")
    }
    

    This should give you a better understanding on how to implement it, CompletionHandlers are powerful

    specially in handling such cases when you have to perform an action that depends on other action results and in networking we can't anyhow predict the time of the operation.

    Read more about completionHandlers here