objective-c singleton macro that supports both ARC enabled and disabled projects - Byron Salau

Posted by | February 20, 2013 | objective-c | 3 Comments

Im a big fan of Matt Gallagher’s singleton macro and have been using a slightly modified version of it by Oliver Jones.

Lately however I’ve been enabling ARC in my projects and came across the problem of including the macro only to have xcode whinge to me about all the release methods. I’m using a library so I wanted my macro to support both environments. I initially went back to Matt’s macro to see if he perhaps updated for this situation but it seems not so. I was also surprised to see that i couldn’t really find any thing else useful on the subject.

So back to basics, my buddy Adam Phin and I figured we can detect if ARC is enabled with the following preproccessing flag

#if __has_feature(objc_arc)

Using this flag we came up with this macro.


//
// SynthesizeSingleton.h
// Copyright 2009 Matt Gallagher. All rights reserved.
//
// Modified by Oliver Jones and Byron Salau
//
// Permission is given to use this source code file without charge in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.

#if __has_feature(objc_arc)
#define SYNTHESIZE_SINGLETON_FOR_CLASS(classname, accessorname) \
+ (classname *)accessorname\
{\
static classname *accessorname = nil;\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
accessorname = [[classname alloc] init];\
});\
return accessorname;\
}
#else
#define SYNTHESIZE_SINGLETON_FOR_CLASS(classname, accessorname) \
static classname *shared##classname = nil; \
+ (void)cleanupFromTerminate \
{ \
classname *temp = shared##classname; \
shared##classname = nil; \
[temp dealloc]; \
} \
+ (void)registerForCleanup \
{ \
[[NSNotificationCenter defaultCenter] addObserver:self \
selector:@selector(cleanupFromTerminate) \
name:UIApplicationWillTerminateNotification \
object:nil]; \
} \
+ (classname *)accessorname \
{ \
static dispatch_once_t p; \
dispatch_once(&p, \
^{ \
if (shared##classname == nil) \
{ \
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \
shared##classname = [[self alloc] init]; \
[self registerForCleanup]; \
[pool drain]; \
} \
}); \
return shared##classname; \
} \
+ (id)allocWithZone:(NSZone *)zone \
{ \
static dispatch_once_t p; \
__block classname* temp = nil; \
dispatch_once(&p, \
^{ \
if (shared##classname == nil) \
{ \
temp = shared##classname = [super allocWithZone:zone]; \
} \
}); \
return temp; \
} \
- (id)copyWithZone:(NSZone *)zone { return self; } \
- (id)retain { return self; } \
- (NSUInteger)retainCount { return NSUIntegerMax; } \
- (oneway void)release { } \
- (id)autorelease { return self; }
#endif

Usage

#import "SynthesizeSingleton.h"
@implementation MyClass
SYNTHESIZE_SINGLETON_FOR_CLASS(MyClass)
@end
  • todd

    Is there no need to continue using registerForCleanup/cleanupFromTerminate in an ARC enabled app?

    • byrons

      Nope, ARC takes care of all that for you :)

  • Joel Middendorf

    This is nice. However, I think you would still want to override copyWithZone in the ARC version too to avoid the possibility of implementing this method in your class that returns a copy of your instance.

    - (id)copyWithZone:(NSZone *)zone { return self; }

    I think you will also want to override mutableCopyWithZone in both the ARC and non-ARC versions as well.