Home > Software design >  Wait until part of the function completes to execute the function
Wait until part of the function completes to execute the function

Time:09-30

I'm trying to fetch data and update core data based on the new updated API-Data.

I have this download function:

func download1(stock: String, completion: @escaping (Result<[Quote], NetworkError>) -> Void) {
    var internalQuotes = [Quote]()
    let downloadQueue = DispatchQueue(label: "com.app.downloadQueue")
    let downloadGroup = DispatchGroup()
    
    downloadGroup.enter()
    let url = URL(string: API.quoteUrl(for: stock))!
    NetworkManager<GlobalQuoteResponse>().fetch(from: url) { (result) in
        switch result {
        case .failure(let err):
            print(err)
            downloadQueue.async {
                downloadGroup.leave()
            }
            
        case .success(let resp):
            downloadQueue.async {
                internalQuotes.append(resp.quote)
                downloadGroup.leave()
            }
        }
    }
    
    downloadGroup.notify(queue: DispatchQueue.global()) {
        completion(.success(internalQuotes))
        DispatchQueue.main.async {
            self.quotes.append(contentsOf: internalQuotes)
        }
    }
}

On the ContentView I try to implement an update function:

func updateAPI() {
    for stock in depot.aktienKatArray {
        download.download1(stock: stock.aKat_symbol ?? "") { _ in
            //
        }
        for allS in download.quotes {
            if allS.symbol == stock.aKat_symbol {
                stock.aKat_currPerShare = Double(allS.price) ?? 0
            }
        }
    }
    PersistenceController.shared.saveContext()
}

My problem is that the for loop in the update function should only go on if the first part (download.download1) is finished with downloading the data from the API.

CodePudding user response:

Don't wait! Never wait!

DispatchGroup is a good choice – however nowadays I highly recommend Swift Concurrency – but it's at the wrong place.

  • .enter() must be called inside the loop before the asynchronous task starts
  • .leave() must be called exactly once inside the completion handler of the asynchronous task (ensured by a defer statement)

I know this code won't work most likely, but I merged the two functions to the correct DispatchGroup workflow. I removed the custom queue because the NetworkManager is supposed to do its work on a custom background queue

func updateAPI() {
    var internalQuotes = [Quote]()
    let downloadGroup = DispatchGroup()
    
    for stock in depot.aktienKatArray {
        downloadGroup.enter()
        let url = URL(string: API.quoteUrl(for: stock))!
        NetworkManager<GlobalQuoteResponse>().fetch(from: url) { result in
            defer { downloadGroup.leave() }
            switch result {
                case .failure(let err):
                    print(err)
                    
                case .success(let resp):
                    internalQuotes.append(resp.quote)
                    for allS in download.quotes {
                       if allS.symbol == stock.aKat_symbol {
                       stock.aKat_currPerShare = Double(allS.price) ?? 0
                    }
                }
            }
        }
        
    }
    downloadGroup.notify(queue: .main) {
       
        self.quotes.append(contentsOf: internalQuotes)
        PersistenceController.shared.saveContext()
    }
}
  • Related