Skip to content

Commit 1ec1bad

Browse files
committed
Improved RemoteImage & image caching
1 parent e0dc17d commit 1ec1bad

File tree

3 files changed

+28
-42
lines changed

3 files changed

+28
-42
lines changed

AsyncMuxDemo/Sources/AppError.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct AppError: LocalizedError {
1313

1414
static var notImpl: Self { .init(code: "not_implemented", message: "Not implemented yet") }
1515
static var unknown: Self { .init(code: "unknown_error", message: "Unknown error") }
16+
static var cachedFileDamaged: Self { .init(code: "cached_file_damaged", message: "Internal: damaged cached file") }
1617

1718
var errorDescription: String? {
1819
message ?? "Application error: \(code)"

AsyncMuxDemo/Sources/RemoteImage/ImageCache.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
// Created by Hovik Melikyan on 28.06.24.
55
//
66

7-
import SwiftUI
7+
import Foundation
8+
import UIKit.UIImage
89
import AsyncMux
910

1011

@@ -13,17 +14,17 @@ private let CacheCapacity = 20
1314

1415
final class ImageCache {
1516

16-
static func request(_ url: URL) async throws -> Image {
17+
static func request(_ url: URL) async throws -> UIImage {
1718
if let image = loadFromMemory(url) {
1819
return image
1920
}
20-
let image = try await Self.requestRemote(url)
21+
let image = try await requestRemote(url)
2122
storeToMemory(url: url, image: image)
2223
return image
2324
}
2425

2526

26-
static func loadFromMemory(_ url: URL) -> Image? {
27+
static func loadFromMemory(_ url: URL) -> UIImage? {
2728
semaphore.wait()
2829
defer { semaphore.signal() }
2930
return memCache.touch(key: url)
@@ -39,26 +40,26 @@ final class ImageCache {
3940

4041
// MARK: - Private part
4142

42-
private static func storeToMemory(url: URL, image: Image) {
43+
private static func storeToMemory(url: URL, image: UIImage) {
4344
semaphore.wait()
4445
defer { semaphore.signal() }
4546
memCache.set(image, forKey: url)
4647
}
4748

4849

4950
@AsyncMediaActor
50-
private static func requestRemote(_ url: URL) async throws -> Image {
51+
private static func requestRemote(_ url: URL) async throws -> UIImage {
5152
let localURL = try await AsyncMedia.request(url: url)
52-
guard let uiImage = UIImage(contentsOfFile: localURL.path) else {
53-
try? FileManager.default.removeItem(at: localURL) // reove the damaged file
54-
throw AppError(code: "cached_file_damaged", message: "Internal: cached file damaged")
53+
guard let image = UIImage(contentsOfFile: localURL.path) else {
54+
try? FileManager.default.removeItem(at: localURL) // remove the damaged file
55+
throw AppError.cachedFileDamaged
5556
}
56-
return Image(uiImage: uiImage)
57+
return image
5758
}
5859

5960

6061
nonisolated(unsafe)
61-
private static var memCache = LRUCache<URL, Image>(capacity: CacheCapacity)
62+
private static var memCache = LRUCache<URL, UIImage>(capacity: CacheCapacity)
6263

6364
private static let semaphore = DispatchSemaphore(value: 1)
6465
}

AsyncMuxDemo/Sources/RemoteImage/RemoteImage.swift

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,51 +5,35 @@
55
//
66

77
import SwiftUI
8-
import AsyncMux
98

109

1110
struct RemoteImage<P: View, I: View>: View {
11+
let url: URL?
12+
@ViewBuilder let content: (Image) -> I
13+
@ViewBuilder let placeholder: (Error?) -> P
1214

13-
init(url: URL?, @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping (Error?) -> P) {
14-
self.model = url.map { Model(url: $0) }
15-
self.content = content
16-
self.placeholder = placeholder
17-
}
18-
19-
private let model: Model?
20-
@ViewBuilder private let content: (Image) -> I
21-
@ViewBuilder private let placeholder: (Error?) -> P
15+
@State private var uiImage: UIImage?
16+
@State private var error: Error?
2217

2318
var body: some View {
24-
if let image = model?.image {
19+
if let image = uiImage.map({ Image(uiImage: $0) }) {
2520
content(image)
2621
}
27-
else if let error = model?.error {
22+
else if let error {
2823
placeholder(error)
2924
}
3025
else {
3126
placeholder(nil)
32-
}
33-
}
34-
35-
36-
@MainActor
37-
@Observable final class Model {
38-
var image: Image?
39-
var error: Error?
40-
41-
init(url: URL) {
42-
image = ImageCache.loadFromMemory(url)
43-
if image == nil {
44-
Task {
45-
do {
46-
self.image = try await ImageCache.request(url)
47-
}
48-
catch {
49-
self.error = error
27+
.task {
28+
if let url {
29+
do {
30+
self.uiImage = try await ImageCache.request(url)
31+
}
32+
catch {
33+
self.error = error
34+
}
5035
}
5136
}
52-
}
5337
}
5438
}
5539
}

0 commit comments

Comments
 (0)