Associated Code?

When showing an ImageDownloader actor, the presenter said a solution on avoiding duplicate downloads is shown with the code associated with this video. When will this code be available? I don't see it on the video page.

  • Came here with the same question 😂

  • I still don't see the code available, glad to see I'm not crazy... (well at least not this time)

Add a Comment

Replies

actor ImageDownloader {

    private enum CacheEntry {
        case inProgress(Task.Handle<Image, Error>)
        case ready(Image)
    }

    private var cache: [URL: CacheEntry] = [:]

    func image(from url: URL) async throws -> Image? {
        if let cached = cache[url] {
            switch cached {
            case .ready(let image):
                return image
            case .inProgress(let handle):
                return try await handle.get()
            }
        }

        let handle = async {
            try await downloadImage(from: url)
        }

        cache[url] = .inProgress(handle)

        do {
            let image = try await handle.get()
            cache[url] = .ready(image)
            return image
        } catch {
            cache[url] = nil
            throw error
        }
    }
}
  • That's very nice, but Task.Handle has been marked as deprecated even before its release

Add a Comment

please show downloadImage func example ?

The second task avoiding entire download action depends on the downloadImage() ?

Just slightly tweaking @mayoff's answer which seems sensible to me (accounting for the deprecations):

struct Image {
    let url: URL
}

actor ImageDownloader {
    private var cache = [URL: Task<Image, Error>]()

    func image(for url: URL) async throws -> Image {
        if let imageTask = cache[url] {
            switch await imageTask.result {
            case .success(let image):
                return image
            case .failure:
                cache[url] = nil
            }
        }

        let imageTask = Task {
            try await downloadImage(url: url)
        }
        cache[url] = imageTask

        switch await imageTask.result {
        case .success(let image):
            return image
        case .failure(let error):
            cache[url] = nil
            throw error
        }
    }

    private func downloadImage(url: URL) async throws -> Image {
        print("Downloading image - \(url.absoluteString)")
        try await Task.sleep(nanoseconds: 2 * 1_000_000_000)
        return Image(url: url)
    }
}

And you can test this using:

let imageDownloader = ImageDownloader()
async let image1 = try imageDownloader.image(for: URL(string: "https://test.com")!)
async let image2 = try imageDownloader.image(for: URL(string: "https://test.com")!)
async let image3 = try imageDownloader.image(for: URL(string: "https://test.com")!)
async let anotherImage = try imageDownloader.image(for: URL(string: "https://another.test.com")!)
print(try await image1.url.absoluteString)
print(try await image2.url.absoluteString)
print(try await image3.url.absoluteString)
print(try await anotherImage.url.absoluteString)

Which should give:

Downloading image - https://test.com
Downloading image - https://another.test.com
https://test.com
https://test.com
https://test.com
https://another.test.com

Which shows only two calls to downloadImage(url:) were actually made.