Skip to content

Commit

Permalink
Replace LRUAnimationCache with a thread-safe NSCache-based cache (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelofabri authored Oct 19, 2022
1 parent cb58033 commit 818cf21
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 54 deletions.
12 changes: 12 additions & 0 deletions Lottie.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,10 @@
D453D8AB28FE6EE300D3F49C /* LottieAnimationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D453D8AA28FE6EE300D3F49C /* LottieAnimationCache.swift */; };
D453D8AC28FE6EE300D3F49C /* LottieAnimationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D453D8AA28FE6EE300D3F49C /* LottieAnimationCache.swift */; };
D453D8AD28FE6EE300D3F49C /* LottieAnimationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D453D8AA28FE6EE300D3F49C /* LottieAnimationCache.swift */; };
D453D8AF28FF9BC600D3F49C /* AnimationCacheProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D453D8AE28FF9BC600D3F49C /* AnimationCacheProviderTests.swift */; };
D453D8B228FF9EA900D3F49C /* DefaultAnimationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D453D8B028FF9E3A00D3F49C /* DefaultAnimationCache.swift */; };
D453D8B328FF9EAA00D3F49C /* DefaultAnimationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D453D8B028FF9E3A00D3F49C /* DefaultAnimationCache.swift */; };
D453D8B428FF9EAA00D3F49C /* DefaultAnimationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D453D8B028FF9E3A00D3F49C /* DefaultAnimationCache.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -803,6 +807,8 @@
A1D5BAAB27C731A500777D06 /* DataURLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataURLTests.swift; sourceTree = "<group>"; };
A40460582832C52B00ACFEDC /* BlendMode+Filter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BlendMode+Filter.swift"; sourceTree = "<group>"; };
D453D8AA28FE6EE300D3F49C /* LottieAnimationCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieAnimationCache.swift; sourceTree = "<group>"; };
D453D8AE28FF9BC600D3F49C /* AnimationCacheProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationCacheProviderTests.swift; sourceTree = "<group>"; };
D453D8B028FF9E3A00D3F49C /* DefaultAnimationCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAnimationCache.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -875,6 +881,7 @@
2E8040BD27A07343006E74CB /* Utils */,
2E044E262820536800FA773B /* AutomaticEngineTests.swift */,
6DB3BDB528243FA5002A276D /* ValueProvidersTests.swift */,
D453D8AE28FF9BC600D3F49C /* AnimationCacheProviderTests.swift */,
);
path = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -1438,6 +1445,7 @@
2EAF59E227A0798700E00531 /* AnimationCacheProvider.swift */,
2EAF59E327A0798700E00531 /* LRUAnimationCache.swift */,
D453D8AA28FE6EE300D3F49C /* LottieAnimationCache.swift */,
D453D8B028FF9E3A00D3F49C /* DefaultAnimationCache.swift */,
);
path = AnimationCache;
sourceTree = "<group>";
Expand Down Expand Up @@ -1706,6 +1714,7 @@
2EAF5AA427A0798700E00531 /* FilepathImageProvider.macOS.swift in Sources */,
2E9C97292822F43100677516 /* EllipseAnimation.swift in Sources */,
2E9C96DE2822F43100677516 /* GradientRenderLayer.swift in Sources */,
D453D8B428FF9EAA00D3F49C /* DefaultAnimationCache.swift in Sources */,
2E9C966C2822F43100677516 /* LayerImageProvider.swift in Sources */,
2EAF5ABC27A0798700E00531 /* FilepathImageProvider.swift in Sources */,
2EAF5AE927A0798700E00531 /* AnimationTextProvider.swift in Sources */,
Expand Down Expand Up @@ -1897,6 +1906,7 @@
6DB3BDB628243FA5002A276D /* ValueProvidersTests.swift in Sources */,
2E72128327BB329C0027BC56 /* AnimationKeypathTests.swift in Sources */,
2E044E272820536800FA773B /* AutomaticEngineTests.swift in Sources */,
D453D8AF28FF9BC600D3F49C /* AnimationCacheProviderTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -1920,6 +1930,7 @@
2EAF5AA527A0798700E00531 /* FilepathImageProvider.macOS.swift in Sources */,
2E9C972A2822F43100677516 /* EllipseAnimation.swift in Sources */,
2E9C96DF2822F43100677516 /* GradientRenderLayer.swift in Sources */,
D453D8B228FF9EA900D3F49C /* DefaultAnimationCache.swift in Sources */,
2E9C966D2822F43100677516 /* LayerImageProvider.swift in Sources */,
2EAF5ABD27A0798700E00531 /* FilepathImageProvider.swift in Sources */,
2EAF5AEA27A0798700E00531 /* AnimationTextProvider.swift in Sources */,
Expand Down Expand Up @@ -2113,6 +2124,7 @@
2EAF5AA627A0798700E00531 /* FilepathImageProvider.macOS.swift in Sources */,
2E9C972B2822F43100677516 /* EllipseAnimation.swift in Sources */,
2E9C96E02822F43100677516 /* GradientRenderLayer.swift in Sources */,
D453D8B328FF9EAA00D3F49C /* DefaultAnimationCache.swift in Sources */,
2E9C966E2822F43100677516 /* LayerImageProvider.swift in Sources */,
2EAF5ABE27A0798700E00531 /* FilepathImageProvider.swift in Sources */,
2EAF5AEB27A0798700E00531 /* AnimationTextProvider.swift in Sources */,
Expand Down
54 changes: 54 additions & 0 deletions Sources/Public/AnimationCache/DefaultAnimationCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// DefaultAnimationCache.swift
// Lottie
//
// Created by Marcelo Fabri on 10/18/22.
//

