패키지, 저장소 및 앱 카테고리 검색

패키지 상세v4.1.2

weak_map

WeakMap은 키가 약한 참조를 사용하는 맵입니다. WeakContainer를 사용하면 이전에 가졌던 객체와 동일한지 확인할 수 있습니다. 약한 참조를 사용하여 함수를 캐시하고 메모이제이션을 수행합니다.

AndroidiOSWebmacOSWindowsLinux
30일 다운로드
6.3만
좋아요
19
Pub 점수
150

패키지 설치

flutter pub add weak_map

패키지 개요

공개 정보를 바탕으로 생성한 선택 가이드입니다.

WeakMap은 키에 약한 참조를 사용하는 맵을 구현하는 Dart 패키지로, 다른 참조가 없을 경우 키 객체를 가비지 컬렉션할 수 있게 해줍니다. 메모리 누수를 방지하여 효율적인 메모리 관리를 가능하게 합니다. WeakContainer는 객체의 정체성을 확인하여 이전과 동일한 인스턴스임을 보장합니다. 이 패키지는 약한 참조를 사용해 캐시 함수를 통해 메모이제이션을 지원하며, 일시적인 데이터 저장에 적합합니다. 장시간 실행되는 애플리케이션에서 성능과 안정성을 고려해 설계되었습니다.

사용 사례

  • 자동 정리가 가능한 메모이제이션
  • 누수 없이 객체 정체성 추적
  • 임시 객체와 연결된 결과 캐싱
  • 콜백에서 메모리 누수 방지
  • 인스턴스별 메타데이터 저장
  • 임시 상태 관리

주요 기능

  • 약하게 참조된 키
  • 가비지 수집 가능한 항목
  • 신원 기반 객체 검사
  • 내장된 메모이제이션 유틸리티
  • 크로스 플랫폼 지원
  • 가볍고 빠름

적합한 대상

  • 자주 객체를 생성하는 장시간 실행 앱
  • 메모리 민감한 애플리케이션
  • 인스턴스별 데이터가 필요한 플러그인
  • 개발자들이 강한 참조 사이클을 피하는 방법
  • 일시적인 데이터 저장이 필요한 사용 사례
  • 고성능 캐싱

고려 사항

  • 키는 예기치 않게 수집될 수 있습니다
  • 지속 데이터에 적합하지 않습니다
  • 직접적인 반복 또는 크기 접근 없음
  • 객체 키 기반 저장에 제한됨
  • 비동기 컨텍스트에서 주의 깊게 다뤄야 합니다
  • 일시적 또는 단기적인 객체와 함께 사용하는 것이 가장 좋습니다

원본 README

연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.

Pub popularity Pub Version GitHub stars GitHub repo size PRs Welcome Developed by Marcelo Glasberg Glasberg.dev on pub.dev Platforms

Sponsor

weak_map

This package contains the classes:

  • WeakMap
  • WeakContainer

And also the functions:

  • cache1state
  • cache1state_1param
  • cache1state_2params
  • cache2states
  • cache2states_1param
  • cache2states_2params
  • cache3states
  • cache1state_0params_x
  • cache2states_0params_x
  • cache3states_0params_x
Why is this package useful?
  1. Dart doesn't allow for real weak-references, but this package allows you to go as close as possible (internally it uses the Expando class). The Dart engine stores a value in memory while it is reachable (and can potentially be used). Usually, keys in a map are considered reachable and kept in memory while the map itself is in memory. This means if we put an object into a map or into a variable, then while the map is alive, the object will be alive as well, even if there are no other references to it. It occupies memory and may not be garbage collected. WeakMap and WeakContainer are fundamentally different in this aspect. They don't prevent garbage-collection of key objects.

  2. Caches that keep the result of expensive processes calculated over immutable data can also benefit from weak-maps. I here provide functions similar to the ones of the reselect package, but better. This can be used with Redux or with any other calculations over immutable data.


WeakMap

A WeakMap lets you garbage-collect its keys. Please note the keys can be garbage-collected, not their corresponding values.

This means if you use some object as a key to a map-entry, this alone will not prevent Dart to garbage-collect this object.

In other words, after all other references to that object have been destroyed, its entry (key and value) may be removed automatically from the map at any moment. To create a map:

var map = WeakMap();

To add and retrieve a value:

map["John"] = 42;
var age = map["John"];

The following map methods work as expected:

map.remove("John")
map.clear()
map.contains("John"))

However, adding some null value to the map is the same as removing the key:

map["John"] = null; // Same as map.remove("John")

