Using NSImages as keys to a dictionary

  • Hello,

    My app needs a way of caching scaled images (thumbnails) so that they
    aren't recreated each time. I first did this using an
    NSMutableDictionary mapping from original image to scaled image.
    However, this didn't work because apparently one original image wasn't
    isEqual: to another.

    I realized that I just wanted pointer equality. So I did this:

    // originalIcon is an NSImage *
    [cachedIcons objectForKey:[NSNumber numberWithInt:(int)originalIcon]];

    It works beautifully. However, this code seems really weird to me. Is
    there a better way?

    Larry
  • On May 12, 2006, at 11:24 PM, Lawrence Sanbourne wrote:

    > Hello,
    >
    > My app needs a way of caching scaled images (thumbnails) so that they
    > aren't recreated each time. I first did this using an
    > NSMutableDictionary mapping from original image to scaled image.
    > However, this didn't work because apparently one original image wasn't
    > isEqual: to another.
    >
    > I realized that I just wanted pointer equality. So I did this:
    >
    > // originalIcon is an NSImage *
    > [cachedIcons objectForKey:[NSNumber numberWithInt:(int)originalIcon]];
    >
    > It works beautifully. However, this code seems really weird to me. Is
    > there a better way?
    >
    > Larry
    >

    Hey Larry,

    You could always create a NSImage subclass which holds its own
    singleton thumbnail.  If the thumbnail is nil when you request it,
    create it.

    - Andrew Bowman
  • El 13/05/2006, a las 8:24, Lawrence Sanbourne escribió:

    > My app needs a way of caching scaled images (thumbnails) so that they
    > aren't recreated each time. I first did this using an
    > NSMutableDictionary mapping from original image to scaled image.
    > However, this didn't work because apparently one original image wasn't
    > isEqual: to another.
    >
    > I realized that I just wanted pointer equality. So I did this:
    >
    > // originalIcon is an NSImage *
    > [cachedIcons objectForKey:[NSNumber numberWithInt:(int)originalIcon]];
    >
    > It works beautifully. However, this code seems really weird to me. Is
    > there a better way?

    According to the docs objects added to dictionaries are retained but
    keys are copied. So when you use your thumbnail as a key it is
    actually getting copied, thus changing the pointer. As you've
    discovered "isEqual:" evidently isn't doing a low-level comparison to
    see if it's really the same image and the equality test was failing.

    Although the test you describe looks "weird" to you it is in fact the
    way you would do it if what you actually want to compare on is
    pointer equality. The down-side of this approach is that you then
    have to worry about the lifespan of the thumbnail objects seeing as
    the dictionary is neither copying nor retaining them.

    As already suggested, an NSImage subclass might be more elegant. The
    other alternative is a compound class (with instance variables for
    the full-size image and for the thumbnail), or just use dictionaries
    directly with keys like "image" and "thumbnail". If you do this in a
    key-value coding compliant way then you can use it with bindings.

    Cheers,
    Greg
  • Just one detail I'd like to add. You use NSValue, not NSNumber, for pointers. So

    [NSNumber numberWithInt:(int)originalIcon]
    becomes
    [NSValue valueWithPointer:originalIcon]
  • Lots of great ideas. Thanks so much! I ended up making a icon cache
    object that holds the icon dictionary (using [NSValue
    valueWithPointer:originalIcon] -- thanks!). It also adds all the
    originalIcon objects to an NSMutableArray so that they get retained.
    (I didn't feel like manually retaining/releasing as the objects are
    added or removed from the dictionary.)

    Larry
  • On May 13, 2006, at 01:29, Greg Hurrell wrote:

    > El 13/05/2006, a las 8:24, Lawrence Sanbourne escribió:
    >
    >> My app needs a way of caching scaled images (thumbnails) so that they
    >> aren't recreated each time. I first did this using an
    >> NSMutableDictionary mapping from original image to scaled image.
    >> However, this didn't work because apparently one original image
    >> wasn't
    >> isEqual: to another.
    >>
    >> I realized that I just wanted pointer equality. So I did this:
    >>
    >> // originalIcon is an NSImage *
    >> [cachedIcons objectForKey:[NSNumber numberWithInt:(int)
    >> originalIcon]];
    >>
    >> It works beautifully. However, this code seems really weird to me. Is
    >> there a better way?
    >
    > According to the docs objects added to dictionaries are retained
    > but keys are copied. So when you use your thumbnail as a key it is
    > actually getting copied, thus changing the pointer. As you've
    > discovered "isEqual:" evidently isn't doing a low-level comparison
    > to see if it's really the same image and the equality test was
    > failing.
    >
    > Although the test you describe looks "weird" to you it is in fact
    > the way you would do it if what you actually want to compare on is
    > pointer equality. The down-side of this approach is that you then
    > have to worry about the lifespan of the thumbnail objects seeing as
    > the dictionary is neither copying nor retaining them.

    Another way to do this might be to create a CFMutableDictionary with
    custom callbacks that retain  keys instead of copying them, and
    compares keys based on pointer equality; with toll-free bridging,
    you'd just treat it as a standard NSMutableDictionary.  You have to
    ensure that the hash of the keys (NSImage instances) doesn't change
    while they're in the dictionary, though.

    --
    Adam
  • On 5/13/06, Adam R. Maxwell <amaxwell...> wrote:
    >
    > Another way to do this might be to create a CFMutableDictionary with
    > custom callbacks that retain  keys instead of copying them, and
    > compares keys based on pointer equality; with toll-free bridging,
    > you'd just treat it as a standard NSMutableDictionary.  You have to
    > ensure that the hash of the keys (NSImage instances) doesn't change
    > while they're in the dictionary, though.

    Sadly, this doesn't work. While you can indeed treat such a
    CFMutableDictionary as an NSMutableDictionary, the Cocoa methods will
    not respect your custom callbacks. In other words, if you create a
    CFMutableDictionary which does not copy its keys, then do [myCFDict
    setObject:obj forKey:key], key *will* be copied.

    I consider this a bug and have filed it as rdar://4350677 . The
    obvious workaround is to use the actual CF functions instead, which is
    not really a problem.

    Mike
  • On May 13, 2006, at 5:16 PM, Michael Ash wrote:

    > On 5/13/06, Adam R. Maxwell <amaxwell...> wrote:
    >>
    >> Another way to do this might be to create a CFMutableDictionary with
    >> custom callbacks that retain  keys instead of copying them, and
    >> compares keys based on pointer equality; with toll-free bridging,
    >> you'd just treat it as a standard NSMutableDictionary.  You have to
    >> ensure that the hash of the keys (NSImage instances) doesn't change
    >> while they're in the dictionary, though.
    >
    > Sadly, this doesn't work. While you can indeed treat such a
    > CFMutableDictionary as an NSMutableDictionary, the Cocoa methods will
    > not respect your custom callbacks. In other words, if you create a
    > CFMutableDictionary which does not copy its keys, then do [myCFDict
    > setObject:obj forKey:key], key *will* be copied.
    >
    > I consider this a bug and have filed it as rdar://4350677 . The
    > obvious workaround is to use the actual CF functions instead, which is
    > not really a problem.

    You could implement a subclass of the NSDictionary and
    NSMutableDictionary class clusters, using primitive methods that
    typecast "self" and call the CF functions. The result is an object
    that conforms to the standard NSMutableDictionary interface, but does
    respect your custom callbacks.

    sherm--

    Cocoa programming in Perl: http://camelbones.sourceforge.net
    Hire me! My resume: http://www.dot-app.org
  • On May 13, 2006, at 14:16, Michael Ash wrote:

    > On 5/13/06, Adam R. Maxwell <amaxwell...> wrote:
    >>
    >> Another way to do this might be to create a CFMutableDictionary with
    >> custom callbacks that retain  keys instead of copying them, and
    >> compares keys based on pointer equality; with toll-free bridging,
    >> you'd just treat it as a standard NSMutableDictionary.  You have to
    >> ensure that the hash of the keys (NSImage instances) doesn't change
    >> while they're in the dictionary, though.
    >
    > Sadly, this doesn't work. While you can indeed treat such a
    > CFMutableDictionary as an NSMutableDictionary, the Cocoa methods will
    > not respect your custom callbacks. In other words, if you create a
    > CFMutableDictionary which does not copy its keys, then do [myCFDict
    > setObject:obj forKey:key], key *will* be copied.

    Confirmed, and that sucks...we use custom dictionary/set/array
    callbacks fairly often, so I'll have to check those.  I think I
    generally use CF functions to add values, so I'm probably safe.
    Thanks for the warning.

    For the OP's problem, I'd still use this instead of wrapping pointers
    with NSValue and retaining them in a separate array, but YMMV.

    > I consider this a bug and have filed it as rdar://4350677 . The
    > obvious workaround is to use the actual CF functions instead, which is
    > not really a problem.

    Using the CF functions is easy enough, but it wasn't evident to me
    that this was required.  Further, I'm fairly sure the custom equality
    callback is used, at least in sets, so this seems inconsistent.

    -- Adam