Ditch custom image request grouping, rely on URLSession's
This commit is contained in:
parent
ba032412eb
commit
59d866aa23
|
@ -24,10 +24,6 @@ class ImageCache {
|
|||
private let cache: ImageDataCache
|
||||
private let desiredPixelSize: CGSize?
|
||||
|
||||
private var groups = MultiThreadDictionary<URL, RequestGroup>()
|
||||
|
||||
private var backgroundQueue = DispatchQueue(label: "ImageCache completion queue", qos: .default)
|
||||
|
||||
init(name: String, memoryExpiry: CacheExpiry, diskExpiry: CacheExpiry? = nil, desiredSize: CGSize? = nil) {
|
||||
// todo: might not always want to use UIScreen.main for this, e.g. Catalyst?
|
||||
let pixelSize = desiredSize?.applying(.init(scaleX: UIScreen.main.scale, y: UIScreen.main.scale))
|
||||
|
@ -61,14 +57,9 @@ class ImageCache {
|
|||
wrappedCompletion?(entry.data, entry.image)
|
||||
return nil
|
||||
} else {
|
||||
if let group = groups[url] {
|
||||
return group.addCallback(wrappedCompletion)
|
||||
} else {
|
||||
let group = createGroup(url: url)
|
||||
let request = group.addCallback(wrappedCompletion)
|
||||
group.run()
|
||||
return request
|
||||
}
|
||||
let task = dataTask(url: url, completion: wrappedCompletion)
|
||||
task.resume()
|
||||
return task
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,22 +76,23 @@ class ImageCache {
|
|||
// if caching is disabled, don't bother fetching since nothing will be done with the result
|
||||
guard !ImageCache.disableCaching else { return }
|
||||
|
||||
if !((try? cache.has(url.absoluteString)) ?? false),
|
||||
!groups.contains(key: url) {
|
||||
let group = createGroup(url: url)
|
||||
group.run()
|
||||
if !((try? cache.has(url.absoluteString)) ?? false) {
|
||||
let task = dataTask(url: url) { data, image in
|
||||
guard let data else { return }
|
||||
try? self.cache.set(url.absoluteString, data: data, image: image)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
private func createGroup(url: URL) -> RequestGroup {
|
||||
let group = RequestGroup(url: url) { (data, image) in
|
||||
if let data = data {
|
||||
try? self.cache.set(url.absoluteString, data: data, image: image)
|
||||
private func dataTask(url: URL, completion: ((Data?, UIImage?) -> Void)?) -> URLSessionDataTask {
|
||||
return URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
guard error == nil,
|
||||
let data else {
|
||||
return
|
||||
}
|
||||
_ = self.groups.removeValue(forKey: url)
|
||||
completion?(data, UIImage(data: data))
|
||||
}
|
||||
groups[url] = group
|
||||
return group
|
||||
}
|
||||
|
||||
func getData(_ url: URL) -> Data? {
|
||||
|
@ -111,87 +103,10 @@ class ImageCache {
|
|||
return try? cache.get(url.absoluteString, loadOriginal: loadOriginal)
|
||||
}
|
||||
|
||||
func cancelWithoutCallback(_ url: URL) {
|
||||
groups[url]?.cancelWithoutCallback()
|
||||
}
|
||||
|
||||
func reset() throws {
|
||||
try cache.removeAll()
|
||||
}
|
||||
|
||||
private class RequestGroup {
|
||||
let url: URL
|
||||
private let onFinished: (Data?, UIImage?) -> Void
|
||||
private var task: URLSessionDataTask?
|
||||
private var requests = [Request]()
|
||||
|
||||
init(url: URL, onFinished: @escaping (Data?, UIImage?) -> Void) {
|
||||
self.url = url
|
||||
self.onFinished = onFinished
|
||||
}
|
||||
|
||||
deinit {
|
||||
task?.cancel()
|
||||
}
|
||||
|
||||
func run() {
|
||||
task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
|
||||
guard error == nil, let data = data else {
|
||||
self.complete(with: nil)
|
||||
return
|
||||
}
|
||||
self.complete(with: data)
|
||||
})
|
||||
task!.resume()
|
||||
}
|
||||
|
||||
func addCallback(_ completion: ((Data?, UIImage?) -> Void)?) -> Request {
|
||||
let request = Request(callback: completion)
|
||||
requests.append(request)
|
||||
return request
|
||||
}
|
||||
|
||||
func cancelWithoutCallback() {
|
||||
if let request = requests.first(where: { $0.callback == nil && !$0.cancelled }) {
|
||||
request.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func requestCancelled() {
|
||||
let remaining = requests.filter { !$0.cancelled }.count
|
||||
if remaining <= 0 {
|
||||
task?.cancel()
|
||||
complete(with: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func complete(with data: Data?) {
|
||||
let image = data != nil ? UIImage(data: data!) : nil
|
||||
|
||||
requests.filter { !$0.cancelled }.forEach {
|
||||
if let callback = $0.callback {
|
||||
callback(data, image)
|
||||
}
|
||||
}
|
||||
|
||||
self.onFinished(data, image)
|
||||
}
|
||||
}
|
||||
|
||||
class Request {
|
||||
private weak var group: RequestGroup?
|
||||
private(set) var callback: ((Data?, UIImage?) -> Void)?
|
||||
private(set) var cancelled: Bool = false
|
||||
|
||||
init(callback: ((Data?, UIImage?) -> Void)?) {
|
||||
self.callback = callback
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
cancelled = true
|
||||
callback = nil
|
||||
group?.requestCancelled()
|
||||
}
|
||||
}
|
||||
typealias Request = URLSessionDataTask
|
||||
|
||||
}
|
||||
|
|
|
@ -175,14 +175,4 @@ extension BookmarksTableViewController: UITableViewDataSourcePrefetching, Status
|
|||
let ids = indexPaths.map { statuses[$0.row].id }
|
||||
prefetchStatuses(with: ids)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
let ids: [String] = indexPaths.compactMap {
|
||||
guard $0.row < statuses.count else {
|
||||
return nil
|
||||
}
|
||||
return statuses[$0.row].id
|
||||
}
|
||||
cancelPrefetchingStatuses(with: ids)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -467,11 +467,6 @@ extension ConversationTableViewController: UITableViewDataSourcePrefetching, Sta
|
|||
let ids: [String] = indexPaths.compactMap { item(for: $0)?.id }
|
||||
prefetchStatuses(with: ids)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
let ids: [String] = indexPaths.compactMap { item(for: $0)?.id }
|
||||
cancelPrefetchingStatuses(with: ids)
|
||||
}
|
||||
}
|
||||
|
||||
extension ConversationTableViewController: ToastableViewController {
|
||||
|
|
|
@ -294,14 +294,4 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let group = dataSource.itemIdentifier(for: indexPath)?.group else { continue }
|
||||
for notification in group.notifications {
|
||||
guard let avatar = notification.account.avatar else { continue }
|
||||
ImageCache.avatars.cancelWithoutCallback(avatar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,22 +30,6 @@ extension StatusTablePrefetching {
|
|||
}
|
||||
}
|
||||
|
||||
func cancelPrefetchingStatuses(with ids: [String]) {
|
||||
let context = apiController.persistentContainer.prefetchBackgroundContext
|
||||
context.perform {
|
||||
guard let statuses = getStatusesWith(ids: ids, in: context) else {
|
||||
return
|
||||
}
|
||||
for status in statuses {
|
||||
guard let avatar = status.account.avatar else { continue }
|
||||
ImageCache.avatars.cancelWithoutCallback(avatar)
|
||||
for attachment in status.attachments where attachment.kind == .image {
|
||||
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileprivate func getStatusesWith(ids: [String], in context: NSManagedObjectContext) -> [StatusMO]? {
|
||||
|
|
Loading…
Reference in New Issue