views:

246

answers:

3

Say I have a class method like

+ (double)function:(id)param1 :(id)param2
{
   // I want to memoize this like...
   static NSMutableDictionary* cache = nil;
   //
   // test if (param1,param2) is in cache and return cached value, etc. etc
   //
}

Thanks!!

+2  A: 

If you want to create the cache once and check against it, I generally use an +initialize method. This method is called before the first message sent to the class, so the cache would be created before +function:: (which, by the way, is a terrible selector name) could be called. In this case, I usually declare the cache variable in the .m file, but declaring it in the method definition may also work.


Edit: Adding an example at request of OP:

// MyClass.m

static NSMutableDictionary* cache;

+ (void) initialize {
    cache = [[NSMutableDictionary alloc] init];
}

+ (double) cachedValueForParam1:(id)param1 param2:(id)param2 {
    // Test if (param1,param2) is in cache and return cached value.
}

Obviously, if a value doesn't exist in the cache, you should have some code that adds the value. Also, I have no idea how you intend to combine param1 and param2 as the key for the cache, or how you'll store the value. (Perhaps +[NSNumber numberWithDouble:] and -[NSNumber doubleValue]?) You'll want to make sure you understand dictionary lookups before implementing such a strategy.

Quinn Taylor
Obviously the names are generic lol. Could you provide an example of creating the variable in +initialize as it is not an instance member variable.
DevDevDev
I wouldn't recommend this approach. It's not multi-threaded safe, nor is it safe to call `+initialize` multiple times (which can happen via sub-classes calling `[super initialize]`).
johne
Certainly, there are ways you could make the method "safer", I just didn't want to overcomplicate the answer or overwhelm the OP. And honestly, it's a cache, so while replacing an existing dictionary would be a mistake and cause leaks, it would be easy to work around such a problem. For example, my `+initialize` generally checks (and often synchronizes on) `[self class]` before doing anything. Best practice is wonderful, but let's not throw every possible error case at a simple question, shall we?
Quinn Taylor
A: 

Depending on what you're trying to do and whether thread safety is an issue, you may also want to consider a singleton class as in the answer to this earlier question.

corprew
Also check out http://stackoverflow.com/questions/1986736/nsmutabledictionary-thread-safety on NSMutableDictionary thread safety.
corprew
+1  A: 

I use something like the following. Unlike the version posted by @Quinn Taylor, this version has the following properties:

  • Creates a NSAutoreleasePool to guarantee that a pool exists. Best to assume the worst when dealing with 'start-up' like code. Harmless if a pool already exists.
  • Creates cache exactly once:
    • Safe to call +initialize multiple times (may happen via sub-classing).
    • Multi-threaded safe. No matter how many threads concurrently call +initialize at the exact same time, cache is guaranteed to only be created once. The thread that 'wins' the atomic CAS retains cache, the threads that 'loose' autorelease their attempts.

If you want to be extremely conservative, you can add assertion checks that both pool and initCache are not NULL. Also note that this does nothing to ensure that cache is used in a multi-threaded safe way once it's been created.

#include <libkern/OSAtomic.h>

static NSMutableDictionary *cache;

+ (void)initialize
{
  NSAutoreleasePool   *pool      = [[NSAutoreleasePool alloc] init];
  NSMutableDictionary *initCache = [[[NSMutableDictionary alloc] init] autorelease];
  _Bool                didSwap   = false;

  while((cache == NULL) && ((didSwap = OSAtomicCompareAndSwapPtrBarrier(NULL, initCache, (void * volatile)&cache)) == false)) { /* Allows for spurious CAS failures. */ }
  if(didSwap == true) { [cache retain]; }

  [pool release];
  pool = NULL;
}
johne
+1 Those are good checks, and ones that I do in my own code. I left them out for simplicity, but they're a logical extension. The autorelease pool is a toss-up for me, since I always have on in `main()`, but may be applicable if one's code is used by third parties. For subclasses, I generally protect with `-isMemberOfClass:`, and for multi-threading I use @synchronized blocks on `[self class]` for simplicity. Still, good to include in a complete answer.
Quinn Taylor