SPR
SPR

Reputation: 43

Wait until part of the function completes to execute the function

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.

Upvotes: 0

Views: 161

Answers (1)

vadian
vadian

Reputation: 285079

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()
    }
}

Upvotes: 1

Related Questions