Use separate caches for still and animated media.

This commit is contained in:
Matthew Chen 2021-01-20 17:48:54 -03:00
parent c73f54c1ac
commit 281ba67ffc
15 changed files with 65 additions and 63 deletions

View File

@ -205,8 +205,7 @@ extension CVComponentBase: CVNode {
public var itemViewState: CVItemViewState { itemModel.itemViewState }
public var messageCellType: CVMessageCellType { componentState.messageCellType }
public var conversationStyle: ConversationStyle { itemModel.conversationStyle }
public var cellMediaCache: NSCache<NSString, AnyObject> { itemModel.cellMediaCache }
public var mediaViewCache: CVMediaViewCache { itemModel.mediaViewCache }
public var mediaCache: CVMediaCache { itemModel.mediaCache }
public var isDarkThemeEnabled: Bool { conversationStyle.isDarkThemeEnabled }
public var isGroupThread: Bool {

View File

@ -66,8 +66,7 @@ public class CVComponentBodyMedia: CVComponentBase, CVComponent {
let conversationStyle = self.conversationStyle
let albumView = componentView.albumView
albumView.configure(mediaCache: self.cellMediaCache,
mediaViewCache: self.mediaViewCache,
albumView.configure(mediaCache: self.mediaCache,
items: self.items,
isOutgoing: self.isOutgoing,
isBorderless: self.isBorderless,

View File

@ -51,12 +51,12 @@ public class CVComponentSticker: CVComponentBase, CVComponent {
let cacheKey = attachmentStream.uniqueId
let isAnimated = attachmentStream.shouldBeRenderedByYY
let reusableMediaView: ReusableMediaView
if let cachedView = mediaViewCache.getMediaView(cacheKey, isAnimated: isAnimated) {
if let cachedView = mediaCache.getMediaView(cacheKey, isAnimated: isAnimated) {
reusableMediaView = cachedView
} else {
let mediaViewAdapter = MediaViewAdapterSticker(attachmentStream: attachmentStream)
reusableMediaView = ReusableMediaView(mediaViewAdapter: mediaViewAdapter, mediaCache: cellMediaCache)
mediaViewCache.setMediaView(reusableMediaView, forKey: cacheKey, isAnimated: isAnimated)
reusableMediaView = ReusableMediaView(mediaViewAdapter: mediaViewAdapter, mediaCache: mediaCache)
mediaCache.setMediaView(reusableMediaView, forKey: cacheKey, isAnimated: isAnimated)
}
reusableMediaView.owner = componentView

View File

@ -28,8 +28,7 @@ public class CVItemModel: NSObject {
// and request a new one.
private let coreState: CVCoreState
public var conversationStyle: ConversationStyle { coreState.conversationStyle }
public var cellMediaCache: NSCache<NSString, AnyObject> { coreState.cellMediaCache }
public var mediaViewCache: CVMediaViewCache { coreState.mediaViewCache }
public var mediaCache: CVMediaCache { coreState.mediaCache }
public var itemModel: CVItemModel {
self

View File

@ -61,7 +61,7 @@ extension CVItemBuildingContext {
var thread: TSThread { threadViewModel.threadRecord }
var threadUniqueId: String { thread.uniqueId }
var conversationStyle: ConversationStyle { viewStateSnapshot.conversationStyle }
var cellMediaCache: NSCache<NSString, AnyObject> { viewStateSnapshot.cellMediaCache }
var mediaCache: CVMediaCache { viewStateSnapshot.mediaCache }
}
// MARK: -
@ -87,7 +87,7 @@ extension CVItemBuilding {
var thread: TSThread { itemBuildingContext.thread }
var viewStateSnapshot: CVViewStateSnapshot { itemBuildingContext.viewStateSnapshot }
var conversationStyle: ConversationStyle { itemBuildingContext.conversationStyle }
var cellMediaCache: NSCache<NSString, AnyObject> { itemBuildingContext.cellMediaCache }
var mediaCache: CVMediaCache { itemBuildingContext.mediaCache }
var transaction: SDSAnyReadTransaction { itemBuildingContext.transaction }
var avatarBuilder: CVAvatarBuilder { itemBuildingContext.avatarBuilder }
}

View File

@ -61,7 +61,7 @@ public class CVLoadCoordinator: NSObject {
private weak var componentDelegate: CVComponentDelegate?
private let viewState: CVViewState
private var cellMediaCache: NSCache<NSString, AnyObject> { viewState.cellMediaCache }
private var mediaCache: CVMediaCache { viewState.mediaCache }
private let threadUniqueId: String

View File

@ -249,13 +249,11 @@ public class CVLoader: NSObject {
thread: TSThread,
containerView: UIView,
transaction: SDSAnyReadTransaction) -> CVRenderItem? {
let cellMediaCache = NSCache<NSString, AnyObject>()
let conversationStyle = ConversationStyle(type: .`default`,
thread: thread,
viewWidth: containerView.width)
let coreState = CVCoreState(conversationStyle: conversationStyle,
cellMediaCache: cellMediaCache,
mediaViewCache: CVMediaViewCache())
mediaCache: CVMediaCache())
return CVLoader.buildStandaloneRenderItem(interaction: interaction,
thread: thread,
coreState: coreState,

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -57,7 +57,7 @@ public protocol CVNode {
var interaction: TSInteraction { get }
var messageCellType: CVMessageCellType { get }
var conversationStyle: ConversationStyle { get }
var cellMediaCache: NSCache<NSString, AnyObject> { get }
var mediaCache: CVMediaCache { get }
}
// MARK: -

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -27,7 +27,7 @@ struct CVViewStateSnapshot {
// and request a new one.
let coreState: CVCoreState
public var conversationStyle: ConversationStyle { coreState.conversationStyle }
public var cellMediaCache: NSCache<NSString, AnyObject> { coreState.cellMediaCache }
public var mediaCache: CVMediaCache { coreState.mediaCache }
// TODO: We need to determine exactly what the desired behavior here is.
let collapseCutoffDate = Date()

View File

@ -103,7 +103,7 @@ public class CVViewState: NSObject {
public var sendMessageController: SendMessageController?
public let mediaViewCache = CVMediaViewCache()
public let mediaCache = CVMediaCache()
// MARK: - Gestures
@ -116,14 +116,6 @@ public class CVViewState: NSObject {
// MARK: -
@objc
public let cellMediaCache: NSCache<NSString, AnyObject> = {
let cache = NSCache<NSString, AnyObject>()
// Cache the cell media for ~24 cells.
cache.countLimit = 24
return cache
}()
var initialScrollState: CVInitialScrollState?
@objc
@ -253,7 +245,7 @@ public extension ConversationViewController {
var isMeasuringKeyboardHeight: Bool { inputToolbar?.isMeasuringKeyboardHeight ?? false }
var mediaViewCache: CVMediaViewCache { viewState.mediaViewCache }
var mediaCache: CVMediaCache { viewState.mediaCache }
// MARK: - Gestures
@ -288,10 +280,6 @@ public extension ConversationViewController {
// MARK: -
var cellMediaCache: NSCache<NSString, AnyObject> {
viewState.cellMediaCache
}
#if TESTABLE_BUILD
var initialLoadBenchSteps: BenchSteps { viewState.initialLoadBenchSteps }
#endif
@ -302,9 +290,7 @@ public extension ConversationViewController {
extension CVViewState {
var asCoreState: CVCoreState {
CVCoreState(conversationStyle: conversationStyle,
cellMediaCache: cellMediaCache,
mediaViewCache: mediaViewCache)
CVCoreState(conversationStyle: conversationStyle, mediaCache: mediaCache)
}
}
@ -355,8 +341,7 @@ extension ConversationViewController {
// pieces of CVC state during async loads.
struct CVCoreState {
let conversationStyle: ConversationStyle
let cellMediaCache: NSCache<NSString, AnyObject>
let mediaViewCache: CVMediaViewCache
let mediaCache: CVMediaCache
}
// MARK: -

View File

@ -24,8 +24,7 @@ public class CVMediaAlbumView: UIStackView {
super.init(frame: .zero)
}
public func configure(mediaCache: NSCache<NSString, AnyObject>,
mediaViewCache: CVMediaViewCache,
public func configure(mediaCache: CVMediaCache,
items: [CVMediaAlbumItem],
isOutgoing: Bool,
isBorderless: Bool,
@ -40,7 +39,6 @@ public class CVMediaAlbumView: UIStackView {
self.items = items
self.itemViews = CVMediaAlbumView.itemsToDisplay(forItems: items).map {
CVMediaView(mediaCache: mediaCache,
mediaViewCache: mediaViewCache,
attachment: $0.attachment,
isOutgoing: isOutgoing,
maxMessageWidth: maxMessageWidth,

View File

@ -14,8 +14,7 @@ public class CVMediaView: UIView {
// MARK: -
private let mediaViewCache: CVMediaViewCache
private let mediaCache: NSCache<NSString, AnyObject>
private let mediaCache: CVMediaCache
public let attachment: TSAttachment
private let conversationStyle: ConversationStyle
private let isOutgoing: Bool
@ -26,14 +25,12 @@ public class CVMediaView: UIView {
// MARK: - Initializers
public required init(mediaCache: NSCache<NSString, AnyObject>,
mediaViewCache: CVMediaViewCache,
public required init(mediaCache: CVMediaCache,
attachment: TSAttachment,
isOutgoing: Bool,
maxMessageWidth: CGFloat,
isBorderless: Bool,
conversationStyle: ConversationStyle) {
self.mediaViewCache = mediaViewCache
self.mediaCache = mediaCache
self.attachment = attachment
self.isOutgoing = isOutgoing
@ -146,7 +143,7 @@ public class CVMediaView: UIView {
private func createNewReusableMediaView(mediaViewAdapter: MediaViewAdapter, isAnimated: Bool) {
let reusableMediaView = ReusableMediaView(mediaViewAdapter: mediaViewAdapter, mediaCache: mediaCache)
mediaViewCache.setMediaView(reusableMediaView, forKey: mediaViewAdapter.cacheKey, isAnimated: isAnimated)
mediaCache.setMediaView(reusableMediaView, forKey: mediaViewAdapter.cacheKey, isAnimated: isAnimated)
applyReusableMediaView(reusableMediaView)
}
@ -163,7 +160,7 @@ public class CVMediaView: UIView {
// cachekey to avoid conflicts with the actual attachment contents.
let cacheKey = blurHash
let isAnimated = false
if let reusableMediaView = mediaViewCache.getMediaView(cacheKey, isAnimated: isAnimated) {
if let reusableMediaView = mediaCache.getMediaView(cacheKey, isAnimated: isAnimated) {
applyReusableMediaView(reusableMediaView)
return
}
@ -175,7 +172,7 @@ public class CVMediaView: UIView {
private func configureForAnimatedImage(attachmentStream: TSAttachmentStream) {
let cacheKey = attachmentStream.uniqueId
let isAnimated = attachmentStream.shouldBeRenderedByYY
if let reusableMediaView = mediaViewCache.getMediaView(cacheKey, isAnimated: isAnimated) {
if let reusableMediaView = mediaCache.getMediaView(cacheKey, isAnimated: isAnimated) {
applyReusableMediaView(reusableMediaView)
return
}
@ -187,7 +184,7 @@ public class CVMediaView: UIView {
private func configureForStillImage(attachmentStream: TSAttachmentStream) {
let cacheKey = attachmentStream.uniqueId
let isAnimated = attachmentStream.shouldBeRenderedByYY
if let reusableMediaView = mediaViewCache.getMediaView(cacheKey, isAnimated: isAnimated) {
if let reusableMediaView = mediaCache.getMediaView(cacheKey, isAnimated: isAnimated) {
applyReusableMediaView(reusableMediaView)
return
}
@ -199,7 +196,7 @@ public class CVMediaView: UIView {
private func configureForVideo(attachmentStream: TSAttachmentStream) {
let cacheKey = attachmentStream.uniqueId
let isAnimated = attachmentStream.shouldBeRenderedByYY
if let reusableMediaView = mediaViewCache.getMediaView(cacheKey, isAnimated: isAnimated) {
if let reusableMediaView = mediaCache.getMediaView(cacheKey, isAnimated: isAnimated) {
applyReusableMediaView(reusableMediaView)
return
}

View File

@ -5,7 +5,17 @@
import Foundation
@objc
public class CVMediaViewCache: NSObject {
public class CVMediaCache: NSObject {
private static func buildMediaCache(countLimit: Int) -> NSCache<NSString, AnyObject> {
let cache = NSCache<NSString, AnyObject>()
cache.countLimit = countLimit
return cache
}
private let stillMediaCache = buildMediaCache(countLimit: 16)
private let animatedMediaCache = buildMediaCache(countLimit: 8)
private let stillMediaViewCache = MediaInnerCache<String, ReusableMediaView>(maxSize: 12)
private let animatedMediaViewCache = MediaInnerCache<String, ReusableMediaView>(maxSize: 6)
@ -23,6 +33,18 @@ public class CVMediaViewCache: NSObject {
object: nil)
}
@objc
public func getMedia(_ key: String, isAnimated: Bool) -> AnyObject? {
let cache = isAnimated ? animatedMediaCache : stillMediaCache
return cache.object(forKey: key as NSString)
}
@objc
public func setMedia(_ value: AnyObject, forKey key: String, isAnimated: Bool) {
let cache = isAnimated ? animatedMediaCache : stillMediaCache
cache.setObject(value, forKey: key as NSString)
}
@objc
public func getMediaView(_ key: String, isAnimated: Bool) -> ReusableMediaView? {
let cache = isAnimated ? animatedMediaViewCache : stillMediaViewCache
@ -39,6 +61,9 @@ public class CVMediaViewCache: NSObject {
public func removeAllObjects() {
AssertIsOnMainThread()
stillMediaCache.removeAllObjects()
animatedMediaCache.removeAllObjects()
stillMediaViewCache.removeAllObjects()
animatedMediaViewCache.removeAllObjects()
}

View File

@ -30,6 +30,7 @@ public protocol MediaViewAdapter {
var mediaView: UIView { get }
var isLoaded: Bool { get }
var cacheKey: String { get }
var isAnimated: Bool { get }
func applyMedia(_ media: AnyObject)
func unloadMedia()
@ -54,7 +55,7 @@ public enum ReusableMediaError: Error {
public class ReusableMediaView: NSObject {
private let mediaViewAdapter: MediaViewAdapterSwift
private let mediaCache: NSCache<NSString, AnyObject>
private let mediaCache: CVMediaCache
@objc
public var mediaView: UIView {
@ -92,7 +93,7 @@ public class ReusableMediaView: NSObject {
@objc
public required init(mediaViewAdapter: MediaViewAdapter,
mediaCache: NSCache<NSString, AnyObject>) {
mediaCache: CVMediaCache) {
self.mediaViewAdapter = mediaViewAdapter as! MediaViewAdapterSwift
self.mediaCache = mediaCache
}
@ -177,7 +178,7 @@ public class ReusableMediaView: NSObject {
let mediaViewAdapter = self.mediaViewAdapter
let cacheKey = mediaViewAdapter.cacheKey
let mediaCache = self.mediaCache
if let media = mediaCache.object(forKey: cacheKey as NSString) {
if let media = mediaCache.getMedia(cacheKey, isAnimated: mediaViewAdapter.isAnimated) {
Logger.verbose("media cache hit")
loadCompletion(media)
return
@ -193,7 +194,7 @@ public class ReusableMediaView: NSObject {
}
return mediaViewAdapter.loadMedia()
}.done(on: .main) { (media: AnyObject) in
mediaCache.setObject(media, forKey: cacheKey as NSString)
mediaCache.setMedia(media, forKey: cacheKey, isAnimated: mediaViewAdapter.isAnimated)
loadCompletion(media)
}.catch(on: .main) { (error: Error) in
@ -213,6 +214,7 @@ public class ReusableMediaView: NSObject {
class MediaViewAdapterBlurHash: MediaViewAdapterSwift {
public let isAnimated = false
let blurHash: String
let imageView = UIImageView()
@ -262,6 +264,7 @@ class MediaViewAdapterBlurHash: MediaViewAdapterSwift {
class MediaViewAdapterAnimated: MediaViewAdapterSwift {
public let isAnimated = true
let attachmentStream: TSAttachmentStream
let imageView = YYAnimatedImageView()
@ -315,6 +318,7 @@ class MediaViewAdapterAnimated: MediaViewAdapterSwift {
class MediaViewAdapterStill: MediaViewAdapterSwift {
public let isAnimated = false
let attachmentStream: TSAttachmentStream
let imageView = UIImageView()
@ -373,6 +377,7 @@ class MediaViewAdapterStill: MediaViewAdapterSwift {
class MediaViewAdapterVideo: MediaViewAdapterSwift {
public let isAnimated = false
let attachmentStream: TSAttachmentStream
let imageView = UIImageView()
@ -427,7 +432,7 @@ class MediaViewAdapterVideo: MediaViewAdapterSwift {
@objc
public class MediaViewAdapterSticker: NSObject, MediaViewAdapterSwift {
let isAnimated: Bool
public let isAnimated: Bool
let attachmentStream: TSAttachmentStream
let imageView: UIImageView

View File

@ -565,8 +565,7 @@ typedef enum : NSUInteger {
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
[self updateCellsVisible];
[self.cellMediaCache removeAllObjects];
[self.mediaViewCache removeAllObjects];
[self.mediaCache removeAllObjects];
}
- (void)applicationWillResignActive:(NSNotification *)notification
@ -576,8 +575,7 @@ typedef enum : NSUInteger {
self.isWaitingForDeceleration = NO;
[self saveDraft];
[self markVisibleMessagesAsRead];
[self.cellMediaCache removeAllObjects];
[self.mediaViewCache removeAllObjects];
[self.mediaCache removeAllObjects];
[self cancelReadTimer];
[self dismissPresentedViewControllerIfNecessary];
[self saveLastVisibleSortIdAndOnScreenPercentage];
@ -1067,8 +1065,7 @@ typedef enum : NSUInteger {
[self saveDraft];
[self markVisibleMessagesAsRead];
[self cancelVoiceMemo];
[self.cellMediaCache removeAllObjects];
[self.mediaViewCache removeAllObjects];
[self.mediaCache removeAllObjects];
[self.inputToolbar clearDesiredKeyboard];
self.isUserScrolling = NO;