Skip to content

Commit

Permalink
Introduce CacheClearer to force clear static caches
Browse files Browse the repository at this point in the history
  • Loading branch information
robmaceachern committed Dec 16, 2024
1 parent 24bd219 commit 59f8578
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 3 deletions.
19 changes: 19 additions & 0 deletions CacheClearer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation

@_spi(CacheManagement)
public struct CacheClearer {

/// Clears all static caches that are in use.
///
/// Listable leverages static caching to improve performance however there are situations in which
/// this can cause object lifetimes to be extended unexpectedly, especially in cases where cached
/// views reference other objects.
///
/// - WARNING: Clearing these caches can have global performance implications. This method
/// should be invoked sparingley and only after other workarounds to manage object lifetimes have failed.
@_spi(CacheManagement)
public static func clearStaticCaches() {
ListProperties.headerFooterMeasurementCache.removeAllObjects()
ListProperties.itemMeasurementCache.removeAllObjects()
}
}
19 changes: 19 additions & 0 deletions CacheClearerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import XCTest
@testable @_spi(CacheManagement) import ListableUI

class CacheClearerTests: XCTestCase {

func test_clearStaticCaches() {

ListProperties.headerFooterMeasurementCache.push(UIView(), with: .identifier(for: UIView.self))
ListProperties.itemMeasurementCache.push(UIView(), with: .identifier(for: UIView.self))

XCTAssertEqual(ListProperties.headerFooterMeasurementCache.cachedViewCount, 1)
XCTAssertEqual(ListProperties.itemMeasurementCache.cachedViewCount, 1)

CacheClearer.clearStaticCaches()

XCTAssertEqual(ListProperties.headerFooterMeasurementCache.cachedViewCount, 0)
XCTAssertEqual(ListProperties.itemMeasurementCache.cachedViewCount, 0)
}
}
10 changes: 9 additions & 1 deletion ListableUI/Sources/Internal/ReusableViewCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import Foundation
final class ReusableViewCache
{
private var views : [String:[AnyObject]] = [:]


var cachedViewCount : Int {
return self.views.values.reduce(0) { $0 + $1.count }
}

init() {}

func count<Content>(for reuseIdentifier : ReuseIdentifier<Content>) -> Int
Expand Down Expand Up @@ -61,4 +65,8 @@ final class ReusableViewCache
return result
}
}

func removeAllObjects() {
self.views = [:]
}
}
4 changes: 2 additions & 2 deletions ListableUI/Sources/Layout/ListLayout/ListLayout+Layout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import UIKit

extension ListProperties {

private static let headerFooterMeasurementCache = ReusableViewCache()
private static let itemMeasurementCache = ReusableViewCache()
static let headerFooterMeasurementCache = ReusableViewCache()
static let itemMeasurementCache = ReusableViewCache()

/// **Note**: For testing or measuring content sizes only.
///
Expand Down
30 changes: 30 additions & 0 deletions ListableUI/Tests/Internal/ReusableViewCacheTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,36 @@ class ReusableViewCacheTests: XCTestCase
XCTAssertEqual(result, "result")
}
}

func test_removeAllObjects() {
self.testcase("empty") {
let cache = ReusableViewCache()

XCTAssertEqual(cache.cachedViewCount, 0)

cache.removeAllObjects()

XCTAssertEqual(cache.cachedViewCount, 0)
}

self.testcase("non-empty") {
let cache = ReusableViewCache()

let view1 = TestView1()
view1.identifier = "pushed_1"
cache.push(view1, with: ReuseIdentifier.identifier(for: TestView1.self))

let view2 = TestView1()
view2.identifier = "pushed_2"
cache.push(view2, with: ReuseIdentifier.identifier(for: TestView1.self))

XCTAssertEqual(cache.cachedViewCount, 2)

cache.removeAllObjects()

XCTAssertEqual(cache.cachedViewCount, 0)
}
}
}


Expand Down

0 comments on commit 59f8578

Please sign in to comment.