Skip to content

Instantly share code, notes, and snippets.

@malhal
Created May 8, 2024 21:37
Show Gist options
  • Save malhal/7a4e687962f89392c16c388635227aec to your computer and use it in GitHub Desktop.
Save malhal/7a4e687962f89392c16c388635227aec to your computer and use it in GitHub Desktop.
ResourceCacheTest.swift
//
// ResourceCacheTest.swift
// Test (iOS)
//
// Created by Malcolm Hall on 08/05/2024.
//
import SwiftUI
import AsyncAlgorithms
struct ResourceCacheTest {
struct RuntimeError: LocalizedError {
let description: String
init(_ description: String) {
self.description = description
}
var errorDescription: String? {
description
}
}
@MainActor
class ResourceCache {
enum Request {
case inprogress
case success(UIImage)
case failed(Error)
}
let urls: AsyncChannel<URL>
var requests: [AsyncChannel<(URL, Request)>] = []
init() {
let urls = AsyncChannel<URL>()
self.urls = urls
Task {
var requestsByURL: [URL : Request] = [:]
for await url in urls {
print("loop")
try? await Task.sleep(for: .seconds(1)) // testing
print("url \(url)")
if let request = requestsByURL[url] {
switch request {
case .inprogress:
continue
default:
for channel in requests {
await channel.send((url, request))
}
}
}
var request: Request = .inprogress
requestsByURL[url] = request
let urlRequest = URLRequest(url: url)
do {
print("requesting...")
let (data, _) = try await URLSession.shared.data(for: urlRequest)
let image = UIImage(data: data)!
request = .success(image)
} catch {
print("Failed to fetch resource:", error)
request = .failed(error)
}
requestsByURL[url] = request
for channel in requests {
await channel.send((url, request))
}
}
}
}
}
struct MyView: View {
let resourceCache: ResourceCache
@State var image: UIImage?
let text: String
let url: URL
var body: some View {
Group {
if let image {
Image(uiImage: image)
}
else {
Text("Loading")
.task {
await withDiscardingTaskGroup { group in
group.addTask { @MainActor in
let requests = AsyncChannel<(URL, ResourceCache.Request)>()
resourceCache.requests.append(requests)
for await (_, request) in requests.filter({ url, request in
url == self.url
}) {
switch request {
case .success(let image):
self.image = image
default:
break
}
}
resourceCache.requests.removeAll { requests === $0 }
}
print("sending \(text)")
await resourceCache.urls.send(url)
print("sent \(text)")
}
}
}
}
}
}
struct ContentView: View {
@State var resourceCache: ResourceCache?
var body: some View {
if let resourceCache {
VStack {
MyView(resourceCache: resourceCache, text: "1", url: URL(string: "https://avatars.githubusercontent.com/u/61242156?s=100&v=4")!)
MyView(resourceCache: resourceCache, text: "2", url: URL(string: "https://avatars.githubusercontent.com/u/31996?s=100&v=4")!)
MyView(resourceCache: resourceCache, text: "3", url: URL(string: "https://avatars.githubusercontent.com/u/61242156?s=100&v=4")!)
}
}else {
Text("Initialising...")
.onAppear {
resourceCache = ResourceCache()
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment