diff --git a/Tests/OrderedCacheTests.swift b/Tests/OrderedCacheTests.swift new file mode 100644 index 00000000..7ea3fd0e --- /dev/null +++ b/Tests/OrderedCacheTests.swift @@ -0,0 +1,63 @@ +// This file is part of Kiwix for iOS & macOS. +// +// Kiwix is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// any later version. +// +// Kiwix is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kiwix; If not, see https://www.gnu.org/licenses/. + +import XCTest +@testable import Kiwix + +final class OrderedCacheTests: XCTestCase { + + @MainActor + func testEmpty() { + let cache = OrderedCache() + XCTAssertEqual(cache.count, 0) + XCTAssertNil(cache.findBy(key: "not to be found")) + } + + @MainActor + func testOneItem() { + let cache = OrderedCache() + let pastDate = Date.distantPast + cache.setValue("test_value", forKey: "keyOne", dated: pastDate) + XCTAssertEqual(cache.count, 1) + XCTAssertNil(cache.findBy(key: "not to be found")) + XCTAssertEqual(cache.findBy(key: "keyOne"), "test_value") + cache.removeOlderThan(pastDate) + XCTAssertEqual(cache.count, 1) + cache.removeOlderThan(Date.now) + XCTAssertEqual(cache.count, 0) + } + + @MainActor + func testRemoveOlderThan() { + let cache = OrderedCache() + let nowDate = Date.now + cache.setValue("test_value", forKey: "keyOne", dated: nowDate) + cache.setValue("old_value", forKey: "keyOld", dated: Date.distantPast) + XCTAssertEqual(cache.count, 2) + cache.removeOlderThan(nowDate.advanced(by: -1)) + XCTAssertEqual(cache.count, 1) + } + + @MainActor + func testRemoveByKey() { + let cache = OrderedCache() + cache.setValue(1, forKey: "one") + cache.setValue(0, forKey: "zero") + cache.removeValue(forKey: "zero") + XCTAssertNil(cache.findBy(key: "zero")) + XCTAssertEqual(cache.findBy(key: "one"), 1) + } + +} diff --git a/ViewModel/BrowserViewModel.swift b/ViewModel/BrowserViewModel.swift index 690215cd..cfdaefb9 100644 --- a/ViewModel/BrowserViewModel.swift +++ b/ViewModel/BrowserViewModel.swift @@ -19,8 +19,6 @@ import CoreLocation import WebKit import Defaults import os - -import OrderedCollections import CoreKiwix // swiftlint:disable file_length @@ -28,23 +26,27 @@ import CoreKiwix final class BrowserViewModel: NSObject, ObservableObject, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate, NSFetchedResultsControllerDelegate { - private static var cache = OrderedDictionary() + @MainActor + private static var cache: OrderedCache? @MainActor static func getCached(tabID: NSManagedObjectID) -> BrowserViewModel { - let viewModel = cache[tabID] ?? BrowserViewModel(tabID: tabID) - cache.removeValue(forKey: tabID) - cache[tabID] = viewModel + if let cachedModel = cache?.findBy(key: tabID) { + return cachedModel + } + if cache == nil { + cache = .init() + } + let viewModel = BrowserViewModel(tabID: tabID) + cache?.removeValue(forKey: tabID) + cache?.setValue(viewModel, forKey: tabID) return viewModel } static func purgeCache() { - guard cache.count > 10 else { return } - let range = 0 ..< cache.count - 5 - cache.values[range].forEach { viewModel in - viewModel.persistState() + Task { @MainActor in + cache?.removeOlderThan(Date.now.advanced(by: -360)) // 6 minutes } - cache.removeSubrange(range) } // MARK: - Properties diff --git a/ViewModel/OrderedCache.swift b/ViewModel/OrderedCache.swift new file mode 100644 index 00000000..0fdc89b3 --- /dev/null +++ b/ViewModel/OrderedCache.swift @@ -0,0 +1,56 @@ +// This file is part of Kiwix for iOS & macOS. +// +// Kiwix is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// any later version. +// +// Kiwix is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kiwix; If not, see https://www.gnu.org/licenses/. + +import Foundation + +@MainActor +final class OrderedCache { + + private struct ValueDated { + let value: V + let date: Date + } + + private var dict: [Key: ValueDated] = [:] + + var count: Int { + dict.count + } + + func findBy(key: Key) -> Value? { + if let dateValue = dict[key] { + return dateValue.value + } + return nil + } + + func removeAll() { + dict = [:] + } + + func removeOlderThan(_ pastDate: Date) { + dict = dict.filter { (_, value: ValueDated) in + value.date >= pastDate + } + } + + func setValue(_ value: Value, forKey key: Key, dated: Date = Date.now) { + dict[key] = ValueDated(value: value, date: dated) + } + + func removeValue(forKey key: Key) { + dict.removeValue(forKey: key) + } +} diff --git a/project.yml b/project.yml index ab9b5f10..210edb94 100644 --- a/project.yml +++ b/project.yml @@ -48,9 +48,6 @@ packages: Defaults: url: https://github.com/sindresorhus/Defaults majorVersion: 6.0.0 - OrderedCollections: - url: https://github.com/apple/swift-collections.git - majorVersion: 1.0.4 targetTemplates: ApplicationTemplate: @@ -73,7 +70,6 @@ targetTemplates: - sdk: NotificationCenter.framework - sdk: QuickLook.framework - package: Defaults - - package: OrderedCollections sources: - path: App - path: Model