Skip to content

Commit 2a7fb86

Browse files
authored
Merge pull request #250 from boostcampwm-2022/feature/imageCache
Feature/image cache
2 parents 8fe2da1 + 28f951f commit 2a7fb86

File tree

6 files changed

+244
-97
lines changed

6 files changed

+244
-97
lines changed

Queenfisher/Sources/ImageCache/ImageCacheImplements.swift

+46-20
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ public final class DefaultImageCache: ImageCacheProtocol {
1313
// MARK: - Properties
1414
private let memoryCache = MemoryCacheStorage()
1515
private let diskCache = DiskCacheStorage()
16+
private let imageDownloader = ImageDownloader()
1617

18+
private var lastImage: CacheableImage?
1719
// 여기서 imageData 하나의 크기까지 지정해주려고 했는데 data 자체의 크기는 크지 않고, image로 바꾸는 연산이 들어가야하므로 빼기로 결정했습니다.
1820
// private let imageCostLimit: Int
1921

@@ -23,20 +25,39 @@ public final class DefaultImageCache: ImageCacheProtocol {
2325
}
2426

2527
// MARK: - Methods
26-
public func fetch(at url: URL, completion: @escaping (CacheableImage?) -> Void) {
27-
memoryCache.fetch(at: url) { [weak self] cacheableImage in
28-
if let cacheableImage {
29-
completion(cacheableImage)
30-
return
31-
}
32-
33-
// disk cache fetch
34-
self?.diskCache.fetch(at: url) { [weak self] diskImage in
35-
self?.executeDiskCacheLogic(diskImage: diskImage, url: url) { image in
36-
completion(image)
37-
}
38-
}
28+
public func fetchCached(at url: URL) -> CacheableImage? {
29+
return lastImage
30+
}
31+
32+
public func fetch(at url: URL, completion: @escaping (CacheableImage?) -> Void) -> URLSessionDataTask? {
33+
34+
if let memoryCached = memoryCache.fetch(at: url) {
35+
completion(memoryCached)
36+
return nil
37+
}
38+
39+
let diskCached = diskCache.fetch(at: url)
40+
var returnImage: CacheableImage?
41+
let task = self.executeDiskCacheLogic(diskImage: diskCached, url: url) { [weak self] image in
42+
returnImage = image
43+
self?.lastImage = image
3944
}
45+
completion(returnImage)
46+
return task
47+
// memoryCache.fetch(at: url) { [weak self] cacheableImage in
48+
// if let cacheableImage {
49+
// completion(cacheableImage)
50+
// return
51+
// }
52+
//
53+
// // disk cache fetch
54+
// self?.diskCache.fetch(at: url) { [weak self] diskImage in
55+
// task = self?.executeDiskCacheLogic(diskImage: diskImage, url: url) { image in
56+
// completion(image)
57+
// }
58+
// }
59+
// }
60+
// return task
4061
}
4162

4263
func config(_ configType: ConfigType) {
@@ -51,26 +72,29 @@ public final class DefaultImageCache: ImageCacheProtocol {
5172
// MARK: 이미지 캐시 로직
5273
extension DefaultImageCache {
5374

54-
private func executeDiskCacheLogic(diskImage: CacheableImage?, url: URL, completion: @escaping (CacheableImage?) -> Void) {
75+
private func executeDiskCacheLogic(diskImage: CacheableImage?, url: URL, completion: @escaping (CacheableImage?) -> Void) -> URLSessionDataTask {
5576
// 캐시에 값 O
77+
let dataTask: URLSessionDataTask
5678
if let diskImage {
57-
diskCacheHitted(diskImage: diskImage, url: url) { cacheableImage in
79+
dataTask = diskCacheHitted(diskImage: diskImage, url: url) { cacheableImage in
5880
completion(cacheableImage)
5981
return
6082
}
83+
6184
}
6285
// 캐시에 값 X
6386
else {
64-
diskCacheNotHitted(url: url) { cacheableImage in
87+
dataTask = diskCacheNotHitted(url: url) { cacheableImage in
6588
completion(cacheableImage)
6689
return
6790
}
6891
}
6992
completion(nil)
93+
return dataTask
7094
}
7195

72-
private func diskCacheHitted(diskImage: CacheableImage, url: URL, completion: @escaping (CacheableImage?) -> Void) {
73-
self.fetchImage(at: url, etag: diskImage.etag) { [weak self] result in
96+
private func diskCacheHitted(diskImage: CacheableImage, url: URL, completion: @escaping (CacheableImage?) -> Void) -> URLSessionDataTask {
97+
let dataTask = self.imageDownloader.fetchImage(at: url, etag: diskImage.etag) { [weak self] result in
7498
switch result {
7599
case .success(let networkImage): // 데이터 변경되어서 새로운 데이터 받아왔을 경우
76100
self?.diskCache.save(of: networkImage, at: url)
@@ -88,10 +112,11 @@ extension DefaultImageCache {
88112
}
89113
}
90114
}
115+
return dataTask
91116
}
92117

93-
private func diskCacheNotHitted(url: URL, completion: @escaping (CacheableImage?) -> Void) {
94-
self.fetchImage(at: url, etag: nil) { [weak self] result in
118+
private func diskCacheNotHitted(url: URL, completion: @escaping (CacheableImage?) -> Void) -> URLSessionDataTask {
119+
let dataTask = self.imageDownloader.fetchImage(at: url, etag: nil) { [weak self] result in
95120
switch result {
96121
case .success(let cacheableImage):
97122
self?.memoryCache.save(at: url, of: cacheableImage)
@@ -103,5 +128,6 @@ extension DefaultImageCache {
103128
return
104129
}
105130
}
131+
return dataTask
106132
}
107133
}

Queenfisher/Sources/ImageCache/ImageCacheProtocol.swift

+28-26
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,36 @@ import Foundation
1111
public protocol ImageCacheProtocol {
1212

1313
// func fetch(at url: URL, etag: String?, completion: @escaping (QFData?) -> Void)
14-
func fetch(at url: URL, completion: @escaping (CacheableImage?) -> Void)
14+
// func fetch(at url: URL, completion: @escaping (CacheableImage?) -> Void) -> URLSessionDataTask
15+
func fetchCached(at url: URL) -> CacheableImage?
16+
func fetch(at url: URL, completion: @escaping (CacheableImage?) -> Void) -> URLSessionDataTask?
1517
}
1618

1719
extension ImageCacheProtocol {
1820

19-
func fetchImage(at url: URL, etag: String?, completion: @escaping (Result<CacheableImage, ImageCacheError>) -> Void) {
20-
var urlRequest = URLRequest(url: url)
21-
22-
if let etag = etag {
23-
urlRequest.addValue(etag, forHTTPHeaderField: "If-None-Match")
24-
}
25-
26-
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
27-
guard let response = response as? HTTPURLResponse else {
28-
completion(.failure(.httpResponseTransformError))
29-
return
30-
}
31-
switch response.statusCode {
32-
case (200...299):
33-
guard let data else { completion(.failure(.unknownError)); return }
34-
let etag = response.allHeaderFields["Etag"] as? String ?? ""
35-
let image = CacheableImage(imageData: data, etag: etag)
36-
completion(.success(image))
37-
case 304:
38-
completion(.failure(.imageNotModifiedError))
39-
default:
40-
completion(.failure(.unknownError))
41-
}
42-
}
43-
}
21+
// func fetchImage(at url: URL, etag: String?, completion: @escaping (Result<CacheableImage, ImageCacheError>) -> Void) {
22+
// var urlRequest = URLRequest(url: url)
23+
//
24+
// if let etag = etag {
25+
// urlRequest.addValue(etag, forHTTPHeaderField: "If-None-Match")
26+
// }
27+
//
28+
// URLSession.shared.dataTask(with: urlRequest) { data, response, error in
29+
// guard let response = response as? HTTPURLResponse else {
30+
// completion(.failure(.httpResponseTransformError))
31+
// return
32+
// }
33+
// switch response.statusCode {
34+
// case (200...299):
35+
// guard let data else { completion(.failure(.unknownError)); return }
36+
// let etag = response.allHeaderFields["Etag"] as? String ?? ""
37+
// let image = CacheableImage(imageData: data, etag: etag)
38+
// completion(.success(image))
39+
// case 304:
40+
// completion(.failure(.imageNotModifiedError))
41+
// default:
42+
// completion(.failure(.unknownError))
43+
// }
44+
// }
45+
// }
4446
}

Queenfisher/Sources/ImageCache/ImageCacheStorage.swift

+23-19
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,15 @@ public final class MemoryCacheStorage {
1717

1818
// MARK: Methods
1919

20-
public func fetch(at url: URL, completion: @escaping (CacheableImage?) -> Void) {
20+
public func fetch(at url: URL) -> CacheableImage? {
2121
let key = key(for: url)
2222

2323
if let data = memoryCache.object(forKey: key) {
2424
let image = try? JSONDecoder().decode(CacheableImage.self, from: data as Data)
25-
completion(image)
26-
return
25+
return image
2726
}
2827
else {
29-
completion(nil)
30-
return
28+
return nil
3129
}
3230
}
3331

@@ -71,20 +69,26 @@ public final class DiskCacheStorage {
7169
self.limitCount = diskConfig
7270
}
7371

74-
public func fetch(at url: URL, completion: @escaping (CacheableImage?) -> Void) {
75-
DispatchQueue.global().async { [weak self] in
76-
guard let self else { return }
77-
78-
let localPath = self.path(for: url)
79-
guard
80-
let data = try? QFData(contentsOf: localPath),
81-
let decoded = try? JSONDecoder().decode(CacheableImage.self, from: data)
82-
else {
83-
completion(nil)
84-
return
85-
}
86-
completion(decoded)
87-
}
72+
public func fetch(at url: URL) -> CacheableImage? {
73+
// DispatchQueue.global().async { [weak self] in
74+
// guard let self else { return }
75+
//
76+
// let localPath = self.path(for: url)
77+
// guard
78+
// let data = try? QFData(contentsOf: localPath),
79+
// let decoded = try? JSONDecoder().decode(CacheableImage.self, from: data)
80+
// else {
81+
// completion(nil)
82+
// return
83+
// }
84+
// completion(decoded)
85+
// }
86+
let localPath = self.path(for: url)
87+
guard
88+
let data = try? QFData(contentsOf: localPath),
89+
let decoded = try? JSONDecoder().decode(CacheableImage.self, from: data)
90+
else { return nil }
91+
return decoded
8892
}
8993

9094
public func save(of cacheableImage: CacheableImage, at url: URL) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// ImageDownloader.swift
3+
// Queenfisher
4+
//
5+
// Created by kimchansoo on 2022/12/29.
6+
// Copyright © 2022 Trinap. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
final class ImageDownloader {
12+
13+
// MARK: Properties
14+
15+
// MARK: Initializers
16+
17+
// MARK: Methods
18+
func fetchImage(at url: URL, etag: String?, completion: @escaping (Result<CacheableImage, ImageCacheError>) -> Void) -> URLSessionDataTask {
19+
20+
var urlRequest = URLRequest(url: url)
21+
22+
if let etag = etag {
23+
urlRequest.addValue(etag, forHTTPHeaderField: "If-None-Match")
24+
}
25+
26+
let dataTask = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
27+
guard let response = response as? HTTPURLResponse else {
28+
completion(.failure(.httpResponseTransformError))
29+
return
30+
}
31+
switch response.statusCode {
32+
case (200...299):
33+
guard let data else { completion(.failure(.unknownError)); return }
34+
let etag = response.allHeaderFields["Etag"] as? String ?? ""
35+
let image = CacheableImage(imageData: data, etag: etag)
36+
completion(.success(image))
37+
case 304:
38+
completion(.failure(.imageNotModifiedError))
39+
default:
40+
completion(.failure(.unknownError))
41+
}
42+
}
43+
44+
return dataTask
45+
}
46+
}

0 commit comments

Comments
 (0)