BNRItemStore as a singleton, but has init()


#1

The BNRItemStore class is designed to be a singleton, but it has an init() function. What would stop someone from doing this?

BNRItemStore *store = [[BNRItemStore alloc] init];

to create an instance of the class?


#2

I think it’s because we’ve overridden allocWithZone: so it returns the singleton. So when you execute your line of code:

BNRItemStore *store = [[BNRItemStore alloc] init];

what happens is [BNRItemStore alloc] calls [BNRItemStore allocWithZone], which returns the singleton. So it’s the singleton that ends up getting reinitialized.


#3

[quote=“SkyPants”]I think it’s because we’ve overridden allocWithZone: so it returns the singleton. So when you execute your line of code:

BNRItemStore *store = [[BNRItemStore alloc] init];

what happens is [BNRItemStore alloc] calls [BNRItemStore allocWithZone], which returns the singleton. So it’s the singleton that ends up getting reinitialized.[/quote]

Nope! I added this code in ItemViewController::init() (C++ notation I know, but you get the point)

    if ([[BNRItemStore alloc] init] == [BNRItemStore sharedStore]) {
        NSLog(@"Yep they're equal!!!");
    }
    else {
        NSLog(@"Nope they're NOT equal!!!");
    }

and got the else clause to print.


#4

I can’t reproduce the same thing. Are you sure you have implemented allocWithZone: as a class method?


#5

Ok, I had old code (from book). I downloaded updated code and I see the discrepancy now. Thanks.


#6

In the implementation of the class method sharedStore, the first step is to declare the static variable sharedStore and initialize it to nil.
Wouldn’t this line of code reset sharedStore to nil in every subsequent calls to the method sharedStore? So it will always recreate the singleton instance?

A related question is: what is the difference between declaring the static variable inside the method sharedStore (as in the book), or outside it as below:

[code]static BNRItemStore *sharedStore = nil;

  • (BNRItemStore *) sharedStore
    {
    NSLog(@“sharedStore:%@”,sharedStore);
    if(!sharedStore){
    sharedStore = [[super allocWithZone:nil] init];
    }
    return sharedStore;
    }[/code]
    In the implementation of singleton pattern, is it crucial that the static variable sharedStore can only be accessed in the method sharedStore?

#7

When you already have a sharedStore singleton, and have three items in the shared store, by calling this:

the first this that happens is, [BNRItemStore alloc] returns the singleton. But wouldn’t init be sent to that result? If so, the init we implemented in the book would do this:

thus resetting the allItems array to nil, correct?


#8

[quote=“rindra”]In the implementation of the class method sharedStore, the first step is to declare the static variable sharedStore and initialize it to nil.
Wouldn’t this line of code reset sharedStore to nil in every subsequent calls to the method sharedStore? So it will always recreate the singleton instance?[/quote]

I had the exact same question, and this is quite easy to test. Just put an NSLog in the if-statement and call the sharedStore method twice. It only logged one statement in my test, so my guess is that Objective-C doesn’t count nil as an actual value.

I can’t answer your other questions with certainty. but I don’t think it would matter much if your static variable could be accessed by other methods since it’s pretty much readonly.

I have another question about this topic though. In the book the sharedStore method returns a pointer to an instance of BNRItemStore. But what if you subclass BNRItemStore? I know alloc and init return id because of inheritance, so wouldn’t it be more logical for sharedStore to return an id?

[quote=“itsmewade”]When you already have a sharedStore singleton, and have three items in the shared store, by calling this:

the first this that happens is, [BNRItemStore alloc] returns the singleton. But wouldn’t init be sent to that result? If so, the init we implemented in the book would do this:

thus resetting the allItems array to nil, correct?[/quote]

This also intrigued me, so I tested it and the allItems array was indeed reset in my case.


#9

Hi,

Rather than asking the question, I tested my question out.

I was wondering why one couldn’t initialize the static variable in one statement (like in Java):

The error states: “Initializer element is not a compile-time constant”

I guess you need to initialize with nil and then set it.

Just to clarify my understanding: we don’t need to worry about concurrent threads accessing a singleton as each client app is hosted on one iOS device that runs with one thread throughout the life of the application?


#10

