Optimize table cell reloading when feed images are made available

This commit is contained in:
Maurice Parker 2019-08-26 12:54:23 -05:00
parent c3aebe7868
commit 1a3e2784ae
2 changed files with 36 additions and 17 deletions

View File

@ -164,8 +164,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange(_:)), name: .AccountStateDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange(_:)), name: .AccountStateDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChange, object: nil)
@ -475,7 +474,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
@objc func feedIconDidBecomeAvailable(_ note: Notification) { @objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { guard showAvatars, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
return return
} }
let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in
@ -511,10 +510,19 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
} }
} }
@objc func imageDidBecomeAvailable(_ note: Notification) { @objc func faviconDidBecomeAvailable(_ note: Notification) {
guard showAvatars, let faviconURL = note.userInfo?[FaviconDownloader.UserInfoKey.faviconURL] as? String else {
return
}
if showAvatars { let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in
queueReloadAvailableCells() guard let article = articles.articleAtRow(row) else {
return false
}
return article.feed?.faviconURL == faviconURL
}
if let indexesToReload = indexesToReload {
reloadCells(for: indexesToReload)
} }
} }

View File

@ -25,10 +25,11 @@ public final class FeedIconDownloader {
private var homePagesWithNoIconURL = Set<String>() private var homePagesWithNoIconURL = Set<String>()
private var urlsInProgress = Set<String>() private var urlsInProgress = Set<String>()
private var cache = [Feed: RSImage]() private var cache = [Feed: RSImage]()
private var waitingForFeedURLs = [String: Feed]()
init(imageDownloader: ImageDownloader) { init(imageDownloader: ImageDownloader) {
self.imageDownloader = imageDownloader self.imageDownloader = imageDownloader
NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader)
} }
func resetCache() { func resetCache() {
@ -45,7 +46,7 @@ public final class FeedIconDownloader {
guard let homePageURL = feed.homePageURL else { guard let homePageURL = feed.homePageURL else {
return return
} }
icon(forHomePageURL: homePageURL) { (image) in icon(forHomePageURL: homePageURL, feed: feed) { (image) in
if let image = image { if let image = image {
self.postFeedIconDidBecomeAvailableNotification(feed) self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = image self.cache[feed] = image
@ -54,7 +55,7 @@ public final class FeedIconDownloader {
} }
if let iconURL = feed.iconURL { if let iconURL = feed.iconURL {
icon(forURL: iconURL) { (image) in icon(forURL: iconURL, feed: feed) { (image) in
if let image = image { if let image = image {
self.postFeedIconDidBecomeAvailableNotification(feed) self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = image self.cache[feed] = image
@ -71,11 +72,20 @@ public final class FeedIconDownloader {
return nil return nil
} }
@objc func imageDidBecomeAvailable(_ note: Notification) {
guard let url = note.userInfo?[UserInfoKey.url] as? String, let feed = waitingForFeedURLs[url] else {
return
}
waitingForFeedURLs[url] = nil
_ = icon(for: feed)
}
} }
private extension FeedIconDownloader { private extension FeedIconDownloader {
func icon(forHomePageURL homePageURL: String, _ imageResultBlock: @escaping (RSImage?) -> Void) { func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
if homePagesWithNoIconURL.contains(homePageURL) { if homePagesWithNoIconURL.contains(homePageURL) {
imageResultBlock(nil) imageResultBlock(nil)
@ -83,14 +93,15 @@ private extension FeedIconDownloader {
} }
if let iconURL = cachedIconURL(for: homePageURL) { if let iconURL = cachedIconURL(for: homePageURL) {
icon(forURL: iconURL, imageResultBlock) icon(forURL: iconURL, feed: feed, imageResultBlock)
return return
} }
findIconURLForHomePageURL(homePageURL) findIconURLForHomePageURL(homePageURL, feed: feed)
} }
func icon(forURL url: String, _ imageResultBlock: @escaping (RSImage?) -> Void) { func icon(forURL url: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
waitingForFeedURLs[url] = feed
guard let imageData = imageDownloader.image(for: url) else { guard let imageData = imageDownloader.image(for: url) else {
imageResultBlock(nil) imageResultBlock(nil)
return return
@ -117,7 +128,7 @@ private extension FeedIconDownloader {
homePageToIconURLCache[homePageURL] = iconURL homePageToIconURLCache[homePageURL] = iconURL
} }
func findIconURLForHomePageURL(_ homePageURL: String) { func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed) {
guard !urlsInProgress.contains(homePageURL) else { guard !urlsInProgress.contains(homePageURL) else {
return return
@ -130,15 +141,15 @@ private extension FeedIconDownloader {
guard let metadata = metadata else { guard let metadata = metadata else {
return return
} }
self.pullIconURL(from: metadata, homePageURL: homePageURL) self.pullIconURL(from: metadata, homePageURL: homePageURL, feed: feed)
} }
} }
func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String) { func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String, feed: Feed) {
if let url = metadata.bestWebsiteIconURL() { if let url = metadata.bestWebsiteIconURL() {
cacheIconURL(for: homePageURL, url) cacheIconURL(for: homePageURL, url)
icon(forURL: url) { (image) in icon(forURL: url, feed: feed) { (image) in
} }
return return
} }