Notes:

  1. The keys are compared using object identity, and not object equivalence (operator ==).

  2. If you use null, a number, a boolean, a String, or a const type as the map key, it will act like a regular map, because these types are never garbage-collected. All other types of object may be garbage-collected.

  3. To retrieve a value added to the map, you can use the equivalent syntaxes var y = map[x] or var y = map.get(x).

  4. Doing map[x] = y is equivalent to map.add(key: x, value: y), but the object is later retrieved by identity.


WeakContainer

As previously explained, Dart doesn't have real weak-references. But you can check that some object is the same you had before.

To create a weak-container:

var obj = Object();
var ref = WeakContainer(obj);
var someObj = Random().nextBool() ? obj : Object();
print(ref.contains(someObj)); // True or false.

This will print true if someObj is the same as the original obj, and will print false if it's a different object, compared by identity. If all references to the original obj have been destroyed, the weak-container will not prevent obj to be garbage-collected.


Why doesn't Dart allow for real weak-references, anyway?

Because the creators of Dart don't want the GC (garbage-collector) to be "visible".

Expandos are not equivalent to weak-references (meaning the Java WeakReference behavior). A weak reference is one that doesn't keep the referenced object alive, so the weak reference value may change to null at any time in the program. This makes the GC visible in the program.

Expandos are maps (from key to value) which won't keep the key alive. There is no way to distinguish an Expando that garbage collects the entry when the key dies, and one that doesn't, because you don't have the key to do the lookup anymore.

Basically, it means that an expando keeps a value alive as long as you have a reference to both the expando and the key, and after that, you can't check if the entry is there or not. With expandos, the GC need not be part of the language specification. It's just an optimization that implementations (are expected to) do to release memory that isn't needed anymore. Disabling the GC will not change the behavior of programs unless they run out of memory.


Cache

Suppose you have some immutable information, which we call "state", and some parameters. We want to perform some expensive process (calculation, selection filtering etc) over the state, and we want to cache the result.

For example, suppose you want to filter an immutable list of millions of users, to create a new list with only the names that start with some text. You could filter the users list to remove all other names, like this:

List<User> filter(String text) => users.where((user)=>user.name.startsWith(text)).toList();

This is an expensive process, so you may want to cache the filtered list.

In this example, we have a single state and a single parameter, so we're going to use the cache1state_1param method:

static List<User> filter(Users users, String text)
   => _filter(users)(text);

static final _filter = cache1state_1param(
        (Users users) 
           => (String text) 
              => users.where((user)=>user.name.startsWith(text)).toList());

The above code will calculate the filtered list only once, and then return it when the filter function is called again with the same users and text.

If the function is called with a different users and/or text, it will recalculate and cache the new result.

However, it treats the state and the parameter differently. If you call the function while keeping the same state and changing only the parameter, it will cache all the results, one for each parameter.

However, as soon as you call the function with a changed state, it will delete all of its previous cached information, since it understands that they are no longer useful.

And even if you don't call that function ever again, it will delete the cached information if it detects that the state is no longer used in other parts of the program. In other words, it keeps the cached information in a weak-map, so that the cache will not hold to old information and have a negative impact in memory usage.

Some functions, marked with an "x", also let you pass some extra information which is not used in any way to decide whether the cache should be used/recalculated/evicted.

For the moment, the following 10 methods are provided, which combine 1, 2 or 3 states with 0, 1 or 2 parameters, and possibly some extra information:

cache1state((state) => () => ...);
cache1state_1param((state) => (parameter) => ...);
cache1state_2params((state) => (parameter1, parameter2) => ...);
cache2states((state1, state2) => () => ...);
cache2states_1param((state1, state2) => (parameter) => ...);
cache2states_2params((state1, state2) => (parameter1, parameter2) => ...);
cache3states((state1, state2, state3) => () => ...);
cache1state_0params_x((state1, extra) => () => ...);
cache2states_0params_x((state1, state2, extra) => () => ...);
cache3states_0params_x((state1, state2, state3, extra) => () => ...);

I have created only those above, because for my own usage I never required more than that. Please, open an issue to ask for more variations in case you feel the need.

Note: These cache functions are similar to the "createSelector" functions found in the reselect package. The differences are: First, here you can keep any number of cached results for each function, one for each time the function is called with the same state and different parameters. Meanwhile, the reselect package only keeps a single cached result per function. Second, here it discards the cached information when the state changes or is no longer used in other parts of the program. Meanwhile, the reselect package will always keep the states and cached results in memory.



By Marcelo Glasberg

glasberg.dev
github.com/marcglasberg
linkedin.com/in/marcglasberg/
twitter.com/glasbergmarcelo
stackoverflow.com/users/3411681/marcg
medium.com/@marcglasberg

My article in the official Flutter documentation:

The Flutter packages I've authored:

My Medium Articles:

카테고리

소스 저장소

이 패키지를 사용하는 프로젝트