How do I implement an Objective-C singleton that is compatible with ARC?
Asked Answered
C

10

178

How do I convert (or create) a singleton class that compiles and behaves correctly when using automatic reference counting (ARC) in Xcode 4.2?

Clements answered 27/9, 2011 at 12:14 Comment(1)
I recently found an Article from Matt Galloway going quite in depth on Singletons for both ARC and manual memory management environments. galloway.me.uk/tutorials/singleton-classesClements
G
403

In exactly the same way that you (should) have been doing it already:

+ (instancetype)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}
Gloom answered 27/9, 2011 at 12:22 Comment(15)
You just don't do any of the memory management hokey pokey Apple used to recommend in developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…Pharisaic
what is onceToken? i understand the need for a token but what is it? an enum? can i type what ever...Kellam
@YogevShelly dispatch_once_t is defined in dispatch/once.h, and it's typedefed as a long. It's probably used as an index on an array or some other global data structure which stores the state of every Dispatch Once token. You can call it whatever you want, and it will still work.Gloom
I'm working from "Pro Objective C Design Patterns" from Apress. The author demonstrates a pre-ARC example of the Singleton pattern. This one works fine for but can someone share the documentation on this pattern please?Amalgamate
@MakingScienceFictionFact, you might want to take a look at this postBenjy
Why isn't static MyClass *sharedInstance = nil; be placed outside of the method?Mont
@Mont Because the whole idea is that access to sharedInstance is safely encapsulated within a method that ensures it will be created properly. Nothing outside of that method should have access to it directly. If you needed to explicitly clear or nil-out the sharedInstance or something, then you would need to place it outside of the method.Gloom
@NickForge but if you were to call shared instance multiple times, wouldn't the shared instance be two different objects?Mont
@Mont static variables declared within a method/function are the same as a static variable declared outside a method/function, they are just only valid within the scope of that method/function. Every separate run through the +sharedInstance method (even on different threads) will 'see' the same sharedInstance variable.Gloom
@NickForge I did some testing and it is what you say. Thank you.Mont
What about if somebody calls [[MyClass alloc] init]? That would create a new object. How can we avoid this (other than declaring static MyClass *sharedInstance = nil outside the method).Ramayana
Because static variables are 0 (or nil) by default, the = nil is redundant.Optics
@RicardoSánchez-Sáez You can delete +alloc~ method to prevent external instantiation completely. I posted an example as an answer.Micamicaela
Personally, I think actively preventing more than one instantiation is usually an incorrect course of action. Provide a shared instance accessor if appropriate, but hard-wiring only one instance is usually wrong IMO. Objective C is a dynamic language with a non-nanny-state culture. YMMV depending on if the singleton class is internal to a project vs. available over a public API.Comeback
If another programmer messes up and calls init when they should have called sharedInstance or similar, it's their error. Subverting the fundamentals, and basic contracts, of the language in order to stop others potentially making mistakes seems quite wrong. There's more discussion at boredzo.org/blog/archives/2009-06-17/doing-it-wrongComeback
M
8

if you want to create other instance as needed.do this:

+ (MyClass *)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

else,you should do this:

+ (id)allocWithZone:(NSZone *)zone
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}
Maje answered 8/4, 2013 at 9:19 Comment(7)
True/False: The dispatch_once() bit means that you won't get additional instances, even in the first example...?Roguish
@Olie: False, because client code can do [[MyClass alloc] init] and bypass the sharedInstance access. DongXu, you should look at Peter Hosey's Singleton article. If you're going to override allocWithZone: to prevent more instances from being created, you also should override init to prevent the shared instance from being re-initialized.Merrileemerrili
Ok, that's what I thought, hence the allocWithZone: version. Thx.Roguish
This completely breaks the contract of allocWithZone.Comeback
For more discussion on "to Singleton or not", see boredzo.org/blog/archives/2009-06-17/doing-it-wrongComeback
singleton just means "only one object in memory at any time",this is one thing,being re-initialized is another thing.Maje
To make this work with allocWithZone:, you should keep a dictionary of MyClass objects, where the keys are the NSZones.Sombrous
L
5

