diff --git a/Tusker/Caching/ImageCache.swift b/Tusker/Caching/ImageCache.swift index 53d95762..f560acb3 100644 --- a/Tusker/Caching/ImageCache.swift +++ b/Tusker/Caching/ImageCache.swift @@ -24,10 +24,6 @@ class ImageCache { private let cache: ImageDataCache private let desiredPixelSize: CGSize? - private var groups = MultiThreadDictionary() - - 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? { @@ -110,88 +102,11 @@ class ImageCache { func get(_ url: URL, loadOriginal: Bool = false) -> ImageDataCache.Entry? { 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 } diff --git a/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift b/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift index a1b26318..0479e1df 100644 --- a/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift +++ b/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift @@ -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) - } } diff --git a/Tusker/Screens/Conversation/ConversationTableViewController.swift b/Tusker/Screens/Conversation/ConversationTableViewController.swift index 9b62ee77..2884e698 100644 --- a/Tusker/Screens/Conversation/ConversationTableViewController.swift +++ b/Tusker/Screens/Conversation/ConversationTableViewController.swift @@ -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 { diff --git a/Tusker/Screens/Notifications/NotificationsTableViewController.swift b/Tusker/Screens/Notifications/NotificationsTableViewController.swift index 06c0931d..df4f46cc 100644 --- a/Tusker/Screens/Notifications/NotificationsTableViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsTableViewController.swift @@ -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) - } - } - } } diff --git a/Tusker/Screens/Utilities/StatusTablePrefetching.swift b/Tusker/Screens/Utilities/StatusTablePrefetching.swift index 9af1fc3e..f560b8be 100644 --- a/Tusker/Screens/Utilities/StatusTablePrefetching.swift +++ b/Tusker/Screens/Utilities/StatusTablePrefetching.swift @@ -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]? {