Skip to content

Instantly share code, notes, and snippets.

@IuriiIaremenko
Created March 3, 2021 13:15
Show Gist options
  • Save IuriiIaremenko/9fbef6861d9c62a5f3fccdadded5517a to your computer and use it in GitHub Desktop.
Save IuriiIaremenko/9fbef6861d9c62a5f3fccdadded5517a to your computer and use it in GitHub Desktop.
Example of simple and lightweight service for image caching without any 3rd party dependencies. UIKit + SwiftUI usage example
//
// ImageLoader.swift
//
// Created by Iurii Iaremenko on 05.02.2021.
//
import Foundation
import Combine
import class UIKit.UIImage
final class ImageLoader: ObservableObject {
@Published var image: UIImage
private var subscriptions = Set<AnyCancellable>()
init(url: URL?, placeholder: UIImage = #imageLiteral(resourceName: "placeholder")) {
image = placeholder
guard let url = url else {
return
}
ImageURLStorage.shared
.cachedImage(with: url)
.map { image = $0 }
ImageURLStorage.shared
.getImage(for: url)
.compactMap { $0 }
.receive(on: RunLoop.main)
.sink(
receiveCompletion: { _ in },
receiveValue: { [weak self] in
self?.image = $0
})
.store(in: &subscriptions)
}
}
final class OptionalImageLoader: ObservableObject {
@Published var image: UIImage?
private var subscriptions = Set<AnyCancellable>()
init(url: URL?, placeholder: UIImage? = nil) {
image = placeholder
guard let url = url else {
return
}
ImageURLStorage.shared
.cachedImage(with: url)
.map { image = $0 }
ImageURLStorage.shared
.getImage(for: url)
.compactMap { $0 }
.receive(on: RunLoop.main)
.sink(
receiveCompletion: { _ in },
receiveValue: { [weak self] in
self?.image = $0
})
.store(in: &subscriptions)
}
}
//
// ImageURLStorage.swift
//
// Created by Iurii Iaremenko on 05.02.2021.
//
import Foundation
import Combine
import class UIKit.UIImage
public protocol ImageStorage: AnyObject {
func getImage(for url: URL) -> AnyPublisher<UIImage?, Error>
func cachedImage(with url: URL) -> UIImage?
func clearStorage()
}
/// Simple and lightweight image caching without any 3rd party dependencies.
public final class ImageURLStorage: ImageStorage {
public static let shared: ImageStorage = ImageURLStorage()
private let cache: URLCache
private let session: URLSession
private let cacheSize: Int = .megaBytes(150)
private init() {
let config = URLSessionConfiguration.default
cache = URLCache(memoryCapacity: cacheSize, diskCapacity: cacheSize)
config.urlCache = cache
config.requestCachePolicy = .reloadRevalidatingCacheData
config.httpMaximumConnectionsPerHost = 5
session = URLSession(configuration: config)
}
public func getImage(for url: URL) -> AnyPublisher<UIImage?, Error> {
latestData(with: url)
.map(UIImage.init)
.eraseToAnyPublisher()
}
public func cachedImage(with url: URL) -> UIImage? {
let request = URLRequest(url: url)
let data = cache.cachedResponse(for: request)?.data
return data.flatMap(UIImage.init)
}
public func clearStorage() {
cache.removeAllCachedResponses()
}
}
extension ImageURLStorage {
private func latestData(with url: URL) -> AnyPublisher<Data, Error> {
let request = URLRequest(url: url)
return session
.dataTaskPublisher(for: request)
.map(\.data)
.mapError { $0 as Error }
.eraseToAnyPublisher()
}
}
private extension Int {
static func megaBytes(_ number: Int) -> Int {
number * 1024 * 1024
}
}
//
// UIImageView+Additions.swift
//
// Created by Iurii Iaremenko on 05.05.2020.
//
import class UIKit.UIImageView
import class UIKit.UIImage
import Combine
public extension UIImageView {
func setImage(with url: URL?, placeholder: UIImage? = nil, _ completion: ((UIImageView, UIImage?) -> Void)? = nil) -> AnyCancellable {
image = placeholder
guard let url = url else {
return Empty<Void, Never>(completeImmediately: true).sink(receiveCompletion: { [weak self] _ in
guard let self = self else { return }
completion?(self, nil)
}, receiveValue: {})
}
ImageURLStorage.shared
.cachedImage(with: url)
.map { image = $0 }
return ImageURLStorage.shared
.getImage(for: url)
.receiveOnMain()
.sink(
receiveCompletion: { _ in },
receiveValue: { [weak self] in
guard let self = self else { return }
self.image = $0
completion?(self, $0)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment