Skip to content

Commit

Permalink
Added complete image caching solution to the demo app
Browse files Browse the repository at this point in the history
  • Loading branch information
crontab committed Jul 1, 2024
1 parent 83b2811 commit 57f5985
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 93 deletions.
22 changes: 20 additions & 2 deletions AsyncMuxDemo/AsyncMuxDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
360EBA4E296A2C10001EC729 /* WeatherAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360EBA4D296A2C10001EC729 /* WeatherAPI.swift */; };
360EBA56296A5388001EC729 /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360EBA55296A5388001EC729 /* ErrorAlert.swift */; };
360EBA58296A5C7E001EC729 /* ServerTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360EBA57296A5C7E001EC729 /* ServerTask.swift */; };
361F7D152C32A0750050F657 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 361F7D142C32A0750050F657 /* ImageCache.swift */; };
361F7D182C32A0EA0050F657 /* LRUCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 361F7D172C32A0EA0050F657 /* LRUCache.swift */; };
3625CE1E296A283800D09A8F /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3625CE1D296A283800D09A8F /* Globals.swift */; };
36C131B829805B8E0076E476 /* AsyncMux.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36C131B729805B8E0076E476 /* AsyncMux.framework */; };
36C131B929805B8E0076E476 /* AsyncMux.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 36C131B729805B8E0076E476 /* AsyncMux.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -42,6 +44,8 @@
360EBA4D296A2C10001EC729 /* WeatherAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherAPI.swift; sourceTree = "<group>"; };
360EBA55296A5388001EC729 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = "<group>"; };
360EBA57296A5C7E001EC729 /* ServerTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTask.swift; sourceTree = "<group>"; };
361F7D142C32A0750050F657 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
361F7D172C32A0EA0050F657 /* LRUCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRUCache.swift; sourceTree = "<group>"; };
3625CE1D296A283800D09A8F /* Globals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Globals.swift; sourceTree = "<group>"; };
36C131B729805B8E0076E476 /* AsyncMux.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AsyncMux.framework; sourceTree = BUILT_PRODUCTS_DIR; };
36F73231297AE6B00019807B /* AsyncMuxDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AsyncMuxDemo.entitlements; sourceTree = "<group>"; };
Expand All @@ -66,6 +70,16 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
361F7D162C32A09C0050F657 /* RemoteImage */ = {
isa = PBXGroup;
children = (
361F7D172C32A0EA0050F657 /* LRUCache.swift */,
361F7D142C32A0750050F657 /* ImageCache.swift */,
3606A39429EDACB8002E8878 /* RemoteImage.swift */,
);
path = RemoteImage;
sourceTree = "<group>";
};
3697D0FA296B76C100FE3B76 /* Frameworks */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -95,13 +109,13 @@
36FB700E296A10F90006ACBA /* AsyncMuxDemo */ = {
isa = PBXGroup;
children = (
361F7D162C32A09C0050F657 /* RemoteImage */,
3625CE1D296A283800D09A8F /* Globals.swift */,
36FB701F296A14400006ACBA /* AppError.swift */,
36FB701D296A12D20006ACBA /* URLRequestEx.swift */,
360EBA4D296A2C10001EC729 /* WeatherAPI.swift */,
360EBA55296A5388001EC729 /* ErrorAlert.swift */,
360EBA57296A5C7E001EC729 /* ServerTask.swift */,
3606A39429EDACB8002E8878 /* RemoteImage.swift */,
36FB7011296A10F90006ACBA /* ContentView.swift */,
36FB700F296A10F90006ACBA /* AsyncMuxDemoApp.swift */,
36FB7013296A10FB0006ACBA /* Assets.xcassets */,
Expand Down Expand Up @@ -151,7 +165,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1410;
LastUpgradeCheck = 1410;
LastUpgradeCheck = 1540;
TargetAttributes = {
36FB700B296A10F90006ACBA = {
CreatedOnToolsVersion = 14.1;
Expand Down Expand Up @@ -198,8 +212,10 @@
36FB7012296A10F90006ACBA /* ContentView.swift in Sources */,
360EBA56296A5388001EC729 /* ErrorAlert.swift in Sources */,
36FB701E296A12D20006ACBA /* URLRequestEx.swift in Sources */,
361F7D152C32A0750050F657 /* ImageCache.swift in Sources */,
360EBA4E296A2C10001EC729 /* WeatherAPI.swift in Sources */,
360EBA58296A5C7E001EC729 /* ServerTask.swift in Sources */,
361F7D182C32A0EA0050F657 /* LRUCache.swift in Sources */,
3625CE1E296A283800D09A8F /* Globals.swift in Sources */,
36FB7010296A10F90006ACBA /* AsyncMuxDemoApp.swift in Sources */,
);
Expand Down Expand Up @@ -244,6 +260,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
Expand Down Expand Up @@ -305,6 +322,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
Expand Down
15 changes: 8 additions & 7 deletions AsyncMuxDemo/AsyncMuxDemo/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import SwiftUI
import AsyncMux


// TODO: a function to add a custom location by city name


private let backgroundURL = URL(string: "https://images.unsplash.com/photo-1513051265668-0ebab31671ae")!


Expand Down Expand Up @@ -69,11 +72,9 @@ struct ContentView: View {
}


struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(items: [
.init(place: .init(city: "London", countryCode: "GB", lat: 51.51, lon: -0.13), weather: .init(currentWeather: .init(temperature: 8.1, weathercode: 2))),
.init(place: .init(city: "Paris", countryCode: "FR", lat: 48.84, lon: 2.36), weather: .init(currentWeather: .init(temperature: 10.2, weathercode: 3)))
])
}
#Preview {
ContentView(items: [
.init(place: .init(city: "London", countryCode: "GB", lat: 51.51, lon: -0.13), weather: .init(currentWeather: .init(temperature: 8.1, weathercode: 2))),
.init(place: .init(city: "Paris", countryCode: "FR", lat: 48.84, lon: 2.36), weather: .init(currentWeather: .init(temperature: 10.2, weathercode: 3)))
])
}
79 changes: 0 additions & 79 deletions AsyncMuxDemo/AsyncMuxDemo/RemoteImage.swift

This file was deleted.

64 changes: 64 additions & 0 deletions AsyncMuxDemo/AsyncMuxDemo/RemoteImage/ImageCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// ImageCache.swift
//
// Created by Hovik Melikyan on 28.06.24.
//

import SwiftUI
import AsyncMux


private let capacity = 20


final class ImageCache {

static func request(_ url: URL) async throws -> Image {
if let image = loadFromMemory(url) {
return image
}
let image = try await Self.requestRemote(url)
storeToMemory(url: url, image: image)
return image
}


static func loadFromMemory(_ url: URL) -> Image? {
semaphore.wait()
defer { semaphore.signal() }
return memCache.touch(key: url)
}


static func clear() {
semaphore.wait()
defer { semaphore.signal() }
memCache.removeAll()
}


// MARK: - Private part

private static func storeToMemory(url: URL, image: Image) {
semaphore.wait()
defer { semaphore.signal() }
memCache.set(image, forKey: url)
}


@AsyncMedia
private static func requestRemote(_ url: URL) async throws -> Image {
let localURL = try await AsyncMedia.shared.request(url: url)
guard let uiImage = UIImage(contentsOfFile: localURL.path) else {
try? FileManager.default.removeItem(at: localURL) // reove the damaged file
throw AppError(code: "cached_file_damaged", message: "Internal: cached file damaged")
}
return Image(uiImage: uiImage)
}


nonisolated(unsafe)
private static var memCache = LRUCache<URL, Image>(capacity: capacity)

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

0 comments on commit 57f5985

Please sign in to comment.