Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,31 @@ import UIKit
public extension UIImageView {
@MainActor
func setAsyncImage(_ url: URL) async {
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let image = UIImage(data: data)
else {
debugPrint("이미지 다운로드 실패: \(url)")
if let cachedImage = ImageCache.readCache(with: url) { // MARK: 캐시 히트
guard let cachedUIImage = UIImage(data: cachedImage.imageData) else {
debugPrint("캐싱이미지 변환에 실패했습니다. \(url)")
return
}
self.image = image

} catch {
debugPrint("이미지 다운로드 중 오류 발생: \(error.localizedDescription)")
self.image = cachedUIImage
} else { // MARK: 캐시 히트에 실패한 경우
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let image = UIImage(data: data)
else {
debugPrint("이미지 다운로드 실패: \(url)")
return
}

let cachingImage = CacheableImage(imageData: data)
ImageCache.updateCache(with: url, image: cachingImage)

self.image = image

} catch {
debugPrint("이미지 다운로드 중 오류 발생: \(error.localizedDescription)")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

public final class CacheableImage: Codable {
let imageData: Data

public init(imageData: Data) {
self.imageData = imageData
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import UIKit

public enum ImageCache {
private static let memoryCache: NSCache<NSString, CacheableImage> = {
let memoryCache = NSCache<NSString, CacheableImage>()
memoryCache.totalCostLimit = 1024 * 1024 * 50 // MARK: 메모리 캐시 최대 용량 50MB
return memoryCache
}()

private static let diskCache: ImageDiskCache = {
let diskCache = ImageDiskCache()
return diskCache
}()

/// 메모리 캐시의 최대 용량을 설정합니다.
public static func setMaximumMemoryCache(with maximumBytes: Int) {
self.memoryCache.totalCostLimit = maximumBytes
}

public static func readCache(with imageURL: URL) -> CacheableImage? {
let imageURLStr = imageURL.absoluteString as NSString

if let memoryCachedImage = memoryCache.object(forKey: imageURLStr) {
return memoryCachedImage
} else {
guard let diskCachedImage = diskCache.readCache(with: imageURL) else { return nil }
memoryCache.setObject(diskCachedImage, forKey: imageURLStr)
return diskCachedImage
}
}

public static func updateCache(with imageURL: URL, image: CacheableImage) {
let key = imageURL.absoluteString as NSString
memoryCache.setObject(image, forKey: key)
diskCache.updateCache(with: imageURL, image: image)
}

public static func removeCache() {
memoryCache.removeAllObjects()
diskCache.removeCache()
}

}

public class DiskCache<K, V> where K: NSString, V: Codable {
let fileManager = FileManager.default
let cacheDirectoryPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
}

public final class ImageDiskCache: DiskCache<NSString, CacheableImage> {
public func readCache(with imageURL: URL) -> CacheableImage? {
guard let path = super.cacheDirectoryPath else { return nil }
let filePath = path.appendingPathComponent(imageURL.pathComponents.joined(separator: "-"))

if fileManager.fileExists(atPath: filePath.path) {
guard let imageData = try? Data(contentsOf: filePath) else { return nil }
return CacheableImage(imageData: imageData)
}
return nil
}

public func updateCache(with imageURL: URL, image: CacheableImage) {
guard let path = super.cacheDirectoryPath else { return }
let filePath = path.appendingPathComponent(imageURL.pathComponents.joined(separator: "-"))

fileManager.createFile(atPath: filePath.path, contents: image.imageData)
}

public func removeCache() {
guard let path = super.cacheDirectoryPath else { return }
guard let files = try? fileManager.contentsOfDirectory(atPath: path.path) else { return }

files.forEach {
let filePath = path.appendingPathComponent($0)
try? fileManager.removeItem(at: filePath)
}
}
}