How to add network fetch inside swift struct initializer

130 Views Asked by At

I'm trying to get data from an URL (1st network call), then make a 2nd network call to fetch image from the url in the first response, and present on tableview in swift.

The json from 1st network call contains 3 strings:

struct Item: Codable {
    var title: String?
    var image_url: String?
    var content: String
}

What I need to do is, after json decoding and get this [Item] array, I also need to make network call for each item in it so I can also get the UIImage for each of them and present title, content and Image on each cell.

My question is, where is the best place to make the network call (in MVVM pattern)?

My first thought is in the TableViewCell:

 func configCell(item: Item) {
    titleLabel.text = item.title
    descriptionLable.text = item.content
    
    // fetch image
    service.fetchImage(with: item.image_url) { result in
        switch result {
        case .success(let image):
            DispatchQueue.main.async { [weak self] in
                if let image = image {
                    self?.iconView.isHidden = false
                }
            }
        case .failure(let error):
            print(error.localizedDescription)
            return
        }
    }
}

However, I'm not sure if this the right way/place to do so because it might cause wrong image attach to each cell. Also it couples network layer into view layer.

So my 2nd thought is, create a second model in the viewModel and make network call in it:

struct ImageItem: Codable {
var title: String?
var image_url: String?
var content: String
var image: Data?

init(with item: Item) {
    self.title = item.title
    self.content = item.content
    ContentService().fetchImage(with: item.image_url) { result in
        switch result {
        case .success(let image):
            self.image = image?.pngData()
        case .failure(let error):
            print(error.localizedDescription)
        }
    }
}

But that doesn't feel right either. Also seems like I can't make network call in struct initializer.

Could anyone help or give me advice about where to put this 2nd layer network call for fetching the image?

1

There are 1 best solutions below

0
On

If those images are lightweight, you can do it directly inside cellForRow(at:) method of UITableViewDataSource. If using a library/pod such as Kingfisher - it's just 1 line that takes care of of, plus another one to import the library:

import Kingfisher
...

cell.iconView.kf.setImage(with: viewModel.image_url)

Otherwise, you may have to take care yourself of caching, not downloading images unless really needed etc.

As long as the image download/configuration is done in UIViewController and not inside the Custom Cell, it should be all good. But still make sure you use some protocol and separate Repository class implementing that protocol and containing the image download functionality, because the View Controller should be more like a Mediator and not be loaded with too much code such as networking and business logic (VCs should act more like view, and generally speaking it's not their job to get the remote image for your custom cells, but for simpler apps that should be ok, unless you want to over-engineer things :) ).

Some people argue that View Models should be light weight and therefore structs, while others make them classes and add lots of functionality such as interacting with external service. It all depends of what variation of MVVM you are using. Personally I prefer to simply keep a URL property in the View Model and implement the "getRemoteImage(url:)" part outside the custom cell, but I've seen people adding UIImage properties directly to the View Models or Data type properties and having some converters injected that transform Data -> UIImage.

You can actually have a Repository and inject the Networking code to the View Model, and then add an Observer<UIImage?> inside the View Model and bind the corresponding cells' images to those properties via closures so the cell automatically updates itself on successful download (if still visible) or next time it appears on screen... Since each cell would have a corresponding view model - each individual view model can keep track if the image for IndexPath was already dowloaded or not.