Implement image caching for the MapSnapshottingService.
This commit is contained in:
parent
d26ac2b866
commit
e2f97420a3
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import CoreLocation
|
||||
|
||||
|
||||
|
@ -15,5 +16,12 @@ extension Pad {
|
|||
var coordinate: CLLocationCoordinate2D {
|
||||
.init(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
|
||||
|
||||
func snapshotCacheKey(from size: CGSize) -> String {
|
||||
"Snapshot Key (\(size.width) X \(size.height))) :: " +
|
||||
"Pad \(id) :: " +
|
||||
"Latitude: \(latitude), Longitude: \(longitude)"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Cache+Entry.swift
|
||||
// PadFinder
|
||||
//
|
||||
// Created by CypherPoet on 1/30/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
internal extension Cache {
|
||||
|
||||
/// A class (because NSCache entries need to be class instances)
|
||||
/// to wrap the underlying value (class or not) that we want to cache.
|
||||
final class Entry {
|
||||
let value: Value
|
||||
|
||||
init(value: Value) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// Cache+WrappedKey.swift
|
||||
// PadFinder
|
||||
//
|
||||
// Created by CypherPoet on 1/30/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
internal extension Cache {
|
||||
|
||||
/// A class that inherits from `NSObject` (because NSCache keys need to
|
||||
/// do so) and wraps the underlying key (anything `Hashable`, class or not),
|
||||
/// that we want to use with a cache entry.
|
||||
final class WrappedKey: NSObject {
|
||||
let key: Key
|
||||
|
||||
init(_ key: Key) {
|
||||
self.key = key
|
||||
}
|
||||
|
||||
|
||||
override var hash: Int { key.hashValue }
|
||||
|
||||
|
||||
override func isEqual(_ object: Any?) -> Bool {
|
||||
guard let otherValue = object as? WrappedKey else { return false }
|
||||
|
||||
return otherValue.key == key
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Cache.swift
|
||||
// PadFinder
|
||||
//
|
||||
// Created by CypherPoet on 1/30/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
final class Cache<Key: Hashable, Value> {
|
||||
private let wrappedCache = NSCache<WrappedKey, Entry>()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Public API Methods
|
||||
extension Cache {
|
||||
|
||||
func insert(_ value: Value, forKey key: Key) {
|
||||
let entry = Entry(value: value)
|
||||
|
||||
wrappedCache.setObject(entry, forKey: WrappedKey(key))
|
||||
}
|
||||
|
||||
|
||||
func value(forKey key: Key) -> Value? {
|
||||
if let entry = wrappedCache.object(forKey: WrappedKey(key)) {
|
||||
return entry.value
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func removeValue(forKey key: Key) {
|
||||
wrappedCache.removeObject(forKey: WrappedKey(key))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Subscript
|
||||
extension Cache {
|
||||
|
||||
subscript(key: Key) -> Value? {
|
||||
get { value(forKey: key) }
|
||||
|
||||
set {
|
||||
guard let valueToCache = newValue else {
|
||||
// If nil was assigned using our subscript,
|
||||
// then we remove any value for that key:
|
||||
removeValue(forKey: key)
|
||||
return
|
||||
}
|
||||
|
||||
insert(valueToCache, forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,12 +13,17 @@ import Combine
|
|||
|
||||
|
||||
protocol MapSnapshotServicing: class {
|
||||
typealias SnapshotCache = Cache<String, MKMapSnapshotter.Snapshot>
|
||||
|
||||
var snapshotOptions: MKMapSnapshotter.Options { get }
|
||||
var queue: DispatchQueue { get }
|
||||
var snapshotCache: SnapshotCache { get }
|
||||
|
||||
|
||||
func takeSnapshot(
|
||||
with size: CGSize,
|
||||
at coordinate: CLLocationCoordinate2D,
|
||||
cacheKey: String?,
|
||||
size: CGSize,
|
||||
coordinate: CLLocationCoordinate2D,
|
||||
latitudeSpan: CLLocationDegrees,
|
||||
longitudeSpan: CLLocationDegrees
|
||||
) -> Future<MKMapSnapshotter.Snapshot, Error>
|
||||
|
@ -28,37 +33,53 @@ protocol MapSnapshotServicing: class {
|
|||
extension MapSnapshotServicing {
|
||||
|
||||
func takeSnapshot(
|
||||
with size: CGSize,
|
||||
at coordinate: CLLocationCoordinate2D,
|
||||
cacheKey: String? = nil,
|
||||
size: CGSize,
|
||||
coordinate: CLLocationCoordinate2D,
|
||||
latitudeSpan: CLLocationDegrees = 1.75,
|
||||
longitudeSpan: CLLocationDegrees = 1.75
|
||||
) -> Future<MKMapSnapshotter.Snapshot, Error> {
|
||||
let span = MKCoordinateSpan(latitudeDelta: latitudeSpan, longitudeDelta: longitudeSpan)
|
||||
|
||||
snapshotOptions.region = MKCoordinateRegion(
|
||||
center: coordinate,
|
||||
span: span
|
||||
)
|
||||
|
||||
snapshotOptions.size = size
|
||||
|
||||
let snapshotter = MKMapSnapshotter(options: snapshotOptions)
|
||||
|
||||
// TOOD: Ideally, we'd implement some kind of cahcing here, or save the images
|
||||
// as part of each pad model -- which could be persisted in Core Data.
|
||||
return Future { promise in
|
||||
Future { promise in
|
||||
// if
|
||||
// let cacheKey = cacheKey,
|
||||
// let cachedSnapshot = self.snapshotFromCache(key: cacheKey)
|
||||
// {
|
||||
// return promise(.success(cachedSnapshot))
|
||||
// }
|
||||
|
||||
let span = MKCoordinateSpan(
|
||||
latitudeDelta: latitudeSpan,
|
||||
longitudeDelta: longitudeSpan
|
||||
)
|
||||
|
||||
self.snapshotOptions.region = MKCoordinateRegion(
|
||||
center: coordinate,
|
||||
span: span
|
||||
)
|
||||
|
||||
self.snapshotOptions.size = size
|
||||
|
||||
let snapshotter = MKMapSnapshotter(options: self.snapshotOptions)
|
||||
|
||||
snapshotter.start(with: self.queue) { (snapshot, error) in
|
||||
guard error == nil else {
|
||||
guard let snapshot = snapshot else {
|
||||
return promise(.failure(error!))
|
||||
}
|
||||
|
||||
guard let snapshot = snapshot else {
|
||||
preconditionFailure("No snapshot returned despite snapshotter completing without error.")
|
||||
if let cacheKey = cacheKey {
|
||||
self.snapshotCache.insert(snapshot, forKey: cacheKey)
|
||||
}
|
||||
|
||||
return promise(.success(snapshot))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func snapshotFromCache(key: String) -> MKMapSnapshotter.Snapshot? {
|
||||
snapshotCache.value(forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -12,14 +12,20 @@ import MapKit
|
|||
|
||||
final class MapSnapshottingService: MapSnapshotServicing {
|
||||
var snapshotOptions: MKMapSnapshotter.Options
|
||||
var snapshotCache: MapSnapshotServicing.SnapshotCache
|
||||
var queue: DispatchQueue
|
||||
|
||||
|
||||
static var sharedSnapshotCache: MapSnapshotServicing.SnapshotCache = .init()
|
||||
|
||||
|
||||
init(
|
||||
snapshotOptions: MKMapSnapshotter.Options = .init(),
|
||||
snapshotCache: MapSnapshotServicing.SnapshotCache = sharedSnapshotCache,
|
||||
queue: DispatchQueue = DispatchQueue(label: "Map Snapshotting Service", qos: .default)
|
||||
) {
|
||||
self.snapshotOptions = snapshotOptions
|
||||
self.snapshotCache = snapshotCache
|
||||
self.queue = queue
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue