Apply LRUCache everywhere.
This commit is contained in:
parent
d6b23e4a45
commit
ccd2cedbc8
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@ -8,7 +8,7 @@ import UIKit
|
||||
// Coincides with Android's max text message length
|
||||
let kMaxMessageBodyCharacterCount = 2000
|
||||
|
||||
protocol AttachmentTextToolbarDelegate: AnyObject, MentionTextViewDelegate {
|
||||
protocol AttachmentTextToolbarDelegate: MentionTextViewDelegate {
|
||||
func attachmentTextToolbarDidTapSend(_ attachmentTextToolbar: AttachmentTextToolbar)
|
||||
func attachmentTextToolbarDidBeginEditing(_ attachmentTextToolbar: AttachmentTextToolbar)
|
||||
func attachmentTextToolbarDidEndEditing(_ attachmentTextToolbar: AttachmentTextToolbar)
|
||||
|
||||
@ -61,22 +61,12 @@ public class AnyLRUCache: NSObject {
|
||||
// A simple LRU cache bounded by the number of entries.
|
||||
public class LRUCache<KeyType: Hashable & Equatable, ValueType> {
|
||||
|
||||
private let unfairLock = UnfairLock()
|
||||
private var cacheMap: [KeyType: ValueType] = [:]
|
||||
private var cacheOrder: [KeyType] = []
|
||||
private let cache = NSCache<AnyObject, AnyObject>()
|
||||
private let maxSize: Int
|
||||
|
||||
public init(maxSize: Int, nseMaxSize: Int = 0) {
|
||||
self.maxSize = CurrentAppContext().isNSE ? nseMaxSize : maxSize
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(didReceiveMemoryWarning),
|
||||
name: UIApplication.didReceiveMemoryWarningNotification,
|
||||
object: nil)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(didEnterBackground),
|
||||
name: .OWSApplicationDidEnterBackground,
|
||||
object: nil)
|
||||
self.cache.countLimit = maxSize
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -95,66 +85,25 @@ public class LRUCache<KeyType: Hashable & Equatable, ValueType> {
|
||||
clear()
|
||||
}
|
||||
|
||||
private func markKeyAsFirst(key: KeyType) {
|
||||
cacheOrder = cacheOrder.filter { $0 != key }
|
||||
cacheOrder.append(key)
|
||||
}
|
||||
|
||||
public func get(key: KeyType) -> ValueType? {
|
||||
unfairLock.withLock {
|
||||
guard let value = cacheMap[key] else {
|
||||
// Miss
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hit
|
||||
markKeyAsFirst(key: key)
|
||||
|
||||
return value
|
||||
}
|
||||
return cache.object(forKey: key as AnyObject) as? ValueType
|
||||
}
|
||||
|
||||
public func set(key: KeyType, value: ValueType) {
|
||||
unfairLock.withLock {
|
||||
guard maxSize > 0 else {
|
||||
Logger.warn("Using disabled cache.")
|
||||
return
|
||||
}
|
||||
|
||||
cacheMap[key] = value
|
||||
|
||||
markKeyAsFirst(key: key)
|
||||
|
||||
while cacheOrder.count > maxSize {
|
||||
guard let staleKey = cacheOrder.first else {
|
||||
owsFailDebug("Cache ordering unexpectedly empty")
|
||||
return
|
||||
}
|
||||
cacheOrder.removeFirst()
|
||||
cacheMap.removeValue(forKey: staleKey)
|
||||
}
|
||||
guard maxSize > 0 else {
|
||||
Logger.warn("Using disabled cache.")
|
||||
return
|
||||
}
|
||||
cache.setObject(value as AnyObject, forKey: key as AnyObject)
|
||||
}
|
||||
|
||||
public func remove(key: KeyType) {
|
||||
unfairLock.withLock {
|
||||
guard maxSize > 0 else {
|
||||
Logger.warn("Using disabled cache.")
|
||||
return
|
||||
}
|
||||
|
||||
cacheMap.removeValue(forKey: key)
|
||||
|
||||
cacheOrder = cacheOrder.filter { $0 != key }
|
||||
}
|
||||
cache.removeObject(forKey: key as AnyObject)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func clear() {
|
||||
unfairLock.withLock {
|
||||
cacheMap.removeAll()
|
||||
cacheOrder.removeAll()
|
||||
}
|
||||
cache.removeAllObjects()
|
||||
}
|
||||
|
||||
// MARK: - NSCache Compatibility
|
||||
|
||||
140
SignalServiceKit/tests/Util/LRUCacheTest.swift
Normal file
140
SignalServiceKit/tests/Util/LRUCacheTest.swift
Normal file
@ -0,0 +1,140 @@
|
||||
//
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
import Curve25519Kit
|
||||
|
||||
@testable import SignalServiceKit
|
||||
|
||||
class LRUCacheTest: SSKBaseTestSwift {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
func testStringString() {
|
||||
let cache = LRUCache<String, String>(maxSize: 16)
|
||||
let key1 = "a"
|
||||
let key2 = "b"
|
||||
let key3 = "c"
|
||||
let value1 = "d"
|
||||
let value2 = "e"
|
||||
|
||||
XCTAssertNil(cache.get(key: key1))
|
||||
XCTAssertNil(cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
|
||||
cache.set(key: key1, value: value1)
|
||||
|
||||
XCTAssertEqual(value1, cache.get(key: key1))
|
||||
XCTAssertNil(cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
|
||||
cache.set(key: key2, value: value2)
|
||||
|
||||
XCTAssertEqual(value1, cache.get(key: key1))
|
||||
XCTAssertEqual(value2, cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
|
||||
cache.clear()
|
||||
|
||||
XCTAssertNil(cache.get(key: key1))
|
||||
XCTAssertNil(cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
}
|
||||
|
||||
func testStructStruct() {
|
||||
struct TestStruct: CustomStringConvertible, Hashable {
|
||||
let payload: String
|
||||
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
public var description: String { payload }
|
||||
|
||||
// MARK: - Hashable
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
payload.hash(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
let cache = LRUCache<TestStruct, TestStruct>(maxSize: 16)
|
||||
let key1a = TestStruct(payload: "a")
|
||||
let key1b = TestStruct(payload: "a")
|
||||
let key2 = TestStruct(payload: "b")
|
||||
let key3 = TestStruct(payload: "c")
|
||||
let value1 = TestStruct(payload: "d")
|
||||
let value2 = TestStruct(payload: "e")
|
||||
|
||||
XCTAssertNil(cache.get(key: key1a))
|
||||
XCTAssertNil(cache.get(key: key1b))
|
||||
XCTAssertNil(cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
|
||||
cache.set(key: key1a, value: value1)
|
||||
|
||||
XCTAssertEqual(value1, cache.get(key: key1a))
|
||||
XCTAssertEqual(value1, cache.get(key: key1b))
|
||||
XCTAssertNil(cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
|
||||
cache.set(key: key2, value: value2)
|
||||
|
||||
XCTAssertEqual(value1, cache.get(key: key1a))
|
||||
XCTAssertEqual(value1, cache.get(key: key1b))
|
||||
XCTAssertEqual(value2, cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
|
||||
cache.clear()
|
||||
|
||||
XCTAssertNil(cache.get(key: key1a))
|
||||
XCTAssertNil(cache.get(key: key1b))
|
||||
XCTAssertNil(cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
}
|
||||
|
||||
func testIntInt() {
|
||||
|
||||
let cache = LRUCache<Int, Int>(maxSize: 16)
|
||||
let key1a: Int = 1
|
||||
let key1b: Int = 1
|
||||
let key2: Int = 2
|
||||
let key3: Int = 3
|
||||
let value1: Int = 4
|
||||
let value2: Int = 5
|
||||
|
||||
XCTAssertNil(cache.get(key: key1a))
|
||||
XCTAssertNil(cache.get(key: key1b))
|
||||
XCTAssertNil(cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
|
||||
cache.set(key: key1a, value: value1)
|
||||
|
||||
XCTAssertEqual(value1, cache.get(key: key1a))
|
||||
XCTAssertEqual(value1, cache.get(key: key1b))
|
||||
XCTAssertNil(cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
|
||||
cache.set(key: key2, value: value2)
|
||||
|
||||
XCTAssertEqual(value1, cache.get(key: key1a))
|
||||
XCTAssertEqual(value1, cache.get(key: key1b))
|
||||
XCTAssertEqual(value2, cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
|
||||
cache.clear()
|
||||
|
||||
XCTAssertNil(cache.get(key: key1a))
|
||||
XCTAssertNil(cache.get(key: key1b))
|
||||
XCTAssertNil(cache.get(key: key2))
|
||||
XCTAssertNil(cache.get(key: key3))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user