This is a version for ARC and non-ARC

How To use:

MySingletonClass.h

@interface MySingletonClass : NSObject

+(MySingletonClass *)sharedInstance;

@end

MySingletonClass.m

#import "MySingletonClass.h"
#import "SynthesizeSingleton.h"
@implementation MySingletonClass
SYNTHESIZE_SINGLETON_FOR_CLASS(MySingletonClass)
@end
Levasseur answered 7/6, 2013 at 5:35 Comment(0)
M
2

This is my pattern under ARC. Satisfies new pattern using GCD and also satisfies Apple's old instantiation prevention pattern.

@implementation AAA
+ (id)alloc
{
    return  [self allocWithZone:nil];
}
+ (id)allocWithZone:(NSZone *)zone
{
    [self doesNotRecognizeSelector:_cmd];
    abort();
}
+ (instancetype)theController
{
    static AAA* c1  =   nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        c1  =   [[super allocWithZone:nil] init];

        // For confirm...       
        NSLog(@"%@", NSStringFromClass([c1 class]));    //  Prints AAA
        NSLog(@"%@", @([c1 class] == self));            //  Prints 1

        Class   real_superclass_obj =   class_getSuperclass(self);
        NSLog(@"%@", @(real_superclass_obj == self));   //  Prints 0
    });

    return  c1;
}
@end
Micamicaela answered 16/10, 2013 at 9:44 Comment(5)
Won't this result in c1 being an instance of AAA's superclass? You need to call +alloc on self, not on super.Gloom
@NickForge super doesn't mean the super-class object. You cannot get super-class object It just means routing messages to the super-class version of method. super still points self class. If you want to get super-class object, you need to get runtime reflection functions.Micamicaela
@NickForge And -allocWithZone: method is just a simple chain to runtime's allocation function to offer overriding point. So ultimately, self pointer == current class object will be passed to allocator, and finally AAA instance will be allocated.Micamicaela
you're correct, I'd forgotten the subtleties of how super works in class methods.Gloom
Remember to use #import <objc/objc-runtime.h>Colwell
T
2

Read this answer and then go and read the other answer.

You must first know what does a Singleton mean and what are its requirements, if you don't understand it, than you won't understand the solution--at all!

To create a Singleton successfully you must be able to do the following 3:

  • If there was a race condition, then we must not allow multiple instances of your SharedInstance to be created at the same time!
  • Remember and keep the value among multiple invocations.
  • Create it only once. By controlling the entry point.

dispatch_once_t helps you to solve a race condition by only allowing its block to be dispatched once.

Static helps you to “remember” its value across any number of invocations. How does it remember? It doesn't allow any new instance with that exact name of your sharedInstance to be created again it just works with the one that was created originally.

Not using calling alloc init (i.e. we still have alloc init methods since we are an NSObject subclass, though we should NOT use them) on our sharedInstance class, we achieve this by using +(instancetype)sharedInstance, which is bounded to only be initiated once, regardless of multiple attempts from different threads at the same time and remember its value.

Some of the most common system Singletons that come with Cocoa itself are:

  • [UIApplication sharedApplication]
  • [NSUserDefaults standardUserDefaults]
  • [NSFileManager defaultManager]
  • [NSBundle mainBundle]
  • [NSOperations mainQueue]
  • [NSNotificationCenter defaultCenter]

Basically anything that would need to have centralized effect would need to follow some sort of a Singleton design pattern.

Taconite answered 16/4, 2016 at 17:29 Comment(0)
C
1

Alternatively, Objective-C provides the +(void)initialize method for NSObject and all its sub-classes. It is always called before any methods of the class.

