1

Question

When I receive URL strings nested in JSON from the request response, what would be the best practices for downloading data from them?

Detail

For example, let's say I have downloaded several profile data from a network and then need to display them in a table where the names are displayed with profile images.

Raw data

{
    {
        "name": "Jack",
        "imageUrl": "https://example.image/1.jpg"
    },
    {
        "name": "Tom",
        "imageUrl": "https://example.image/2.jpg"
    },
    {
        "name": "Mark",
        "imageUrl": "https://example.image/3.jpg"
    }
}

Codable struct

struct ProfileData: Codable {
    let name: String
    let imageUrl: String
    
    enum CodingKeys: String, CodingKey {
        case name, imageUrl
    }
}

I usually use dataTaskPublihser to get an array of Profiles([Profile]) and image(UIImage) like:


func getProfileData() -> AnyPublisher<[ProfileData], Error> {
    let exampleUrl = URL(string: "https://exmaple.com")!
    return URLSession.shared.dataTaskPublisher(for: exampleUrl)
        .tryMap { (data, response) -> Data in
            guard let httpResponse = response as? HTTPURLResponse,
                  httpResponse.statusCode == 200 else {
                throw URLError(.badServerResponse)
            }
            return data
        }
        .decode(type: [Profile].self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

func getImage(url: String) -> AnyPublisher<UIImage, Error> {
    let exampleUrl = URL(string: url)!
    return URLSession.shared.dataTaskPublisher(for: exampleUrl)
        .tryMap { (data, response) -> Data in
            guard let httpResponse = response as? HTTPURLResponse,
                  httpResponse.statusCode == 200 else {
                throw URLError(.badServerResponse)
            }
            return data
        }
        .map { UIImage(data: $0) ?? UIImage(systemName: "xmark")! }
        .eraseToAnyPublisher()
}
    }

But it has a problem that I should download the actual image data after the first network request(getProfileData at this time) manually.

I made a sample case using the method I mentioned right before. In here, a cell initializes the view model when it's shown on the screen, so the image loads too slowly. In addition, it creates unnecessary network requests because the text field refreshes the entire view every time you type a character(because the variance test is @State).

So I'm wondering are there any ways to create the following:

// New struct which has UIImage, not URL
struct Profile {
    let id = UUID()
    let name: String
    let image: UIImage
}

func getProfile() -> AnyPublisher<[Profile], Error> { ... }

Not sure if this is the best way, but it seems to solve the problem.

Please let me know if there is a better way. You don't even have to provide detailed solutions. I will appreciate it even if you give me just keywords or links.

Thank you.

1
  • What about using an extern library like SDWebImage, KingFisher, Alamofire+Image, etc which should handle the requests, cancel them if needed, avoid calling multiple times the same one, caching the retrieved images too. Or do that all or some of theses options manually, but that's most of the optimizations.
    – Larme
    Commented Apr 13, 2022 at 7:33

0

Browse other questions tagged or ask your own question.