Why does weak reference on immutable types return the object itself?

Former Member
Former Member
From documentation:
If you are calling weak()on one of the immutable types (Number, Float, Long, Double, String), then it returns the object itself. Otherwise it will return a WeakReference instance.

Why is that?

Top Replies

All Replies

  • Because those types cannot possibly be used to create a circular reference.

    The only reason for weak reference is to handle the situation where object A has a reference to B, and B has a reference to A. If you don't break the chain and you let all other references go away, both A and B will be leaked. If the types of A and B are such that one of them can't hold a reference to the other, you can never get into this circular reference situation... so this isn't a concern for the built-in primitive types.

    Travis
  • Former Member
    Former Member over 9 years ago
    Thanks, clear now!

    Though I think immutable objects in general could do circular references (though it would make sense that the reference is not public and is used for inner-workings only), but these specific ones don't.
  • Theoretically the immutable types could have a reference to something, but Garmin controls the implementation. They know that it doesn't hold references to anything else, so the optimization is safe.
  • Preventing circular reference is not the only reason why someone would use WeakReferences:

    import Toybox.Lang;
    import Toybox.WatchUi;
    import Toybox.System;
    
    class ResourceCache {
        hidden var cache as Dictionary<Symbol, WeakReference> = {} as Dictionary<Symbol, WeakReference>;
    
        function initialize() {
        }
    
        function get(symbol as Symbol) as Resource {
            var cache = self.cache;
            var value;
            var weakReference = cache.get(symbol);
            System.println("weakReference: " + weakReference);
            // for some reason neither weakReference.stillAlive() nor weakReference.get() works (Symbol Not Found)
            // if (weakReference != null && weakReference.stillAlive()) {
            if (cache.hasKey(symbol) && weakReference != null && weakReference.get() != null) {
                value = weakReference.get() as Resource;
                logMemory("CACHE: ", ", found symbol: " + symbol.toString());
            } else {
                value = WatchUi.loadResource(symbol);
                cache.put(symbol, value.weak());
                logMemory("CACHE: ", ", loaded symbol: " + symbol.toString() + " => " + value.toString().length() + " bytes");
            }
            return value;
        }
    }
    
    const CACHE = new ResourceCache();
    

    I think that using WeakReferences for a cache like this would make sense (it would allow an app to use as much memory as possible and use the slow IO less).

    The problem is, that now after a lot of time spent on this I realized that the problem is that value.weak() returns a String instead of a WeakReference to a String. So the whole concept doesn't work Disappointed

  • You can modify the concept to make it work:

    1) Create a container class which holds a Resource and implement a get() accessor method

    2) Ensure that you never store / pass the result of get() within your own code. (Obviously at some point you will probably need to pass the result of get() to an API function, but that's ok as long as it's not some kind of memory cache-related function)

    class ReferenceContainer {
      private var reference = null;
      function initialize(r) {
        reference = r;
      }
      // do not store the return value of get().
      // call get() every time you need the value
      function get() {
        return reference;
      }
    }

    Obviously it's not ideal.

  • I don't think this will work. I think you missed the point. You solved a problem I didn't think of: to be able to "get" to the original object with the same "interface": cache.get(symbol).get(). This is nice, but the problem with this is that my goal wasn't to keep the immutable object in the memory "forever". The whole point of ResourceCache is to keep as many resources as the memory enables in memory, but when there's not enough memory it should be able to free them!

    However I went along with your advice so I created this helper function:

        hidden static function weakReference(resource as Resource) as WeakReference {
            var weakReference = resource.weak();
            if (weakReference == resource) {
                weakReference = new ReferenceContainer(resource).weak();
            }
            return weakReference;
        }

    and modified the line:

    cache.put(symbol, value.weak());
    to:
    cache.put(symbol, weakReference(value));
    But the problem now is that there are 2 types of objects in cache:
    1. WeakReferences to mutable objects => to get to the object: weakReference.get()
    2. WeakReferences to ReferenceContainers that hold the immutable object => I need weakReference.get().get()
    So funnily enough by solving the previously non-existant problem and giving a tip, you created the opposite of the problem you solved ;) Let me know if you have any idea ho to solve it in a nice way. Maybe using instanceof... But it's getting ugly. The whole overhead might not be worth, maybe I'll just use loadResource every time I need a resource...
  •  I think you missed the point.

    You like to say this a lot, but in this case it's not true. I understood your intent perfectly.

    My idea was to wrap ALL resources in a ResourceContainer, not just primitives. And again, the crucial part is to never store or unnecessarily pass around the result of ResourceContainer.get(). So ResourceCache.get() returns a ResourceContainer, and the calling code has to call ResourceContainer.get() whenever it wants to use the value (by manipulating or inspecting it somehow, or by passing it to an API function).

    That way, when the resource container is garbage collected, so are its contents. (The resource container should hold the only "persistent" strong reference to the resource.)

    Like I said it's not ideal, and I apologize for not explaining the idea in depth.

    This is how I would write ResourceCache.get(), to implement my idea. Of course this assumes that your original idea is viable, which may not be the case (see below)

     function get(symbol as Symbol) as ResourceContainer {
        var cache = self.cache;
        var container;
        var weakReference = cache.get(symbol);
        if (weakReference != null && weakReference.stillAlive()) {
            container = weakReference.get() as ResourceContainer;
        } else {
            var value = WatchUi.loadResource(symbol);
            container = new ReferenceContainer(value);
            cache.put(symbol, container.weak());
        }
        return container;
    }

    (*) I do think the one unstated assumption you are making is that an object that is *only* referenced via a weak reference will stay alive as long as there's sufficient memory.

    Not sure if this is the case. It could actually be that that as soon as the last strong reference to an object goes out of scope, the object is immediately garbage collected, which means that your cache may not cache anything at all.

    https://developer.garmin.com/connect-iq/api-docs/Toybox/Lang/WeakReference.html

    A weak reference is a loosely bound reference to another object. If all strong references have been freed, the WeakReference.get() method will return null. This allows the developer to avoid circular references.

    https://developer.garmin.com/connect-iq/monkey-c/objects-and-memory/

    Monkey C is reference counted, which means the runtime system will free memory when the number of objects referencing that memory decrements to zero.

    In both of these statements, it seems implicit that memory would be freed immediately, instead of the runtime waiting around for memory to become "full". 

    i.e. Let's pretend the original code works as intended, in an alternate universe where Object.weak() always returns a WeakReference.

    - other code calls ResourceCache.get(:someResource) for the first time

    - ResourceCache.get() retrieves the value associated with the key :someResource (let's call it someResourceVal) and stores a weak reference in the cache

    - ResourceCache.get() returns someResourceVal, which is stored in a local variable res by the caller

    - the caller uses res somehow

    - once res goes out of scope in the caller, the number of strong references to someResourceVal will be decremented to 0. At this point it seems logical that someResourceVal should be garbage collected immediately. Why wait?

    The wikipedia article on weak references addresses the cache use case:

    https://en.wikipedia.org/wiki/Weak_reference

    When holding cached data that can be recreated if necessary, weak references allow the cache to be reclaimed, effectively producing discardable memory. This last case (a cache) is distinct from others, as it is preferable that the objects only be garbage collected if necessary, and there is thus a need for finer distinctions within weak references, here a stronger form of a weak reference.

    Not to state the obvious, but Monkey C has no such finer distinctions within weak references.

    Also not to state the obvious, but Garmin obviously designed its implementation of WeakReference solely to handle the use case of avoiding circular references.

  • You're right! I saw in the simulator that the when I fetch the same symbol twice, even in the same second, the 2nd time it loads it again. So my whole concept doesn't fit CIQ. Then I'll just use loadResource every time.