Skip to content

Commit da9190e

Browse files
committed
Add Swift rewrite with async/await and SwiftUI support
Major modernization of JMImageCache: - Add Swift Package Manager support (iOS 15+, macOS 12+, tvOS 15+, watchOS 8+) - Implement async/await API for modern Swift concurrency - Add SwiftUI CachedAsyncImage view component - Add UIKit UIImageView extension with Swift API - Maintain backward compatibility with Objective-C code - Add comprehensive test suite (11 tests) - Fix infinite recursion bug from issue #33 - Fix UI thread safety issues from issue #40 - Update README with Swift/SwiftUI/UIKit examples and migration guide
1 parent cd355a4 commit da9190e

8 files changed

Lines changed: 1138 additions & 99 deletions

File tree

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ build/*
66
*.pbxuser
77
*.perspectivev3
88
*.tm_build_errors
9-
*.mode1v3
10-
*.pbxuser
119
xcuserdata
1210
project.xcworkspace
11+
12+
# Swift Package Manager
13+
.build/
14+
.swiftpm/
15+
Package.resolved

Package.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// swift-tools-version: 5.9
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "JMImageCache",
8+
platforms: [
9+
.iOS(.v15),
10+
.macOS(.v12),
11+
.tvOS(.v15),
12+
.watchOS(.v8)
13+
],
14+
products: [
15+
.library(
16+
name: "JMImageCache",
17+
targets: ["JMImageCache"]
18+
),
19+
],
20+
targets: [
21+
.target(
22+
name: "JMImageCache",
23+
dependencies: [],
24+
path: "Sources/JMImageCache"
25+
),
26+
.testTarget(
27+
name: "JMImageCacheTests",
28+
dependencies: ["JMImageCache"],
29+
path: "Tests/JMImageCacheTests"
30+
),
31+
]
32+
)

README.markdown

Lines changed: 0 additions & 97 deletions
This file was deleted.

README.md

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# JMImageCache
2+
3+
A fast, simple, and lightweight image caching library for iOS, macOS, tvOS, and watchOS.
4+
5+
`JMImageCache` provides both in-memory (NSCache) and disk-based caching for images downloaded from the network. It supports modern Swift async/await patterns while maintaining full backward compatibility with the original Objective-C API.
6+
7+
## Features
8+
9+
- **In-memory caching** - Automatic memory management via NSCache
10+
- **Disk caching** - Persistent storage with SHA1-hashed filenames
11+
- **Async/await support** - Modern Swift concurrency patterns
12+
- **SwiftUI support** - `CachedAsyncImage` view component
13+
- **UIKit support** - `UIImageView` extension for easy image loading
14+
- **Backward compatible** - Original Objective-C API still works
15+
- **Cross-platform** - iOS, macOS, tvOS, and watchOS
16+
- **Thread-safe** - All operations are properly synchronized
17+
- **Lightweight** - No external dependencies
18+
19+
## Requirements
20+
21+
- iOS 15.0+ / macOS 12.0+ / tvOS 15.0+ / watchOS 8.0+
22+
- Swift 5.9+
23+
- Xcode 15.0+
24+
25+
For older iOS versions (5.0-14.x), use the legacy Objective-C implementation included in this repo.
26+
27+
## Installation
28+
29+
### Swift Package Manager (Recommended)
30+
31+
Add JMImageCache to your project via SPM:
32+
33+
```swift
34+
dependencies: [
35+
.package(url: "https://github.com/jakemarsh/JMImageCache.git", from: "2.0.0")
36+
]
37+
```
38+
39+
Or in Xcode: File → Add Package Dependencies → Enter the repository URL.
40+
41+
### CocoaPods (Legacy)
42+
43+
```ruby
44+
pod 'JMImageCache'
45+
```
46+
47+
## Quick Start
48+
49+
### Swift (Async/Await)
50+
51+
```swift
52+
import JMImageCache
53+
54+
// Simple async/await
55+
let image = try await JMImageCache.shared.image(for: url)
56+
57+
// With custom cache key
58+
let image = try await JMImageCache.shared.image(for: url, key: "my-custom-key")
59+
```
60+
61+
### Swift (Completion Handler)
62+
63+
```swift
64+
JMImageCache.shared.image(for: url) { image in
65+
imageView.image = image
66+
} failure: { error in
67+
print("Failed: \(error)")
68+
}
69+
```
70+
71+
### SwiftUI
72+
73+
```swift
74+
import JMImageCache
75+
76+
struct ContentView: View {
77+
var body: some View {
78+
CachedAsyncImage(url: URL(string: "https://example.com/image.jpg")) { phase in
79+
switch phase {
80+
case .empty:
81+
ProgressView()
82+
case .success(let image):
83+
image
84+
.resizable()
85+
.aspectRatio(contentMode: .fit)
86+
case .failure:
87+
Image(systemName: "photo")
88+
.foregroundColor(.gray)
89+
}
90+
}
91+
}
92+
}
93+
```
94+
95+
### UIKit
96+
97+
```swift
98+
import JMImageCache
99+
100+
// Simple usage
101+
imageView.setImage(with: url)
102+
103+
// With placeholder
104+
imageView.setImage(with: url, placeholder: UIImage(named: "placeholder"))
105+
106+
// With callbacks
107+
imageView.setImage(with: url, placeholder: placeholderImage) { image in
108+
print("Loaded!")
109+
} failure: { error in
110+
print("Error: \(error)")
111+
}
112+
113+
// Cancel loading
114+
imageView.cancelImageLoad()
115+
```
116+
117+
### Objective-C (Legacy)
118+
119+
```objc
120+
#import "JMImageCache.h"
121+
122+
// UIImageView category
123+
[imageView setImageWithURL:url placeholder:placeholderImage];
124+
125+
// Direct cache access
126+
[[JMImageCache sharedCache] imageForURL:url completionBlock:^(UIImage *image) {
127+
self.imageView.image = image;
128+
}];
129+
```
130+
131+
## How It Works
132+
133+
Images can be in three states:
134+
135+
1. **Cached In Memory** - Returned immediately
136+
2. **Cached On Disk** - Loaded from disk, moved to memory, returned
137+
3. **Not Cached** - Downloaded, saved to disk, cached in memory, returned
138+
139+
This approach ensures the fastest possible image delivery while minimizing network requests.
140+
141+
## Cache Management
142+
143+
### Clear All
144+
145+
```swift
146+
// Swift
147+
JMImageCache.shared.removeAllImages()
148+
149+
// Objective-C
150+
[[JMImageCache sharedCache] removeAllObjects];
151+
```
152+
153+
### Remove Specific Image
154+
155+
```swift
156+
// Swift
157+
JMImageCache.shared.removeImage(for: url)
158+
JMImageCache.shared.removeImage(forKey: "my-key")
159+
160+
// Objective-C
161+
[[JMImageCache sharedCache] removeImageForURL:url];
162+
```
163+
164+
### Pre-cache Images
165+
166+
```swift
167+
// Swift
168+
let image = try await JMImageCache.shared.image(for: url)
169+
// Image is now cached for future use
170+
```
171+
172+
### Check Cache
173+
174+
```swift
175+
// Swift
176+
if let cached = JMImageCache.shared.cachedImage(for: url) {
177+
// Image is in memory cache
178+
}
179+
```
180+
181+
## Custom Configuration
182+
183+
```swift
184+
// Custom cache directory
185+
let cache = JMImageCache(
186+
cacheDirectory: customURL,
187+
urlSession: .shared
188+
)
189+
```
190+
191+
## Migration from v1.x (Objective-C)
192+
193+
The Swift rewrite maintains API compatibility. Most Objective-C code will continue to work. For Swift projects:
194+
195+
| Old API (Obj-C) | New API (Swift) |
196+
|-----------------|-----------------|
197+
| `[imageView setImageWithURL:url placeholder:placeholder]` | `imageView.setImage(with: url, placeholder: placeholder)` |
198+
| `[[JMImageCache sharedCache] imageForURL:url completionBlock:^...]` | `try await JMImageCache.shared.image(for: url)` |
199+
| `[[JMImageCache sharedCache] cachedImageForURL:url]` | `JMImageCache.shared.cachedImage(for: url)` |
200+
| `[[JMImageCache sharedCache] removeAllObjects]` | `JMImageCache.shared.removeAllImages()` |
201+
202+
## Error Handling
203+
204+
```swift
205+
do {
206+
let image = try await JMImageCache.shared.image(for: url)
207+
} catch JMImageCacheError.invalidResponse(let response) {
208+
// Server returned an error
209+
} catch JMImageCacheError.invalidImageData {
210+
// Data couldn't be converted to an image
211+
} catch {
212+
// Other error
213+
}
214+
```
215+
216+
## Thread Safety
217+
218+
All JMImageCache operations are thread-safe:
219+
220+
- Memory cache operations are synchronized via NSCache
221+
- Disk operations run on a dedicated serial queue
222+
- Completion handlers and async results are always delivered on the main thread
223+
224+
## Demo App
225+
226+
The repository includes a demo project showing typical usage in a `UITableViewController` with image loading.
227+
228+
## License
229+
230+
JMImageCache is available under the MIT license. See the LICENSE file for details.
231+
232+
## Author
233+
234+
Jake Marsh ([@jakemarsh](https://twitter.com/jakemarsh))
235+
236+
Originally created in 2011, rewritten in Swift with async/await in 2024.

0 commit comments

Comments
 (0)