Search code examples
swiftfor-loopasynchronousurlsessioninstagram-graph-api

How to exit a for loop with data from an async function in swift?


How can the function findMediaLimit return the highest i, received from cURL ?

class func findMediaLimit(IgBusinessAccount: String, token: String) {
    let igBId = IgBusinessAccount
        
    for i in 1...12 {
        guard let encodedUrl = self.buildURLAPIGraph(IgBusinessAccount: igBId, token: token, i: i) else { return }
            
        //Async function that returns different i's in random order
        self.cURL(urlT: encodedUrl) { (result) in
        print(i)   
        //return the highest i             
        }
    }
}

I have created this function in order to filter media that have been posted after conversion to a business Instagram account.

that's my cURL function

class func cURL (urlT: String,Completion block: @escaping ((OfficialProfile) -> ())) {
    
    //visuJson (urlT: urlT)
    
    GetJson.loadJson(fromURLString: urlT) { (result) in
        switch result {
        case .success(let data):
            //Parse
            do {
                let decodedData = try JSONDecoder().decode(OfficialProfile.self, from: data)
                if decodedData.username != nil {
                    block(decodedData)
                }
            } catch {
                print("decode error: ",error)
            }
        case .failure(let error):
            print("loadJson error:", error)
        }
    }
}

and that is my loadJson func

class func loadJson(fromURLString urlString: String,
                          completion: @escaping (Result<Data, Error>) -> Void) {
        if let url = URL(string: urlString) {
            let urlSession = URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
                if let error = error {
                    completion(.failure(error))
                }
                
                if let data = data {
                    completion(.success(data))
                }
            }
            urlSession.resume()
        }
    }

Solution

  • To see what the issue is with what you're apparently trying to do, let's simulate it in a simpler way. I'll use an asynchronous random number generator. Try this in a playground:

    func delay(_ delay:Double, closure:@escaping ()->()) {
        let when = DispatchTime.now() + delay
        DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
    }
    func asyncRand(_ completion: @escaping (Int) -> ()) {
        let d = Double.random(in: 1...3)
        delay(d) {
            let i = Int.random(in: 1...100)
            completion(i)
        }
    }
    func go() {
        for i in 1...12 {
            asyncRand() { result in
                print(i, result)
            }
        }
        print("finished")
    }
    go()
    

    The result in a typical run might be:

    finished
    8 13
    1 15
    9 9
    10 56
    7 57
    3 87
    2 70
    11 88
    6 82
    12 16
    4 81
    5 46
    

    So there are two issues here, because of the asynchronous nature of the central asyncRand call: the results come back in no order, over time, and the go method itself (the loop) finishes before any results come back, so there is no opportunity to find out which result is the maximum.

    To straighten that out, while we are waiting for async/await to appear in Swift (possibly as soon as next week), you can use a dispatch group. Ignoring any threading issues, we might do this:

    func go() {
        let group = DispatchGroup()
        var pairs = [[Int]]()
        for i in 1...12 {
            group.enter()
            asyncRand() { result in
                print(i, result)
                pairs.append([i,result])
                group.leave()
            }
        }
        group.notify(queue: DispatchQueue.main) {
            print("finished")
            if let maximum = pairs.max(by: {$0[1] < $1[1]}) {
                print(maximum)
            }
        }
    }
    

    Now the result is (for example):

    11 52
    8 6
    2 1
    4 6
    12 77
    1 88
    7 45
    9 36
    6 25
    3 22
    10 78
    5 33
    finished
    [1, 88]
    

    So you see we have "waited" until all the results have come back, and we have picked out the pair where the largest value was returned.

    So you could do something along those lines. But whether that's a good idea is another question.