johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
import UIKit
/// Accounts' pictures, decoded and kept while the app runs, so that a screen
/// draws those it has seen in its first frame. Each is asked for once, however
/// many rows show it. The address names the picture's content, so the session's
/// disk cache holds them across launches too.
actor Faces {
static let shared = Faces()
private static let limit = 64 * 1024 * 1024
// NSCache is safe across threads, though it isn't marked Sendable.
private nonisolated(unsafe) static let store: NSCache<NSURL, UIImage> = {
let cache = NSCache<NSURL, UIImage>()
cache.totalCostLimit = limit
return cache
}()
private var loading: [URL: Task<UIImage, Error>] = [:]
private nonisolated(unsafe) static let addresses = NSCache<NSString, NSString>()
/// Where an account's picture is, where an answer has said.
nonisolated static func address(of did: String) -> String? { addresses.object(forKey: did as NSString) as String? }
nonisolated static func keep(address: String, of did: String) { addresses.setObject(address as NSString, forKey: did as NSString) }
nonisolated static func forget() { store.removeAllObjects() }
nonisolated static func kept(_ url: URL) -> UIImage? { store.object(forKey: url as NSURL) }
func load(_ url: URL) async throws -> UIImage {
if let face = Self.kept(url) { return face }
// Not tied to the row asking: another may be waiting on the same one.
let task = loading[url] ?? Task.detached {
let data = try await Client.shared.picture(url)
guard let image = UIImage(data: data) else { throw URLError(.cannotDecodeContentData) }
let face = image.preparingForDisplay() ?? image
let bytes = face.cgImage.map { $0.bytesPerRow * $0.height } ?? data.count
Self.store.setObject(face, forKey: url as NSURL, cost: bytes)
return face
}
loading[url] = task
defer { loading[url] = nil }
return try await task.value
}
}