~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
45 lines · 2.0 KB Raw History
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
    }
}