diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index cf24874c3..034cc18aa 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -275,6 +275,7 @@ 4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */; }; 4523149E1F7E916B003A428C /* SlideOffAnimatedTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149D1F7E916B003A428C /* SlideOffAnimatedTransition.swift */; }; 452314A01F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */; }; + 4523D016206EDC2B00A2AB51 /* LRUCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523D015206EDC2B00A2AB51 /* LRUCache.swift */; }; 452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; }; 452C7CA72037628B003D51A5 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170D51E315310003FC1F2 /* Weak.swift */; }; 452D1EE81DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */; }; @@ -874,6 +875,7 @@ 4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldHelper.swift; sourceTree = ""; }; 4523149D1F7E916B003A428C /* SlideOffAnimatedTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SlideOffAnimatedTransition.swift; path = UserInterface/SlideOffAnimatedTransition.swift; sourceTree = ""; }; 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectionalPanGestureRecognizer.swift; sourceTree = ""; }; + 4523D015206EDC2B00A2AB51 /* LRUCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRUCache.swift; sourceTree = ""; }; 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutboundCallInitiator.swift; sourceTree = ""; }; 452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MesssagesBubblesSizeCalculatorTest.swift; path = Models/MesssagesBubblesSizeCalculatorTest.swift; sourceTree = ""; }; 452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentPointerView.swift; sourceTree = ""; }; @@ -1356,6 +1358,7 @@ 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */, 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */, 346129AC1FD1F34E00532771 /* ImageCache.swift */, + 4523D015206EDC2B00A2AB51 /* LRUCache.swift */, 34C3C7902040B0DC0000134C /* OWSAudioPlayer.h */, 34C3C7912040B0DC0000134C /* OWSAudioPlayer.m */, 45666EC41D99483D008FE134 /* OWSAvatarBuilder.h */, @@ -3037,6 +3040,7 @@ 45BE4EA22012AD2000935E59 /* DisappearingTimerConfigurationView.swift in Sources */, 346129F71FD5F31400532771 /* OWS105AttachmentFilePaths.m in Sources */, 45194F931FD7215C00333B2C /* OWSContactOffersInteraction.m in Sources */, + 4523D016206EDC2B00A2AB51 /* LRUCache.swift in Sources */, 344F249A200FD03300CFB4F4 /* MessageApprovalViewController.swift in Sources */, 450998681FD8C0FF00D89EB3 /* AttachmentSharing.m in Sources */, 347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */, diff --git a/Signal/src/network/GiphyDownloader.swift b/Signal/src/network/GiphyDownloader.swift index 1cbd9bfe6..40071753e 100644 --- a/Signal/src/network/GiphyDownloader.swift +++ b/Signal/src/network/GiphyDownloader.swift @@ -368,49 +368,6 @@ enum GiphyAssetRequestState: UInt { } } -// A simple LRU cache bounded by the number of entries. -// -// TODO: We might want to observe memory pressure notifications. -class LRUCache { - - private var cacheMap = [KeyType: ValueType]() - private var cacheOrder = [KeyType]() - private let maxSize: Int - - init(maxSize: Int) { - self.maxSize = maxSize - } - - public func get(key: KeyType) -> ValueType? { - guard let value = cacheMap[key] else { - return nil - } - - // Update cache order. - cacheOrder = cacheOrder.filter { $0 != key } - cacheOrder.append(key) - - return value - } - - public func set(key: KeyType, value: ValueType) { - cacheMap[key] = value - - // Update cache order. - cacheOrder = cacheOrder.filter { $0 != key } - cacheOrder.append(key) - - while cacheOrder.count > maxSize { - guard let staleKey = cacheOrder.first else { - owsFail("Cache ordering unexpectedly empty") - return - } - cacheOrder.removeFirst() - cacheMap.removeValue(forKey: staleKey) - } - } -} - private var URLSessionTaskGiphyAssetRequest: UInt8 = 0 private var URLSessionTaskGiphyAssetSegment: UInt8 = 0 diff --git a/SignalMessaging/environment/OWSSounds.m b/SignalMessaging/environment/OWSSounds.m index ee515f258..09600990e 100644 --- a/SignalMessaging/environment/OWSSounds.m +++ b/SignalMessaging/environment/OWSSounds.m @@ -4,6 +4,7 @@ #import "OWSSounds.h" #import "OWSAudioPlayer.h" +#import #import #import #import @@ -16,7 +17,7 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob @interface OWSSounds () @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) NSMutableDictionary *cachedSoundIDs; +@property (nonatomic, readonly) AnyLRUCache *cachedSoundIDs; @end @@ -52,7 +53,9 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob OWSAssert(primaryStorage); _dbConnection = primaryStorage.newDatabaseConnection; - _cachedSoundIDs = [NSMutableDictionary new]; + + // Don't store too many sounds in memory. Most users will only use 1 or 2 sounds anyway. + _cachedSoundIDs = [[AnyLRUCache alloc] initWithMaxSize:3]; OWSSingletonAssert(); @@ -217,9 +220,10 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob - (SystemSoundID)systemSoundIDForSound:(OWSSound)sound quiet:(BOOL)quiet { NSString *cacheKey = [NSString stringWithFormat:@"%lu:%d", (unsigned long)sound, quiet]; - NSNumber *cachedSoundId = self.cachedSoundIDs[cacheKey]; + NSNumber *_Nullable cachedSoundId = (NSNumber * _Nullable)[self.cachedSoundIDs getWithKey:cacheKey]; if (cachedSoundId) { + OWSAssert([cachedSoundId isKindOfClass:[NSNumber class]]); return (SystemSoundID)cachedSoundId.intValue; } @@ -231,7 +235,7 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob OWSAssert(status == 0); OWSAssert(newSoundID); - self.cachedSoundIDs[cacheKey] = @(newSoundID); + [self.cachedSoundIDs setWithKey:cacheKey value:@(newSoundID)]; return newSoundID; } diff --git a/SignalMessaging/utils/LRUCache.swift b/SignalMessaging/utils/LRUCache.swift new file mode 100644 index 000000000..88c86a560 --- /dev/null +++ b/SignalMessaging/utils/LRUCache.swift @@ -0,0 +1,64 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +@objc +public class AnyLRUCache: NSObject { + + let backingCache: LRUCache + + public init(maxSize: Int) { + backingCache = LRUCache(maxSize: maxSize) + } + + public func get(key: NSObject) -> NSObject? { + return self.backingCache.get(key: key) + } + + public func set(key: NSObject, value: NSObject) { + self.backingCache.set(key: key, value: value) + } +} + +// A simple LRU cache bounded by the number of entries. +// +// TODO: We might want to observe memory pressure notifications. +public class LRUCache { + + private var cacheMap: [KeyType: ValueType] = [:] + private var cacheOrder: [KeyType] = [] + private let maxSize: Int + + public init(maxSize: Int) { + self.maxSize = maxSize + } + + public func get(key: KeyType) -> ValueType? { + guard let value = cacheMap[key] else { + return nil + } + + // Update cache order. + cacheOrder = cacheOrder.filter { $0 != key } + cacheOrder.append(key) + + return value + } + + public func set(key: KeyType, value: ValueType) { + cacheMap[key] = value + + // Update cache order. + cacheOrder = cacheOrder.filter { $0 != key } + cacheOrder.append(key) + + while cacheOrder.count > maxSize { + guard let staleKey = cacheOrder.first else { + owsFail("Cache ordering unexpectedly empty") + return + } + cacheOrder.removeFirst() + cacheMap.removeValue(forKey: staleKey) + } + } +}