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 89f75ba commit 66241fa
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 1 deletion.
10 changes: 9 additions & 1 deletion BlueprintUI/Sources/Element/UIViewElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,15 @@ public struct UIViewElementContext {
}

/// An private type which caches `UIViewElement` views to be reused for sizing and measurement.
private final class UIViewElementMeasurer {
final class UIViewElementMeasurer {

/// The standard shared cache.
static let shared = UIViewElementMeasurer()

var cachedViewCount: Int {
views.count
}

/// Provides the size for the provided element by using a cached measurement view.
func measure(
element: some UIViewElement,
Expand Down Expand Up @@ -173,6 +177,10 @@ private final class UIViewElementMeasurer {
}
}

func removeAllObjects() {
views = [:]
}

private var views: [Key: UIView] = [:]

private struct Key: Hashable {
Expand Down
18 changes: 18 additions & 0 deletions BlueprintUI/Sources/Internal/CacheClearer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

@_spi(CacheManagement)
public struct CacheClearer {

/// Clears all static caches that are in use.
///
/// Blueprint 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() {
UIViewElementMeasurer.shared.removeAllObjects()
}
}
21 changes: 21 additions & 0 deletions BlueprintUI/Tests/UIViewElementTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,27 @@ class UIViewElementTests: XCTestCase {
blueprintView.layoutIfNeeded()
}
}

func test_removeAllObjects() {

struct TestElement: UIViewElement {
func makeUIView() -> UIView {
UIView()
}

func updateUIView(_ view: UIView, with context: BlueprintUI.UIViewElementContext) {}
}

let measurer = UIViewElementMeasurer()

_ = measurer.measure(element: TestElement(), constraint: .unconstrained, environment: .empty)

XCTAssertEqual(measurer.cachedViewCount, 1)

measurer.removeAllObjects()

XCTAssertEqual(measurer.cachedViewCount, 0)
}
}

fileprivate final class TestView: UIView {
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added `CacheCleaner` which exposes a method to force Blueprint's static caches to be cleared.

### Removed

### Changed
Expand Down
29 changes: 29 additions & 0 deletions CacheCleanerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import XCTest
@testable @_spi(CacheManagement) import BlueprintUI

class CacheClearerTests: XCTestCase {

func test_clearStaticCaches() {

struct TestElement: UIViewElement {
func makeUIView() -> UIView {
UIView()
}

func updateUIView(_ view: UIView, with context: BlueprintUI.UIViewElementContext) {}
}

let _ = UIViewElementMeasurer.shared.measure(
element: TestElement(),
constraint: .unconstrained,
environment: .empty
)

XCTAssertEqual(UIViewElementMeasurer.shared.cachedViewCount, 1)

CacheClearer.clearStaticCaches()

XCTAssertEqual(UIViewElementMeasurer.shared.cachedViewCount, 0)
}
}

0 comments on commit 66241fa

Please sign in to comment.