Search code examples
swiftalamofiregrand-central-dispatchswifty-jsoncompletionhandler

How to handle priorities in a Swifty JSON Alamofire request?


How can I use the dispatchQueue or something like "await" in Javascript to return a value in self.arrayData (because the end of my loop is ran before the previous content). I am used to R and Python where the code runs line by line, What is the best behavior to adopt in Swift ?

Here is the function :

func fetch2(){

    var i:Int = 0

    repeat {

    AF.request(itemLookUp[i]).validate().responseJSON { response in

    switch response.result {

    case .failure(let error):
    print("\(error) in fetch2")

    case .success(let value):

        let json = JSON(value)

        //Extract the Matiere for ML Extraction
    self.matiereInput = json["ResultSet"]["0"]["Result"]["0"]["SpAdditional"].string ?? "none"

        let energyCheck:Bool = self.matiereInput.contains("エネルギー") //energy-kcal

    if energyCheck==true && self.arrayData[0]==0.0{
        //regular expression
        var patEnergy = #"(エネルギー)(([^\d]+)(\d+)(\.)(\d+)|([^\d]+)(\d+))"# //avoid the repetition of the pattern within the same matiereinput
        let patEnergy2 = self.matches(for: patEnergy, in: self.matiereInput)
        patEnergy = patEnergy2.joined(separator:"")
        let valueEnergy = self.matches(for: self.regex2, in: patEnergy)
        self.arrayData[0] = Double(valueEnergy.joined(separator: "")) ?? 0.0
        }

    }
        }
        i = i+1
        print(self.arrayData[0])
} while i <= (self.returned-1)
}

Thank you in advance !


Solution

  • The standard pattern is notify with a DispatchGroup, and then use a completion handler to asynchronously notify the caller of the result:

    func fetchAll(completion: @escaping (Result<[Double], Error>) -> Void) {
        let group = DispatchGroup()
    
        var results: [Double] = []
        var errors: [Error] = []
    
        for item in lookupItems {
            group.enter()                                          // enter before request
    
            AF.request(item).validate().responseJSON { response in
                defer { group.leave() }                            // leave when this closure is done
    
                switch response.result {
                case .failure(let error):
                    errors.append(error)
    
                case .success(let value):
                    let result = ...
                    results.append(result)
                }
            }
        }
    
        group.notify(queue: .main) {
            if let error = errors.first {                          // I don’t know what you want to do if there were multiple errors, so for now I’ll just grab the first one
                completion(.failure(error))
            } else {
                completion(.success(results))
            }
        }
    }
    

    And then you’d use it like so:

    fetchAll { result in
        switch result {
        case .failure(let error):
            print(error)
    
        case .success(let values):
            print(values)
        }
    }
    

    Now, I wasn’t able to reverse engineer what you were trying to do (you appear to be updating self.arrayData[0] in every iteration!), so I just returned an array of Double. But you can obviously change the type of results and the parameter of the completion closure to match whatever is relevant in your case.

    But don’t get lost in the details of the above example, but rather just focus on a few key observations:

    1. Supply completion handler closure to call when all the requests are done.
    2. Use DispatchGroup to keep track of when all the requests are done.
    3. Supply a notify closure to your DispatchGroup which will be called when all the group.enter() calls are offset by their respective group.leave() calls.
    4. A more subtle observation is that you should refrain from updating properties from within the responseJSON block. Within your asynchronous code, you really want to limit your interaction to local variables if at all possible. Pass the result back in the completion closure (and the caller can update the model and the UI as it sees fit).