-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #232 from Automattic/etoledom/image-caching-concur…
…rency Refactor image cache
- Loading branch information
Showing
15 changed files
with
156 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,73 @@ | ||
import Foundation | ||
import UIKit | ||
|
||
/// Represents a cache for images. | ||
/// | ||
/// An ImageCaching represents a cache for CacheEntry elements. Each CacheEntry is either an instance of an image, or the task of retrieving an image from | ||
/// remote. | ||
public protocol ImageCaching { | ||
func setImage(_ image: UIImage, forKey key: String) | ||
func getImage(forKey key: String) -> UIImage? | ||
/// Saves an image in the cache. | ||
/// - Parameters: | ||
/// - image: The cache entry to set. | ||
/// - key: The entry's key, used to be found via `.getEntry(key:)`. | ||
func setEntry(_ entry: CacheEntry, for key: String) | ||
|
||
/// Gets a `CacheEntry` from cache for the given key, or nil if none is found. | ||
/// | ||
/// - Parameter key: The key for the entry to get. | ||
/// - Returns: The cache entry which could contain an image, the task that is retrieving the image. Nil is returned if nothing is found. | ||
/// | ||
/// `.inProgress(task)` is used by the image downloader to check if there's already an ongoing download task for the same image. If yes, the image | ||
/// downloader awaits that ask instead of starting a new one. | ||
func getEntry(with key: String) -> CacheEntry? | ||
} | ||
|
||
public class ImageCache: ImageCaching { | ||
private let cache = NSCache<NSString, UIImage>() | ||
/// The default `ImageCaching` used by this SDK. | ||
public struct ImageCache: ImageCaching { | ||
private let cache = NSCache<NSString, CacheEntryObject>() | ||
|
||
/// The default cache used by the image dowloader. | ||
public static var shared: ImageCaching = ImageCache() | ||
public static let shared: ImageCaching = ImageCache() | ||
|
||
public init() {} | ||
|
||
public func setImage(_ image: UIImage, forKey key: String) { | ||
cache.setObject(image, forKey: key as NSString) | ||
public func setEntry(_ entry: CacheEntry, for key: String) { | ||
cache[key] = .init(entry) | ||
} | ||
|
||
public func getEntry(with key: String) -> CacheEntry? { | ||
cache[key] | ||
} | ||
} | ||
|
||
/// ImageCache can save an in-progress task of retreiving an image from remote. | ||
/// This enum represent both possible states for an image in the cache system. | ||
public enum CacheEntry: Sendable { | ||
/// A task of retreiving an image is in progress. | ||
case inProgress(Task<UIImage, Error>) | ||
/// An image instance is readily available. | ||
case ready(UIImage) | ||
} | ||
|
||
private final class CacheEntryObject { | ||
let entry: CacheEntry | ||
init(entry: CacheEntry) { self.entry = entry } | ||
} | ||
|
||
public func getImage(forKey key: String) -> UIImage? { | ||
cache.object(forKey: key as NSString) | ||
extension NSCache where KeyType == NSString, ObjectType == CacheEntryObject { | ||
fileprivate subscript(_ key: String) -> CacheEntry? { | ||
get { | ||
let key = key as NSString | ||
let value = object(forKey: key) | ||
return value?.entry | ||
} | ||
set { | ||
let key = key as NSString | ||
if let entry = newValue { | ||
let value = CacheEntryObject(entry: entry) | ||
setObject(value, forKey: key) | ||
} else { | ||
removeObject(forKey: key) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,32 @@ | ||
import Foundation | ||
import Gravatar | ||
import UIKit | ||
|
||
class TestImageCache: ImageCaching { | ||
var dict: [String: UIImage] = [:] | ||
var dict: [String: CacheEntryWrapper] = [:] | ||
|
||
var getImageCallCount = 0 | ||
var setImageCallsCount = 0 | ||
var setTaskCallCount = 0 | ||
|
||
init() {} | ||
|
||
func setImage(_ image: UIImage, forKey key: String) { | ||
setImageCallsCount += 1 | ||
dict[key] = image | ||
func setEntry(_ entry: Gravatar.CacheEntry, for key: String) { | ||
switch entry { | ||
case .inProgress: | ||
setTaskCallCount += 1 | ||
case .ready: | ||
setImageCallsCount += 1 | ||
} | ||
dict[key] = CacheEntryWrapper(entry) | ||
} | ||
|
||
func getImage(forKey key: String) -> UIImage? { | ||
func getEntry(with key: String) -> Gravatar.CacheEntry? { | ||
getImageCallCount += 1 | ||
return dict[key] | ||
return dict[key]?.cacheEntry | ||
} | ||
} | ||
|
||
class CacheEntryWrapper { | ||
let cacheEntry: CacheEntry | ||
init(_ cacheEntry: CacheEntry) { | ||
self.cacheEntry = cacheEntry | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,32 @@ | ||
import Foundation | ||
import Gravatar | ||
import UIKit | ||
|
||
class TestImageCache: ImageCaching { | ||
var dict: [String: UIImage] = [:] | ||
var dict: [String: CacheEntryWrapper] = [:] | ||
|
||
var getImageCallCount = 0 | ||
var setImageCallsCount = 0 | ||
var setTaskCallCount = 0 | ||
|
||
init() {} | ||
|
||
func setImage(_ image: UIImage, forKey key: String) { | ||
setImageCallsCount += 1 | ||
dict[key] = image | ||
func setEntry(_ entry: Gravatar.CacheEntry, for key: String) { | ||
switch entry { | ||
case .inProgress: | ||
setTaskCallCount += 1 | ||
case .ready: | ||
setImageCallsCount += 1 | ||
} | ||
dict[key] = CacheEntryWrapper(entry) | ||
} | ||
|
||
func getImage(forKey key: String) -> UIImage? { | ||
func getEntry(with key: String) -> Gravatar.CacheEntry? { | ||
getImageCallCount += 1 | ||
return dict[key] | ||
return dict[key]?.cacheEntry | ||
} | ||
} | ||
|
||
class CacheEntryWrapper { | ||
let cacheEntry: CacheEntry | ||
init(_ cacheEntry: CacheEntry) { | ||
self.cacheEntry = cacheEntry | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters