Jack Freeman
Jack Freeman

Reputation: 1414

Self destructing singleton design pattern iOS

I recently came across an issue in which I only wanted one instance of a particular object to exist, and exist for only the brief period of time it needed to perform a specific operation. Its operation was asynchronous so ARC would dealloc it at the end of the run loop if I didn't hold a reference to it. If I did hang onto it I would need delegate callbacks or notifications to know when it was done to release it.

The object needed to download several images and other data and cache it to disk. I didn't want it to waste memory when it wasn't caching items since the cache limit was around 24 hours. I also didn't need feedback of any kind from it; I wanted it to perform it's task and be done with itself.

I came up with a design pattern I liked quite nicely. I've used it in a few other projects since then, and was curios if it was a well known and analyzed pattern that I'm just not aware of (self-destructing singleton???). I'd like to know so I can be made aware of any potential pitfalls I'm not currently seeing.

I'm also very interested to hear any input you guys might have about why this is a bad design.

The Design Goes Like This (this is ARC, but non-arc can work too if you release the singleton through a class method):

A global static object (not really a singleton because it doesn't live the entire time)

    static MySelfDestructingClass* singleton;

A single public class method

    + (void)downloadAndCacheDataIfNeeded
     {
        //Force synchronized access
        @synchronized(singleton){
            //We are already doing something, return
            if(singleton){
             return;
            }
             NSDate* lastCacheDate = [[NSUserDefaults standardDefaults] objectForKey:kKeyForLastUpdate];
            if([[NSDate date] timeIntervalSinceDate:lastCacheDate] > kCacheLimit){
              //Our cache is out of date, we need to update
                singleton = [[self alloc] init];
                [singleton downloadAndCache];
             }
        }
     }

Now our instance methods, we need our object alive so the request can come back:

      - (void)downloadAndCache
        {
               //This would probably be a delegate, but for simplicity of this example it's a notification
               [[NSNotificationCenter defaultCenter] addObserver:self forNotificationWithName:NotificationSomeRequestDidSucceed selector:@selector(someCustomRequestDidSucceed:withData:) object:nil];
               [SomeCustomRequest makeRequestWithURL:@"http://www.someURL.com"];

        }

      - (void)someCustomRequestDidSucceed:(SomeCustomRequest *)request withData:(NSDictionary *)dictionary
        {

             //Do whatever we need to in order to save our data, or fire off image download requests etc...
             ....


            //Set our lastUpdated time in NSUserDefaults
            [[NSUserDefaults standardDefaults] setObject:[NSDate date] forKey:kKeyForLastUpdate];

            //Remove our observer
            [NSNotificationCenter defaultCenter] removeObserver:self name:NotificationSomeRequestDidSucceed object:nil];

            //Release ourselves (ok not really, but tell arc we can be released)
            singleton = nil;
        } 

This way all I have to do anywhere else in the application is:

     [MySelfDestructingClass downloadAndCacheDataIfNeeded];

Now this object will download things if it needs to and release itself when it's done, or not create itself at all. It also won't go and start downloading the data twice.

I'm aware this design has limitations with extendibility and functionality, but for an instance like this, and the other ones I've used it for, I've found it quite useful.

Upvotes: 4

Views: 891

Answers (1)

Jody Hagins
Jody Hagins

Reputation: 28409

This pretty common using blocks. Consider something similar (though I would probably handle multiple invocations differently...)

void ExecuteWithMySingleSelfDestructingObject(void(^block)(MySelfDestructingClass *object)) {
    static MySelfDestructingClass* singleton;
    @synchronized(singleton) {
        if (singleton) {
          // To get past the synchronization primitive, this must be a recursive call.
        }
        // Whatever other conditions you want to have (like your date check)
        singleton = [[MySelfDestructingClass] alloc] init];
        @try { block(singleton); }
        @finally { singleton = nil; }
    }
}

Note double exception handling (try/finally plus what @synchronized does - may want to change that...

Then do whatever you want with the block...

ExecuteWithMySingleSelfDestructingObject(^(MySelfDestructingClass *object){
    // Do whatever I want with the singleton instance that has
    // been given to me as <object>
});

Of course, it could be a class method...

+ (void)performBlock:(void(^)(MySelfDestructingClass *object))block {
    static MySelfDestructingClass* singleton;
    @synchronized(singleton) {
        if (singleton) {
          // To get past the synchronization primitive, this must be a recursive call.
        }
        // Whatever other conditions you want to have (like your date check)
        singleton = [[self] alloc] init];
        @try { block(singleton); }
        @finally { singleton = nil; }
    }
}

[MySelfDestructingClass performBlock:^(MySelfDestructingClass *object){
    // Do whatever I want with the singleton instance that has
    // been given to me as <object>
}];

I hope that makes sense (I typed it free-hand, so syntax may vary, but you should get the idea).

Upvotes: 1

Related Questions