cached_value

一种简单的方法来缓存由昂贵操作产生的值。

缓存那些

  • 以一致的方式从其他值计算出来的值很有用;
  • 可以根据已知和未知条件更改;
  • 不应在每次访问时都进行计算(如 getter);

安装

添加到 pubspec.yaml

    dependencies:
       cached_value: <most recent version>

pub 上查找最新版本。

用法

缓存可以从简单的手动控制缓存创建,并与自动功能组合。
例如依赖关系和生存时间。

创建缓存

简单的缓存仅通过手动使之失效。

  int factorial(int n) {
    if (n < 0) throw ('Negative numbers are not allowed.');
    return n <= 1 ? 1 : n * factorial(n - 1);
  }

  int originalValue = 1;
  final factorialCache = CachedValue(() => factorial(originalValue));
  print(factorialCache.value); // 1

  originalValue = 6;

  print(factorialCache.value); // 1
  print(factorialCache.isValid) // true, invalid only when invalidate is called
  
  // mark as invalid
  factorialCache.invalidate();
  print(factorialCache.isValid); // false

  print(factorialCache.value); // 720
  print(factorialCache.isValid); // true

在缓存无效时访问 value 会刷新缓存。可以通过以下方式手动刷新:
refresh 方法

  // ...
  originalValue = 12;
  factorialCache.refresh();

  print(factorialCache.value); // 12

组合缓存

可以通过声明性 API 将缓存与更多资源组合。通过这样做,可以
添加 TTL 和依赖关系,而不会偏离缓存的原始行为。

示例

  int factorial(int n) {
    if (n < 0) throw ('Negative numbers are not allowed.');
    return n <= 1 ? 1 : n * factorial(n - 1);
  }
  
  int originalValue = 1;
  final fancyFactorialCache = CachedValue(
      () => factorial(originalValue),
  ).withDependency(() => originalValue) // Add dependency
  .withTimeToLive(lifetime: Duration(seconds: 4)); // Add TTL

您甚至可以通过扩展 SingleChildCachedValue 来创建自己的行为。

添加依赖

如果依赖值已更改,则将依赖缓存标记为无效。

  int factorial(int n) {
    if (n < 0) throw ('Negative numbers are not allowed.');
    return n <= 1 ? 1 : n * factorial(n - 1);
  }
  
  int originalValue = 1;
  final factorialCache = CachedValue(
      () => factorial(originalValue),
  ).withDependency(() => originalValue);
  print(factorialCache.value); // 1
  print(factorialCache.isValid); // true
  
  // update value
  originalValue = 6;
  print(factorialCache.isValid); // false

  print(factorialCache.value); // 720
  print(factorialCache.isValid); // true

⚠️重要
依赖回调会在每次访问值时调用。因此,建议保持其尽可能声明式。

// Avoid this:
final someCache = CachedValue(
  // ...
).withDependency(() => someExpensiveOperation(originalValue));

添加生存时间

缓存可以在刷新后一段时间自动标记为无效。

    int factorial(int n) {
        if (n < 0) throw ('Negative numbers are not allowed.');
        return n <= 1 ? 1 : n * factorial(n - 1);
    }
  
    int originalValue = 1;
    final factorialCache = CachedValue(
      () => factorial(originalValue),
    ).withTimeToLive(
      lifetime: Duration(seconds: 3),
    );
    
    originalValue = 6;
    
    print(factorialCache.value); // 1
    
    await Future.delayed(Duration(seconds: 3));
    
    print(factorialCache.value); // 720

更多文档

API 文档 中有更详细的文档。

动机

一些命令式 API,例如 Flutter 渲染对象上的画布绘制,或 Flame 的组件可能
受益于可以在多个帧(或绘制)之间存储和重用的值。

在一些非常具体的情况下,我发现将某些对象(如 PaintTextPainter 实例)存储在帧之间非常方便。
PaintTextPainter 实例。

渲染对象上的示例

class BlurredRenderObject extends RenderObject {

  // ...

  double _blurSigma = 0.0;
  double get blurSigma => _blurSigma;
  set blurSigma(double value) {
    _blurSigma = blurSigma;
    markNeedsPaint();
  }

  // ...

  @override
  void paint(PaintingContext context, Offset offset) {
    final canvas = context.canvas;


    final paint = Paint()..maskFilter = MaskFilter.blur(
        BlurStyle.normal, blurSigma
    );
    canvas.drawRect(Rect.fromLTWH(0, 0, 100, 100), paint);
  }
}

可以更改为

class BlurredRenderObject extends RenderObject {

  // ...

  double _blurSigma = 0.0;
  double get blurSigma => _blurSigma;
  set blurSigma(double value) {
    _blurSigma = blurSigma;
    markNeedsPaint();
  }

  // Add cache:
  late final paintCache = CachedValue(
     () => Paint()..maskFilter = MaskFilter.blur(BlurStyle.normal, blurSigma),
  ).withDependency(() => blurSigma);

  // ...

  @override
  void paint(PaintingContext context, Offset offset) {
    final canvas = context.canvas;

    // use cache:
    final paint = paintCache.value;
    canvas.drawRect(Rect.fromLTWH(0, 0, 100, 100), paint);
  }
}

GitHub

https://github.com/renancaraujo/cached_value