import Foundation

/// A thread-safe Animation Cache that will store animations up to `cacheSize`.
///
/// Once `cacheSize` is reached, animations can be ejected.
/// The default size of the cache is 100.
///
/// This cache implementation also responds to memory pressure, as it's backed by `NSCache`.
public class DefaultAnimationCache: AnimationCacheProvider {

// MARK: Lifecycle

public init() {
cache.countLimit = Self.defaultCacheCountLimit
}

// MARK: Public

/// The global shared Cache.
public static let sharedCache = DefaultAnimationCache()

/// The size of the cache.
public var cacheSize = defaultCacheCountLimit {
didSet {
cache.countLimit = cacheSize
}
}

/// Clears the Cache.
public func clearCache() {
cache.removeAllObjects()
}

public func animation(forKey key: String) -> LottieAnimation? {
cache.object(forKey: key as NSString)
}

public func setAnimation(_ animation: LottieAnimation, forKey key: String) {
cache.setObject(animation, forKey: key as NSString)
}

// MARK: Private

private static let defaultCacheCountLimit = 100

private var cache = NSCache<NSString, LottieAnimation>()
}
56 changes: 4 additions & 52 deletions Sources/Public/AnimationCache/LRUAnimationCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,7 @@

import Foundation

/// An Animation Cache that will store animations up to `cacheSize`.
///
/// Once `cacheSize` is reached, the least recently used animation will be ejected.
/// The default size of the cache is 100.
public class LRUAnimationCache: AnimationCacheProvider {

// MARK: Lifecycle

public init() { }

// MARK: Public

/// The global shared Cache.
public static let sharedCache = LRUAnimationCache()

/// The size of the cache.
public var cacheSize = 100

/// Clears the Cache.
public func clearCache() {
cacheMap.removeAll()
lruList.removeAll()
}

public func animation(forKey: String) -> LottieAnimation? {
guard let animation = cacheMap[forKey] else {
return nil
}
if let index = lruList.firstIndex(of: forKey) {
lruList.remove(at: index)
lruList.append(forKey)
}
return animation
}

public func setAnimation(_ animation: LottieAnimation, forKey: String) {
cacheMap[forKey] = animation
lruList.append(forKey)
if lruList.count > cacheSize {
let removed = lruList.remove(at: 0)
if removed != forKey {
cacheMap[removed] = nil
}
}
}

// MARK: Fileprivate

fileprivate var cacheMap: [String: LottieAnimation] = [:]
fileprivate var lruList: [String] = []

}
@available(*, deprecated, message: """
Use DefaultAnimationCache instead, which is thread-safe and automatically responds to memory pressure.
""")
public typealias LRUAnimationCache = DefaultAnimationCache
4 changes: 2 additions & 2 deletions Sources/Public/AnimationCache/LottieAnimationCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public enum LottieAnimationCache {

/// The animation cache that will be used when loading `LottieAnimation` models.
/// Using an Animation Cache can increase performance when loading an animation multiple times.
/// Defaults to LRUAnimationCache.sharedCache.
public static var shared: AnimationCacheProvider? = LRUAnimationCache.sharedCache
/// Defaults to DefaultAnimationCache.sharedCache.
public static var shared: AnimationCacheProvider? = DefaultAnimationCache.sharedCache
}
40 changes: 40 additions & 0 deletions Tests/AnimationCacheProviderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// AnimationCacheProviderTests.swift
// LottieTests
//
// Created by Marcelo Fabri on 10/18/22.
//

import XCTest

@testable import Lottie

final class AnimationCacheProviderTests: XCTestCase {

func testCaches() throws {
let cache = DefaultAnimationCache()
let animation1 = try XCTUnwrap(Samples.animation(named: "Boat_Loader"))
let animation2 = try XCTUnwrap(Samples.animation(named: "TwitterHeart"))

XCTAssertNil(cache.animation(forKey: "animation1"))
cache.setAnimation(animation1, forKey: "animation1")
XCTAssertNoDiff(cache.animation(forKey: "animation1"), animation1)

XCTAssertNil(cache.animation(forKey: "animation2"))
cache.setAnimation(animation2, forKey: "animation2")
XCTAssertNoDiff(cache.animation(forKey: "animation2"), animation2)
XCTAssertNoDiff(cache.animation(forKey: "animation1"), animation1)
}

func testClearCache() throws {
let cache = DefaultAnimationCache()
let animation = try XCTUnwrap(Samples.animation(named: "Boat_Loader"))

XCTAssertNil(cache.animation(forKey: "animation"))
cache.setAnimation(animation, forKey: "animation")
XCTAssertNotNil(cache.animation(forKey: "animation"))

cache.clearCache()
XCTAssertNil(cache.animation(forKey: "animation"))
}
}

0 comments on commit 818cf21

Please sign in to comment.