That’s not quite correct; you can have multiple threads running in an iOS application if you choose to do so. iOS devices nowadays come with dual cores, so you can get some real concurrency. Check out the Grand Central Dispatch library if you have had experience with multi-threading before.


#11

I must admit I don’t see how this singleton code is safe.

For instance someone can write the following code:

		BNRItemStore *store1 = [[BNRItemStore alloc]init];
		[store1 createItem];
		
		BNRItemStore *store2 = [[BNRItemStore alloc]init];
		[store2 createItem];
		
		NSLog(@"Store1 = %@ - %@", store1, [store1 allItems]);
		NSLog(@"Store2 = %@ - %@", store2, [store2 allItems]);

The output is:

2013-02-25 08:05:06.441 Homepwner[2335:c07] Store1 = <BNRItemStore: 0x9593590> - ("Shiny Spork (5Y2V3): Worth $40, recorded on 2013-02-25 08:05:06 +0000")
2013-02-25 08:05:06.442 Homepwner[2335:c07] Store2 = <BNRItemStore: 0x9593590> - ("Shiny Spork (5Y2V3): Worth $40, recorded on 2013-02-25 08:05:06 +0000")
2013-02-25 08:05:06.448 Homepwner[2335:c07] Destroyed: Rusty Spork (8Q2U8): Worth $73, recorded on 2013-02-25 08:05:06 +0000 

As you can see the address is the same but as init has been called we have lost the original item added to the Store1 reference.

Now in C++ this is easy you just make the constructor private to stop people calling it, how do you do that on Objective-C?

Cheers

Andy


#12

Well one possible solution is to remove the init method from BNRItemStore, so instead we have:

[code]+(BNRItemStore *)sharedStore
{
static BNRItemStore *sharedStore = nil;

if(!sharedStore)
{
	sharedStore = [[super allocWithZone:nil] init];
	[sharedStore postInit];
}

return sharedStore;

}

+(id)allocWithZone:(NSZone *)zone
{
return [self sharedStore];
}

-(void)postInit
{
allItems = [[NSMutableArray alloc]init];
}
[/code]

Now the output is :

2013-02-25 08:22:50.706 Homepwner[3373:c07] Store1 = <BNRItemStore: 0xfc58b80> - (
    "Rusty Spork (8Q2U8): Worth $73, recorded on 2013-02-25 08:22:50 +0000",
    "Shiny Spork (5Y2V3): Worth $40, recorded on 2013-02-25 08:22:50 +0000"
)
2013-02-25 08:22:50.707 Homepwner[3373:c07] Store2 = <BNRItemStore: 0xfc58b80> - (
    "Rusty Spork (8Q2U8): Worth $73, recorded on 2013-02-25 08:22:50 +0000",
    "Shiny Spork (5Y2V3): Worth $40, recorded on 2013-02-25 08:22:50 +0000"
)

Of course the init in NSObject is still being called.


#13

Or this for a slightly nicer version maybe:

static BNRItemStore *sharedStore = nil;

+(BNRItemStore *)sharedStore
{
	if(!sharedStore)
		sharedStore = [[super allocWithZone:nil] init];
	
	return sharedStore;
}

+(id)allocWithZone:(NSZone *)zone
{
	return [self sharedStore];
}


-(id)init
{
	id result = sharedStore;
	
	if(!result)
	{
		self = [super init];
		if(self)
			allItems = [[NSMutableArray alloc]init];
		result = self;
	}
	
	return result;
}

#14

I had the same question about the init part of the equation. I discovered that the init will in fact reinitialize the array. I combined a test I saw elsewhere in this forum and added an NSLog

create a button and add it to the custom header. Create the IBAction in the ItemsViewController and implement the following:
if ([[BNRItemStore alloc] init] == [BNRItemStore sharedStore]) {
NSLog(@“Yep they’re equal!!!”);
}
else {
NSLog(@“Nope they’re NOT equal!!!”);
}

You will see that as expected, they’re equal. Now, in the BNRItemStore.m add the NSLog to the init method…
-(id)init
{
self = [super init];
if (self) {
allItems = [[NSMutableArray alloc]init];
NSLog(@"%p",allItems);
}
return self;
}

You will see that the array has a different memory address every time you press the button you created.

So, unless I’m missing something, allItems array gets initialized even though the sharedStore instance is the same.