From a1feffaf036944ba1021340f1293861e3a4c612c Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 30 Mar 2022 16:39:51 -0700 Subject: [PATCH] Prefetch page content on iOS --- .../App/Views/Home/HomeFeedViewModel.swift | 3 ++ .../Views/WebReader/WebReaderViewModel.swift | 10 ++++++ .../Sources/Models/ArticleContent.swift | 8 +++++ .../Services/DataService/DataService.swift | 32 +++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index e0ebabb8d..6a917f435 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -76,6 +76,9 @@ final class HomeFeedViewModel: ObservableObject { if thisSearchIdx > 0, thisSearchIdx <= self?.receivedIdx ?? 0 { return } + + dataService.prefetchPages(items: result.items) + self?.items = isRefresh ? result.items : (self?.items ?? []) + result.items self?.isLoading = false self?.receivedIdx = thisSearchIdx diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift index 1dd5aa8dd..f111608c9 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -28,6 +28,15 @@ final class WebReaderViewModel: ObservableObject { guard let viewer = dataService.currentViewer else { return } + if let content = dataService.pageCache.object(forKey: NSString(string: slug)) { + print("RETRIEVED FORM CACHE", slug) + isLoading = false + articleContent = content.value + return + } else { + print("MISSED CACHE", slug) + } + dataService.articleContentPublisher(username: viewer.username, slug: slug).sink( receiveCompletion: { [weak self] completion in guard case .failure = completion else { return } @@ -35,6 +44,7 @@ final class WebReaderViewModel: ObservableObject { }, receiveValue: { [weak self] articleContent in self?.articleContent = articleContent + dataService.pageCache.setObject(CachedPageContent(articleContent), forKey: NSString(string: slug)) } ) .store(in: &subscriptions) diff --git a/apple/OmnivoreKit/Sources/Models/ArticleContent.swift b/apple/OmnivoreKit/Sources/Models/ArticleContent.swift index bc55cf570..7a3d5b0e7 100644 --- a/apple/OmnivoreKit/Sources/Models/ArticleContent.swift +++ b/apple/OmnivoreKit/Sources/Models/ArticleContent.swift @@ -1,5 +1,13 @@ import Foundation +public class CachedPageContent: NSObject { + public let value: ArticleContent + + public init(_ content: ArticleContent) { + self.value = content + } +} + public struct ArticleContent { public let htmlContent: String public let highlights: [Highlight] diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 12deb1da3..aca49bdfa 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -1,6 +1,13 @@ +import Combine import Foundation import Models +public class CacheManager: NSObject, NSCacheDelegate { + public func cache(_: NSCache, willEvictObject obj: Any) { + print("evicting object", obj) + } +} + public final class DataService: ObservableObject { public static var registerIntercomUser: ((String) -> Void)? public static var showIntercomMessenger: (() -> Void)? @@ -9,12 +16,37 @@ public final class DataService: ObservableObject { public internal(set) var currentViewer: Viewer? let networker: Networker + public let pageCache = NSCache() + let pageCacheQueue = DispatchQueue.global(qos: .background) + let highlightsCache = NSCache() let highlightsCacheQueue = DispatchQueue(label: "app.omnivore.highlights.cache.queue", attributes: .concurrent) + let cacheManager: CacheManager + var subscriptions = Set() + public init(appEnvironment: AppEnvironment, networker: Networker) { self.appEnvironment = appEnvironment self.networker = networker + self.cacheManager = CacheManager() + pageCache.delegate = cacheManager + } + + public func prefetchPages(items: [FeedItem]) { + print("prefetching items", items, "cost limit", pageCache.countLimit) + + guard let viewer = currentViewer else { return } + + for item in items { + let slug = item.slug + articleContentPublisher(username: viewer.username, slug: slug).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] articleContent in + self?.pageCache.setObject(CachedPageContent(articleContent), forKey: NSString(string: slug)) + } + ) + .store(in: &subscriptions) + } } public func clearHighlights() {