Improve ImageCache loading
Keep track of the number of requests and only cancel the underlying URLSessionTask if there are no concrete requsts remaining. Closes #81
This commit is contained in:
parent
3220436893
commit
d6ae51c02f
|
@ -12,13 +12,13 @@ import Cache
|
|||
class ImageCache {
|
||||
|
||||
static let avatars = ImageCache(name: "Avatars", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24))
|
||||
static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24))
|
||||
static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60))
|
||||
static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2))
|
||||
static let emojis = ImageCache(name: "Emojis", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60))
|
||||
|
||||
let cache: Cache<Data>
|
||||
|
||||
var requests = [URL: Request]()
|
||||
var requests = [URL: RequestGroup]()
|
||||
|
||||
init(name: String, memoryExpiry expiry: Expiry) {
|
||||
let storage = MemoryStorage<Data>(config: MemoryConfig(expiry: expiry))
|
||||
|
@ -36,20 +36,22 @@ class ImageCache {
|
|||
self.cache = .hybrid(HybridStorage(memoryStorage: memory, diskStorage: disk))
|
||||
}
|
||||
|
||||
func get(_ url: URL, completion: ((Data?) -> Void)?) {
|
||||
func get(_ url: URL, completion: ((Data?) -> Void)?) -> Request? {
|
||||
let key = url.absoluteString
|
||||
if (try? cache.existsObject(forKey: key)) ?? false,
|
||||
let data = try? cache.object(forKey: key) {
|
||||
completion?(data)
|
||||
return nil
|
||||
} else {
|
||||
if let completion = completion, let request = requests[url] {
|
||||
request.callbacks.append(completion)
|
||||
if let completion = completion, let group = requests[url] {
|
||||
return group.addCallback(completion)
|
||||
} else {
|
||||
let request = Request(url: url, completion: completion)
|
||||
requests[url] = request
|
||||
request.run { (data) in
|
||||
let group = RequestGroup(url: url)
|
||||
let request = group.addCallback(completion)
|
||||
group.run { (data) in
|
||||
try? self.cache.setObject(data, forKey: key)
|
||||
}
|
||||
return request
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,24 +60,23 @@ class ImageCache {
|
|||
return try? cache.object(forKey: url.absoluteString)
|
||||
}
|
||||
|
||||
func cancel(_ url: URL) {
|
||||
requests[url]?.cancel()
|
||||
func cancelWithoutCallback(_ url: URL) {
|
||||
requests[url]?.cancelWithoutCallback()
|
||||
}
|
||||
|
||||
class Request {
|
||||
class RequestGroup {
|
||||
let url: URL
|
||||
var task: URLSessionDataTask?
|
||||
var callbacks: [(Data?) -> Void]
|
||||
private var task: URLSessionDataTask?
|
||||
private var requests = [Request]()
|
||||
|
||||
init(url: URL, completion: ((Data?) -> Void)?) {
|
||||
if let completion = completion {
|
||||
self.callbacks = [completion]
|
||||
} else {
|
||||
self.callbacks = []
|
||||
}
|
||||
init(url: URL) {
|
||||
self.url = url
|
||||
}
|
||||
|
||||
deinit {
|
||||
task?.cancel()
|
||||
}
|
||||
|
||||
func run(cache: @escaping (Data) -> Void) {
|
||||
task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
|
||||
guard error == nil, let data = data else {
|
||||
|
@ -88,13 +89,56 @@ class ImageCache {
|
|||
task!.resume()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
task?.cancel()
|
||||
complete(with: nil)
|
||||
private func updatePriority() {
|
||||
task?.priority = max(1.0, URLSessionTask.defaultPriority + 0.1 * Float(requests.filter { !$0.cancelled }.count))
|
||||
}
|
||||
|
||||
func addCallback(_ completion: ((Data?) -> Void)?) -> Request {
|
||||
let request = Request(callback: completion)
|
||||
requests.append(request)
|
||||
updatePriority()
|
||||
return request
|
||||
}
|
||||
|
||||
func cancelWithoutCallback() {
|
||||
if let request = requests.first(where: { $0.callback == nil && !$0.cancelled }) {
|
||||
request.cancel()
|
||||
updatePriority()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func requestCancelled() {
|
||||
let remaining = requests.filter { !$0.cancelled }.count
|
||||
if remaining <= 0 {
|
||||
task?.cancel()
|
||||
complete(with: nil)
|
||||
} else {
|
||||
updatePriority()
|
||||
}
|
||||
}
|
||||
|
||||
func complete(with data: Data?) {
|
||||
callbacks.forEach { $0(data) }
|
||||
requests.filter { !$0.cancelled }.forEach {
|
||||
if let callback = $0.callback {
|
||||
callback(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Request {
|
||||
weak var group: RequestGroup?
|
||||
private(set) var callback: ((Data?) -> Void)?
|
||||
private(set) var cancelled: Bool = false
|
||||
|
||||
init(callback: ((Data?) -> Void)?) {
|
||||
self.callback = callback
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
cancelled = true
|
||||
callback = nil
|
||||
group?.requestCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ class AttachmentViewController: UIViewController {
|
|||
var largeImageVC: LargeImageViewController?
|
||||
var loadingVC: LoadingViewController?
|
||||
|
||||
var attachmentRequest: ImageCache.Request?
|
||||
|
||||
private var initialControlsVisible: Bool = true
|
||||
var controlsVisible: Bool {
|
||||
get {
|
||||
|
@ -51,15 +53,25 @@ class AttachmentViewController: UIViewController {
|
|||
} else {
|
||||
loadingVC = LoadingViewController()
|
||||
embedChild(loadingVC!)
|
||||
ImageCache.attachments.get(attachment.url) { [weak self] (data) in
|
||||
attachmentRequest = ImageCache.attachments.get(attachment.url) { [weak self] (data) in
|
||||
guard let self = self else { return }
|
||||
self.attachmentRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self?.loadingVC?.removeViewAndController()
|
||||
self?.createLargeImage(data: data!)
|
||||
self.loadingVC?.removeViewAndController()
|
||||
self.createLargeImage(data: data!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didMove(toParent parent: UIViewController?) {
|
||||
super.didMove(toParent: parent)
|
||||
|
||||
if parent == nil {
|
||||
attachmentRequest?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func createLargeImage(data: Data) {
|
||||
guard let image = UIImage(data: data) else { return }
|
||||
largeImageVC = LargeImageViewController(image: image, description: attachment.description, sourceInfo: nil)
|
||||
|
|
|
@ -159,9 +159,9 @@ extension BookmarksTableViewController: UITableViewDataSourcePrefetching {
|
|||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
_ = ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
for attachment in status.attachments where attachment.kind == .image {
|
||||
ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
_ = ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,9 +169,9 @@ extension BookmarksTableViewController: UITableViewDataSourcePrefetching {
|
|||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
ImageCache.avatars.cancel(status.account.avatar)
|
||||
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
||||
for attachment in status.attachments where attachment.kind == .image {
|
||||
ImageCache.attachments.cancel(attachment.url)
|
||||
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,9 +170,9 @@ extension ConversationTableViewController: UITableViewDataSourcePrefetching {
|
|||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
_ = ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
_ = ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,9 +180,9 @@ extension ConversationTableViewController: UITableViewDataSourcePrefetching {
|
|||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
ImageCache.avatars.cancel(status.account.avatar)
|
||||
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.cancel(attachment.url)
|
||||
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
|||
for indexPath in indexPaths {
|
||||
for notificationID in groups[indexPath.row].notificationIDs {
|
||||
guard let notification = mastodonController.cache.notification(for: notificationID) else { continue }
|
||||
ImageCache.avatars.get(notification.account.avatar, completion: nil)
|
||||
_ = ImageCache.avatars.get(notification.account.avatar, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
|||
for indexPath in indexPaths {
|
||||
for notificationID in groups[indexPath.row].notificationIDs {
|
||||
guard let notification = mastodonController.cache.notification(for: notificationID) else { continue }
|
||||
ImageCache.avatars.cancel(notification.account.avatar)
|
||||
ImageCache.avatars.cancelWithoutCallback(notification.account.avatar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ class MyProfileTableViewController: ProfileTableViewController {
|
|||
mastodonController.getOwnAccount { (account) in
|
||||
self.accountID = account.id
|
||||
|
||||
ImageCache.avatars.get(account.avatar, completion: { (data) in
|
||||
guard let data = data, let image = UIImage(data: data) else { return }
|
||||
_ = ImageCache.avatars.get(account.avatar, completion: { [weak self] (data) in
|
||||
guard let self = self, let data = data, let image = UIImage(data: data) else { return }
|
||||
DispatchQueue.main.async {
|
||||
let size = CGSize(width: 30, height: 30)
|
||||
let tabBarImage = UIGraphicsImageRenderer(size: size).image { (_) in
|
||||
|
|
|
@ -294,9 +294,9 @@ extension ProfileTableViewController: UITableViewDataSourcePrefetching {
|
|||
for indexPath in indexPaths where indexPath.section > 1 {
|
||||
let statusID = timelineSegments[indexPath.section - 2][indexPath.row].id
|
||||
guard let status = mastodonController.cache.status(for: statusID) else { continue }
|
||||
ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
_ = ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
_ = ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -305,9 +305,9 @@ extension ProfileTableViewController: UITableViewDataSourcePrefetching {
|
|||
for indexPath in indexPaths where indexPath.section > 1 {
|
||||
let statusID = timelineSegments[indexPath.section - 2][indexPath.row].id
|
||||
guard let status = mastodonController.cache.status(for: statusID) else { continue }
|
||||
ImageCache.avatars.cancel(status.account.avatar)
|
||||
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.cancel(attachment.url)
|
||||
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,9 +165,9 @@ extension TimelineTableViewController: UITableViewDataSourcePrefetching {
|
|||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = mastodonController.cache.status(for: statusID(for: indexPath)) else { continue }
|
||||
ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
_ = ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
_ = ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,9 +175,9 @@ extension TimelineTableViewController: UITableViewDataSourcePrefetching {
|
|||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = mastodonController.cache.status(for: statusID(for: indexPath)) else { continue }
|
||||
ImageCache.avatars.cancel(status.account.avatar)
|
||||
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.cancel(attachment.url)
|
||||
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class AccountTableViewCell: UITableViewCell {
|
|||
|
||||
var accountID: String!
|
||||
|
||||
var avatarURL: URL?
|
||||
var avatarRequest: ImageCache.Request?
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
@ -44,9 +44,9 @@ class AccountTableViewCell: UITableViewCell {
|
|||
fatalError("Missing cached account \(accountID)")
|
||||
}
|
||||
|
||||
self.avatarURL = account.avatar
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard let data = data, self.avatarURL == account.avatar else { return }
|
||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.accountID == accountID else { return }
|
||||
self.avatarRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = UIImage(data: data)
|
||||
}
|
||||
|
@ -57,6 +57,12 @@ class AccountTableViewCell: UITableViewCell {
|
|||
updateUIForPrefrences()
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
avatarRequest?.cancel()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AccountTableViewCell: SelectableTableViewCell {
|
||||
|
|
|
@ -15,6 +15,8 @@ class LargeAccountDetailView: UIView {
|
|||
var displayNameLabel = UILabel()
|
||||
var usernameLabel = UILabel()
|
||||
|
||||
var avatarRequest: ImageCache.Request?
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
|
@ -49,6 +51,10 @@ class LargeAccountDetailView: UIView {
|
|||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
deinit {
|
||||
avatarRequest?.cancel()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
|
@ -63,8 +69,9 @@ class LargeAccountDetailView: UIView {
|
|||
displayNameLabel.text = account.realDisplayName
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard let data = data else { return }
|
||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
||||
guard let self = self, let data = data else { return }
|
||||
self.avatarRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = UIImage(data: data)
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
var attachment: Attachment!
|
||||
var index: Int!
|
||||
|
||||
private var attachmentRequest: ImageCache.Request?
|
||||
|
||||
var gifData: Data?
|
||||
|
||||
public lazy var animator: Animator? = Animator(withDelegate: self)
|
||||
|
@ -37,6 +39,10 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
loadAttachment()
|
||||
}
|
||||
|
||||
deinit {
|
||||
attachmentRequest?.cancel()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
commonInit()
|
||||
|
@ -71,8 +77,9 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
}
|
||||
|
||||
func loadImage() {
|
||||
ImageCache.attachments.get(attachment.url) { [weak self] (data) in
|
||||
attachmentRequest = ImageCache.attachments.get(attachment.url) { [weak self] (data) in
|
||||
guard let self = self, let data = data else { return }
|
||||
self.attachmentRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
if self.attachment.url.pathExtension == "gif" {
|
||||
self.animate(withGIFData: data)
|
||||
|
@ -87,12 +94,14 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
}
|
||||
|
||||
func loadVideo() {
|
||||
let attachmentURL = self.attachment.url
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let asset = AVURLAsset(url: self.attachment.url)
|
||||
let asset = AVURLAsset(url: attachmentURL)
|
||||
let generator = AVAssetImageGenerator(asset: asset)
|
||||
generator.appliesPreferredTrackTransform = true
|
||||
guard let image = try? generator.copyCGImage(at: CMTime(seconds: 0, preferredTimescale: 1), actualTime: nil) else { return }
|
||||
DispatchQueue.main.async {
|
||||
guard self.attachment.url == attachmentURL else { return }
|
||||
self.image = UIImage(cgImage: image)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,16 @@ class ComposeStatusReplyView: UIView {
|
|||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var statusContentTextView: StatusContentTextView!
|
||||
|
||||
var avatarRequest: ImageCache.Request?
|
||||
|
||||
static func create() -> ComposeStatusReplyView {
|
||||
return UINib(nibName: "ComposeStatusReplyView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! ComposeStatusReplyView
|
||||
}
|
||||
|
||||
deinit {
|
||||
avatarRequest?.cancel()
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
|
@ -39,8 +45,8 @@ class ComposeStatusReplyView: UIView {
|
|||
statusContentTextView.overrideMastodonController = mastodonController
|
||||
statusContentTextView.statusID = status.id
|
||||
|
||||
ImageCache.avatars.get(status.account.avatar) { (data) in
|
||||
guard let data = data else { return }
|
||||
avatarRequest = ImageCache.avatars.get(status.account.avatar) { [weak self] (data) in
|
||||
guard let self = self, let data = data else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = UIImage(data: data)
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ class ContentTextView: LinkTextView {
|
|||
|
||||
for emoji in emojis {
|
||||
group.enter()
|
||||
ImageCache.emojis.get(emoji.url) { (data) in
|
||||
_ = ImageCache.emojis.get(emoji.url) { (data) in
|
||||
defer { group.leave() }
|
||||
guard let data = data, let image = UIImage(data: data) else {
|
||||
return
|
||||
|
|
|
@ -20,6 +20,7 @@ class InstanceTableViewCell: UITableViewCell {
|
|||
var selectorInstance: InstanceSelector.Instance?
|
||||
|
||||
var thumbnailURL: URL?
|
||||
var thumbnailRequest: ImageCache.Request?
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
@ -59,12 +60,21 @@ class InstanceTableViewCell: UITableViewCell {
|
|||
private func updateThumbnail(url: URL) {
|
||||
thumbnailImageView.image = nil
|
||||
thumbnailURL = url
|
||||
ImageCache.attachments.get(url) { (data) in
|
||||
guard self.thumbnailURL == url, let data = data, let image = UIImage(data: data) else { return }
|
||||
thumbnailRequest = ImageCache.attachments.get(url) { [weak self] (data) in
|
||||
guard let self = self, self.thumbnailURL == url, let data = data, let image = UIImage(data: data) else { return }
|
||||
self.thumbnailRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.thumbnailImageView.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
thumbnailRequest?.cancel()
|
||||
instance = nil
|
||||
selectorInstance = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
var group: NotificationGroup!
|
||||
var statusID: String!
|
||||
|
||||
var authorAvatarURL: URL?
|
||||
var avatarRequests = [String: ImageCache.Request]()
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
deinit {
|
||||
|
@ -75,9 +75,10 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard let data = data, self.group.id == group.id else { return }
|
||||
avatarRequests[account.id] = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.group.id == group.id else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.avatarRequests.removeValue(forKey: account.id)
|
||||
imageView.image = UIImage(data: data)
|
||||
}
|
||||
}
|
||||
|
@ -149,10 +150,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
if let authorAvatarURL = authorAvatarURL {
|
||||
ImageCache.avatars.cancel(authorAvatarURL)
|
||||
}
|
||||
|
||||
avatarRequests.values.forEach { $0.cancel() }
|
||||
updateTimestampWorkItem?.cancel()
|
||||
updateTimestampWorkItem = nil
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
|
||||
var group: NotificationGroup!
|
||||
|
||||
var avatarRequests = [String: ImageCache.Request]()
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
deinit {
|
||||
|
@ -55,9 +56,10 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard let data = data, self.group.id == group.id else { return }
|
||||
avatarRequests[account.id] = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.group.id == group.id else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.avatarRequests.removeValue(forKey: account.id)
|
||||
imageView.image = UIImage(data: data)
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +116,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
avatarRequests.values.forEach { $0.cancel() }
|
||||
updateTimestampWorkItem?.cancel()
|
||||
updateTimestampWorkItem = nil
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
|||
var notification: Pachyderm.Notification?
|
||||
var account: Account!
|
||||
|
||||
var avatarRequest: ImageCache.Request?
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
deinit {
|
||||
|
@ -53,8 +54,9 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
|||
func updateUI(account: Account) {
|
||||
self.account = account
|
||||
actionLabel.text = "Request to follow from \(account.realDisplayName)"
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard self.account == account, let data = data, let image = UIImage(data: data) else { return }
|
||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
||||
guard let self = self, self.account == account, let data = data, let image = UIImage(data: data) else { return }
|
||||
self.avatarRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
}
|
||||
|
@ -88,6 +90,7 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
|||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
avatarRequest?.cancel()
|
||||
updateTimestampWorkItem?.cancel()
|
||||
updateTimestampWorkItem = nil
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
|||
|
||||
var accountID: String!
|
||||
|
||||
var avatarURL: URL?
|
||||
var headerURL: URL?
|
||||
var avatarRequest: ImageCache.Request?
|
||||
var headerRequest: ImageCache.Request?
|
||||
|
||||
override func awakeFromNib() {
|
||||
avatarContainerView.layer.masksToBounds = true
|
||||
|
@ -63,19 +63,19 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
|||
usernameLabel.text = "@\(account.acct)"
|
||||
|
||||
avatarImageView.image = nil
|
||||
avatarURL = account.avatar
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard let data = data else { return }
|
||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.accountID == accountID else { return }
|
||||
self.avatarRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = UIImage(data: data)
|
||||
self.avatarURL = nil
|
||||
}
|
||||
}
|
||||
ImageCache.headers.get(account.header) { (data) in
|
||||
guard let data = data else { return }
|
||||
headerImageView.image = nil
|
||||
headerRequest = ImageCache.headers.get(account.header) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.accountID == accountID else { return }
|
||||
self.headerRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.headerImageView.image = UIImage(data: data)
|
||||
self.headerURL = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,12 +131,8 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
if let url = avatarURL {
|
||||
ImageCache.avatars.cancel(url)
|
||||
}
|
||||
if let url = headerURL {
|
||||
ImageCache.headers.cancel(url)
|
||||
}
|
||||
avatarRequest?.cancel()
|
||||
headerRequest?.cancel()
|
||||
}
|
||||
|
||||
@objc func morePressed() {
|
||||
|
|
|
@ -64,8 +64,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
}
|
||||
var showStatusAutomatically = false
|
||||
|
||||
var avatarURL: URL?
|
||||
var attachmentDataTasks: [URLSessionDataTask] = []
|
||||
private var avatarRequest: ImageCache.Request?
|
||||
|
||||
private var statusUpdater: Cancellable?
|
||||
private var accountUpdater: Cancellable?
|
||||
|
@ -180,12 +179,10 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
func updateUI(account: Account) {
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
avatarImageView.image = nil
|
||||
avatarURL = account.avatar
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard let data = data, self.avatarURL == account.avatar else { return }
|
||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.accountID == account.id else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = UIImage(data: data)
|
||||
self.avatarURL = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,9 +197,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
if let avatarURL = avatarURL {
|
||||
ImageCache.avatars.cancel(avatarURL)
|
||||
}
|
||||
avatarRequest?.cancel()
|
||||
attachmentsView.attachmentViews.allObjects.forEach { $0.removeFromSuperview() }
|
||||
showStatusAutomatically = false
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
}
|
||||
|
||||
func updateTimestamp() {
|
||||
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
guard let mastodonController = mastodonController, let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
|
||||
timestampLabel.text = status.createdAt.timeAgoString()
|
||||
timestampLabel.accessibilityLabel = TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date())
|
||||
|
|
Loading…
Reference in New Issue