I set a breakpoint in one once in iOS 6 and dispatch_once appeared in the stack frames.

Chauffer answered 20/5, 2013 at 19:28 Comment(0)
D
0

Singleton Class : No one can create more than one object of class in any case or through any way.

+ (instancetype)sharedInstance
{
    static ClassName *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[ClassName alloc] init];
        // Perform other initialisation...
    });
    return sharedInstance;
}
//    You need need to override init method as well, because developer can call [[MyClass alloc]init] method also. that time also we have to return sharedInstance only. 

-(MyClass)init
{
   return [ClassName sharedInstance];
}
Dyandyana answered 29/4, 2016 at 2:31 Comment(1)
If someone calls init, init will call sharedInstance, sharedInstance will call init, init will call sharedInstance a second time, then crash! First, this is an infinite recursion loop. Second, the second iteration of calling dispatch_once will crash because it can't be called again from inside dispatch_once.Joppa
B
0

There are two issues with the accepted answer, which may or may not be relevant for your purpose.

  1. If from the init method, somehow the sharedInstance method is called again (e.g. because other objects are constructed from there which use the singleton) it will cause a stack overflow.
  2. For class hierarchies there is only one singleton (namely: the first class in the hierarchy on which the sharedInstance method was called), instead of one singleton per concrete class in the hierarchy.

The following code takes care of both of these problems:

+ (instancetype)sharedInstance {
    static id mutex = nil;
    static NSMutableDictionary *instances = nil;

    //Initialize the mutex and instances dictionary in a thread safe manner
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mutex = [NSObject new];
        instances = [NSMutableDictionary new];
    });

    id instance = nil;

    //Now synchronize on the mutex
    //Note: do not synchronize on self, since self may differ depending on which class this method is called on
    @synchronized(mutex) {
        id <NSCopying> key = (id <NSCopying>)self;
        instance = instances[key];
        if (instance == nil) {
            //Break allocation and initialization into two statements to prevent a stack overflow, if init somehow calls the sharedInstance method
            id allocatedInstance = [self alloc];

            //Store the instance into the dictionary, one per concrete class (class acts as key for the dictionary)
            //Do this right after allocation to avoid the stackoverflow problem
            if (allocatedInstance != nil) {
                instances[key] = allocatedInstance;
            }
            instance = [allocatedInstance init];

            //Following code may be overly cautious
            if (instance != allocatedInstance) {
                //Somehow the init method did not return the same instance as the alloc method
                if (instance == nil) {
                    //If init returns nil: immediately remove the instance again
                    [instances removeObjectForKey:key];
                } else {
                    //Else: put the instance in the dictionary instead of the allocatedInstance
                    instances[key] = instance;
                }
            }
        }
    }
    return instance;
}
Burrton answered 2/1, 2017 at 15:8 Comment(0)
A
-2
#import <Foundation/Foundation.h>

@interface SingleTon : NSObject

@property (nonatomic,strong) NSString *name;
+(SingleTon *) theSingleTon;

@end

#import "SingleTon.h"
@implementation SingleTon

+(SingleTon *) theSingleTon{
    static SingleTon *theSingleTon = nil;

    if (!theSingleTon) {

        theSingleTon = [[super allocWithZone:nil] init
                     ];
    }
    return theSingleTon;
}

+(id)allocWithZone:(struct _NSZone *)zone{

    return [self theSingleTon];
}

-(id)init{

    self = [super init];
    if (self) {
        // Set Variables
        _name = @"Kiran";
    }

    return self;
}

@end

Hope above code will help it out.

Arjan answered 6/8, 2015 at 11:48 Comment(0)
E
-2

if you need to create singleton in swift,

class var sharedInstance: MyClass {
    struct Singleton {
        static let instance = MyClass()
    }
    return Singleton.instance
}

or

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

you can use this way

let sharedClass = LibraryAPI.sharedInstance
Ep answered 21/8, 2015 at 14:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.