diff --git a/SignalMessaging/ViewControllers/AttachmentApproval/AttachmentTextToolbar.swift b/SignalMessaging/ViewControllers/AttachmentApproval/AttachmentTextToolbar.swift index 417df1409b..10c27c1830 100644 --- a/SignalMessaging/ViewControllers/AttachmentApproval/AttachmentTextToolbar.swift +++ b/SignalMessaging/ViewControllers/AttachmentApproval/AttachmentTextToolbar.swift @@ -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) diff --git a/SignalServiceKit/src/Util/LRUCache.swift b/SignalServiceKit/src/Util/LRUCache.swift index ea8395d400..6f48cb27cc 100644 --- a/SignalServiceKit/src/Util/LRUCache.swift +++ b/SignalServiceKit/src/Util/LRUCache.swift @@ -61,22 +61,12 @@ public class AnyLRUCache: NSObject { // A simple LRU cache bounded by the number of entries. public class LRUCache { - private let unfairLock = UnfairLock() - private var cacheMap: [KeyType: ValueType] = [:] - private var cacheOrder: [KeyType] = [] + private let cache = NSCache() 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 { 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 diff --git a/SignalServiceKit/tests/Util/LRUCacheTest.swift b/SignalServiceKit/tests/Util/LRUCacheTest.swift new file mode 100644 index 0000000000..13022b7654 --- /dev/null +++ b/SignalServiceKit/tests/Util/LRUCacheTest.swift @@ -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(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(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(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)) + } +}