Skip to content

Instantly share code, notes, and snippets.

@rob-brown
Created July 31, 2011 02:28
Show Gist options
  • Save rob-brown/1116294 to your computer and use it in GitHub Desktop.
Save rob-brown/1116294 to your computer and use it in GitHub Desktop.
A singleton that can be safely subclassed to reduce code duplication.
//
// RBSimpleSingleton.h
//
// Copyright (c) 2011 Robert Brown
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
/**
* RBSimpleSingleton is a singleton that can be safely subclassed. There is only
* one benefit to RBSimpleSingleton. It allows you to put any common singleton
* code you may have in one location, which you probably won't ever have.
* RBSimpleSingleton is so basic it really doesn't need to exist. It's purpose
* is mostly to show an alternative to RBSingleton (or any other standard
* singleton for that matter). Classes in
* Objective-C are singletons. So we just take advantage of that fact. Since
* there is no instance, there are no intance variables (ivars). All data needed
* for this class should be put in either static class variables or static methods variables. To ensure
* thread safety, you can @synchronize(self) ('self' references the class in
* class methods) or use your own locks or Grand Central Dispatch queues. You
* may be surprised with what you can do with just a class.
*/
@interface RBSimpleSingleton : NSObject
// It doesn't get any more simple than this.
@end
//
// RBSingleton.h
//
// Copyright (c) 2011 Robert Brown
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
/**
* RBSingleton is a singleton that can be safely subclassed. There are two main
* benefits to RBSingleton. First, you don't need to include the same singleton
* code in all of your singleton classes. I often forget everything that is
* needed, so I have to hunt down an old singleton and copy and paste its code.
* With RBSingleton, all you need to do is subclass it and you're done. Second,
* if something ever happens that requires you to change the standard singleton
* code, you only need to change it in one place. This situation happened to me
* when Xcode's static analyzer started recognizing singletons as memory leaks.
* I had to change all my singletons as a result.
*
* NOTE: Don't forget to include libobjc.dylib since RBSingleton uses the
* Objective-C dynamic runtime.
*/
@interface RBSingleton : NSObject
/**
* Returns the shared instance of this class. You may want to indirectly call
* this method with your own method to rename it as you like or change the
* return type to be specific to your class (see example 1 below). If you want
* to make the request to get your singleton more efficient, you can cache the
* singleton instance in a static class variable (see example 2 below).
*
* @code
* // Example 1
* + (MyManager *)sharedManager {
* return (MyManager *)[super sharedInstance];
* }
*
* // Example 2
* static MyManager * sharedManager = nil;
*
* + (MyManager *)sharedManager {
*
* @synchronized(self) {
*
* if(!sharedManager) {
* sharedManager = (MyManager *)[super sharedInstance];
* }
*
* return sharedManager;
* }
* }
*/
+ (id)sharedInstance;
@end
//
// RBSingleton.m
//
// Copyright (c) 2011 Robert Brown
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <objc/message.h>
#import "RBSingleton.h"
/**
* A dictionary that holds all of the shared instances for this class and all
* its subclasses. Each class should only have one instance (otherwise it
* wouldn't be a singleton). The key for each class is defined by
* -sharedInstanceKey.
*/
static NSMutableDictionary * sharedInstances = nil;
@interface RBSingleton ()
/**
* A getter that lazy creates the dictionary of instances.
*
* @return A dictionary with all of the allocated singleton instances.
*/
+ (NSMutableDictionary *)sharedInstances;
/**
* The key that is used to store the single in the sharedInstances dictionary.
* By default this returns the class's name. You should have no need to override
* this method, but it's here if you want to.
*
* @return The key that is used to store the single in the sharedInstances
* dictionary.
*/
+ (NSString *)sharedInstanceKey;
/**
* Simulated protected initializer. Override if you need custom initialization.
*
* @return The singleton instance.
*/
- (id)initialize;
@end
@implementation RBSingleton
#pragma mark - Singleton methods
+ (id)sharedInstance {
id sharedInstance = nil;
// Technically a singleton is a memory leak, but we won't tell the static
// analyzer that minor detail.
#if !defined(__clang_analyzer__)
NSMutableDictionary * instances = [self sharedInstances];
// We must synchronize on 'instances' to guarantee thread safety.
@synchronized(instances) {
NSString * classKey = [self sharedInstanceKey];
sharedInstance = [instances valueForKey:classKey];
if (!sharedInstance) {
// We must go straight to the grandsuper class because calling super
// is not safe. Calling super from a subclass of RBSingleton will
// call RBSingleton's implementation of allocWithZone:, which is
// designed to be a no-op. If for some reason, RBSingleton needs to
// be a sublass of some class besides NSObject, this line needs to
// be changed too. It's not hard to dynamically discover the
// grandsuper class, but since we know it, we can hard code it for
// simplicity.
Method allocMethod = class_getClassMethod([NSObject class], @selector(allocWithZone:));
sharedInstance = [method_invoke(self, allocMethod, nil) initialize];
[instances setValue:sharedInstance forKey:classKey];
}
}
#endif
return sharedInstance;
}
+ (NSMutableDictionary *)sharedInstances {
if (!sharedInstances)
#if __has_feature(objc_arc)
sharedInstances = [NSMutableDictionary dictionary];
#else
sharedInstances = [[NSMutableDictionary dictionary] retain];
#endif
return sharedInstances;
}
+ (NSString *)sharedInstanceKey {
return NSStringFromClass(self);
}
- (id)initialize {
return self;
}
+ (id)allocWithZone:(NSZone *)zone {
#if __has_feature(objc_arc)
return [self sharedInstance];
#else
// The retain is needed to satisfy the static analyzer.
return [[self sharedInstance] retain];
#endif
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)init {
return self;
}
#if !__has_feature(objc_arc)
- (id)retain {
return self;
}
- (oneway void)release {
// Do nothing.
}
- (id)autorelease {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax;
}
#endif
@end
@rob-brown
Copy link
Author

Don't forget to include libobjc.dylib since RBSingleton uses the Objective-C dynamic runtime.

@kevinrenskers
Copy link

Just too bad that it returns "id" instead of the actual class. Can't use properties ([MyClass sharedInstance].myProperty) and autocompletion for methods now includes all methods from all classes. I don't see a solution though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment