0

I am writing some singleton class in my framework and I do not want other developer to call init and alloc on those singleton classes so I have override those methods but since I am using ARC, so my code for singleton class is:

+(MFSingletonClass*) sharedInstance
{
    static dispatch_once_t pred = 0;
    dispatch_once(&pred, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

and following are alloc and init methods:

+ (id)alloc {
    static dispatch_once_t pred = 0;
    dispatch_once(&pred, ^{
        sharedInstance = [super alloc];
    });
    return sharedInstance;
}

- (id)init {

    __weak typeof(self) weakSelf = self;
    static dispatch_once_t pred = 0;
    dispatch_once(&pred, ^{
        static BOOL init = NO;
        if (!init) {
            init = YES;
            typeof(self) strongSelf = weakSelf;
            strongSelf = [super init];
        }
    });
    return weakSelf;
}

I wanted to know whether my implementation of alloc and init method is proper or not?

Thanks in advance!

Nuzhat Zari
  • 3,398
  • 2
  • 24
  • 36
  • You should have a look at [Peter Hosey's article on singletons](http://boredzo.org/blog/archives/2009-06-17/doing-it-wrong). And, no, your implementations are not correct. The predicates in `sharedInstance` and `alloc` are totally separate from each other; your shared instance can still be created twice. – jscs Feb 24 '14 at 07:22
  • The link you have provided for duplicate, it does not show any implementation for init. Nor I wanted to know what is the correct way I wanted know what is the problem in my current implementation. – Nuzhat Zari Feb 24 '14 at 08:45
  • @JoshCaswell - While the code is not good how can the shared instance be created twice? (`dispatch_once` blocks in a concurrent situation if another thread is executing the block) – CRD Feb 24 '14 at 18:23
  • Oops, yeah, @CRD, they can't. I was thinking that if `alloc` was called first, two instances would be created. – jscs Feb 24 '14 at 19:37

2 Answers2

3

You ask whether your "implementation of alloc and init method is proper or not?"

Depends on how you define "proper"...

Your code is not incorrect as far as it goes, in that it usually does work, but the init is, let's say, over-cautious and a little incorrect, and there is a trick or two you missed.

Looking at the init you double up checking whether the initialisation has been run:

  1. dispatch_once; and
  2. a BOOL flag init.

The block passed to dispatch_once will be executed at most once, and at that time init will be NO. Overkill. Removing that you get:

- (id)init
{
   __weak typeof(self) weakSelf = self;
   static dispatch_once_t pred = 0;
   dispatch_once(&pred,
                 ^{
                    typeof(self) strongSelf = weakSelf;
                    strongSelf = [super init];
                  });
   return weakSelf;
}

Now consider [super init], this is a method call on the current instance, that is though it is not explicit this expression references self. Given that your efforts to avoid a retain cycle are wasted. Furthermore in those efforts you assigned the result of [super init] to a block-local variable strongSelf while init returns weakSelf - this is the reason for the "usually does work" remark above, most calls to [super init] do actually return the same object, but the need not. If we remove the use of weak references we solve both these issues at once:

- (id)init
{
   static dispatch_once_t pred = 0;
   dispatch_once(&pred,
                 ^{
                    self = [super init];
                  });
   return self;
}

Bot doesn't this leave us with a retain cycle? No, the object referenced by self does not keep a reference to the block, so the block having a reference back to the object doesn't matter.

The "as far as it goes" remark above

The default alloc method just calls allocWithZone:, the latter takes a memory zone - something which is no longer used but exists for historical reasons. Therefore you should really implement allocWithZone: rather than alloc to catch all allocations - the code would follow the same algorithm as your alloc.

Then there is the copy and copyWithZone: pair. If you want a singleton you don't copies of it, so you should implement copyWithZone: (which copy just calls as will alloc/allocWithZone:) and return self:

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

The missed trick

So your code works as far as it goes, apart from the case where [super init] returns a different object, but was overcomplicated; clean up init to address that.

However if you dig up Apple's now deprecated Cocoa Objects documentation you'll find Apple's old recommendation for producing a true singleton. Under ARC you can ignore the MRC overrides, and you can add the use of GCD as you have done to ensure concurrent correctness, and that will leave you with one trick: Apple implement allocWithZone: by calling sharedManager (your sharedInstance) and having that in turn call allocWithZone on super. With your code that would be:

+ (MFSingletonClass*) sharedInstance
{
   static MFSingletonClass *sharedInstance = nil;
   static dispatch_once_t pred = 0;
   dispatch_once(&pred,
                 ^{
                    sharedInstance = [[super allocWithZone:NULL] init];
                  });
   return sharedInstance;
}

+ (id)allocWithZone:(NSZone *)ignore
{
    return [self sharedInstance];
}

Which is a little cleaner, but your approach works fine as well.

And kudos for knowing the difference between a true singleton and just a shared instance!

HTH

CRD
  • 52,522
  • 5
  • 70
  • 86
  • Thanks for explanation. But I want to know why we are calling [[super allocWithZone:NULL] init], why can't [[self alloc] init], since internally it will call allocWithZone? And as you explained, here super refere to self only. – Nuzhat Zari Feb 25 '14 at 06:29
  • I obviously wasn't clear: the call `[super init]` passes the current value of `self` (a reference to the object being initialised) to the called `init` method (which is the one defined by the superclass, or one of its ancestors), where is becomes the value of that method's `self`. If we invoked `[self allocWithZone:NULL]` in `sharedInstance` we would simply get infinite recursion as we've overridden `allocWithZone:` to call `sharedInstance`... So `MFSingletonClass`'s `alloc` invokes its `allocWithZone:`, which invokes its `sharedInstance`, which invokes the superclass' `allocWithZone:`. – CRD Feb 25 '14 at 06:49
1

With singletons, you have two choices: Write the very simple sharedInstance method and be done with it, or try to do all kinds of ingenious tricks to keep people from instantiating more objects. Trying to instantiate the singleton through any call other than sharedInstance is a programming error. You don't write complicated code to prevent programming errors. And you'll never get it right anyway :-)

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • Coward giving this a negative rating should have left a comment, but didn't. My answer stands; if anyone disagrees they can add a comment. – gnasher729 Mar 04 '14 at 07:46