skip to Main Content

I’ve received an array object from the server, and then I want to download images with one property on that object. then I want to update UI with array objects and images (view model). I’m downloading images on a background thread, but I’m getting images with delay and the object doesn’t fills at all, whats I’m doing wrong?

func presentCoinse(_ list: Home.Models.CoinseListResponse) {
        var coins = [Home.Models.coinsViewModel]()
        for item in list {
            getImage(symbol: item.symbol) { image in
                let i = Home.Models.coinsViewModel(image: image,
                                                   symbol: item.symbol,
                                                   name: item.name,
                                                   buyPrice: item.buyPrice,
                                                   sellPrice: item.sellPrice,
                                                   change24Hource: item.symbol)
                coins.append(i)
            }
        }
        viewController?.displayCoinsList(viewModel: coins)
    }

    private func getImage(symbol: String, complation: @escaping(_ image: Data?) -> Void) {
        
        queue.async {
            if let url = URL(string: "(CDN_URL)(symbol).png") {
              
                let data = try? Data(contentsOf: url)
                
                DispatchQueue.main.async {
                    complation(data)
                }
            }
        }
    }

2

Answers


  1. Pay attention that you’re setting coins to empty list, and then you call displayCoinstList with that empty list. the list is appened and updated asyncronically.
    You should trigger the view controller to reload the coins list when it’s ready. I’d make the coins list prior going to fetecg the images, and then upon receiving the images – the UIImageView will render itself.

        func presentCoinse(_ list: Home.Models.CoinseListResponse) {
    
                // async part srarts here
    
                var coins = [Home.Models.coinsViewModel]()
                for item in list {
                    getImage(symbol: item.symbol) { image in
                        let i = Home.Models.coinsViewModel(image: image,
                                                           symbol: item.symbol,
                                                           name: item.name,
                                                           buyPrice: item.buyPrice,
                                                           sellPrice: item.sellPrice,
                                                           change24Hource: item.symbol)
                        coins.append(i)
                    }
                }
    
                // async part ends here
    
                viewController?.displayCoinsList(viewModel: coins)
            }
    

    What you did is equivalent to:

    func presentCoinse(_ list: Home.Models.CoinseListResponse) {
            var coins = [Home.Models.coinsViewModel]()
            viewController?.displayCoinsList(viewModel: coins) // coins in empty at this point)
    
            for item in list {
                getImage(symbol: item.symbol) { image in
                    let i = Home.Models.coinsViewModel(image: image,
                                                       symbol: item.symbol,
                                                       name: item.name,
                                                       buyPrice: item.buyPrice,
                                                       sellPrice: item.sellPrice,
                                                       change24Hource: item.symbol)
                    coins.append(i)
                }
            }
        }
    
    Login or Signup to reply.
  2. The getImage method contains async operations, in this situation the closure will be called after the return of the method.
    And, the method displayCoinsList(viewModel:) is called before every async getImage.

    You can use the DispatchGroup class.

    func presentCoinse(_ list: Home.Models.CoinseListResponse) {
        var coins = [Home.Models.coinsViewModel]()
        let dispatchGroup = DispatchGroup() // add a dispatch group 
        
        for item in list {
            dispatchGroup.enter() // increment group counter before async call
            getImage(symbol: item.symbol) { image in
                defer {
                    dispatchGroup.leave() // decrease group counter in every condition with 'defer'
                }
                let i = Home.Models.coinsViewModel(image: image,
                                                   symbol: item.symbol,
                                                   name: item.name,
                                                   buyPrice: item.buyPrice,
                                                   sellPrice: item.sellPrice,
                                                   change24Hource: item.symbol)
                coins.append(i)
            }
        }
        dispatchGroup.notify(queue: .main) {
            // wait all async calls are completed
            viewController?.displayCoinsList(viewModel: coins)
        }
    }
    
    private func getImage(symbol: String, complation: @escaping(_ image: Data?) -> Void) {
        
        queue.async {
            if let url = URL(string: "(CDN_URL)(symbol).png") {
                let data = try? Data(contentsOf: url)
                complation(data)
            } else {
                complation(nil)
            }
        }
    }
    
    

    NOTE: In getImage Method is required to call complation In every condition. If you forgot to call it the DispatchGroup can’t receive a notify and you’ll be blocked.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search