Help understand the class method returning singleton

Can someone help me understand what the following method does?

+ (Game *) shared { static Game *sharedSingleton; @synchronized(self) { if (!sharedSingleton) { sharedSingleton = [[Game alloc] init]; } } return sharedSingleton; } 
+10
ios objective-c cocoa-touch singleton singleton-methods
Jun 04 2018-11-11T00:
source share
4 answers

Obviously, the idea of ​​a single element is to create only one instance. The first step in achieving this is to declare a static instance of the class through the string static Game *sharedSingleton; .

The second step is to check whether one instance has already been created, and if not, create it or return it to return an existing separate instance. However, this second step opens up potential problems if two separate threads try to call the +shared method at the same time. You would not want one thread to modify a single sharedSingleton variable, while another thread is trying to examine it, as this may lead to unexpected results.

The solution to this problem is to use the @synchronized() compiler directive to synchronize access to the object indicated in parentheses. For example, let's say that this single generic instance of the Game class has an instance variable called players , which is an NSMutableArray instances of the Player class. Let's say the Game class had the -addPlayer: method, which would modify the players instance variable by adding the specified player. It is important that if this method were called from several threads, then only one thread was allowed to modify the players array at a time. Thus, the implementation of this method may look something like this:

 - (void)addPlayer:(Player *)player { if (player == nil) return; @synchronized(players) { [players addObject:player]; } } 

Using the @synchronized() directive ensures that only one thread can access the players variable at a time. If one thread is trying to use another thread at the moment, the first thread must wait for the other thread to finish.

Although this is more straightforward when you talk about an instance variable, it may be less clear how to achieve the same result in the same method of creating the class itself. The line self in the line @synchronized(self) in the following code is basically equal to the class Game . By synchronizing with the Game class, it ensures that the string sharedSingleton = [[Game alloc] init]; only called once.

 + (Game *) shared { static Game *sharedSingleton; @synchronized(self) // assures only one thread can call [Game shared] at a time { if (!sharedSingleton) { sharedSingleton = [[Game alloc] init]; } } return sharedSingleton; } 

[EDIT]: updated. Based on my tests some time ago (and I just re-tested it now), they all look equivalent:

External @implementation :

 Game *sharedInstance; @implementation Game + (Game *)sharedGame { @synchronized(self) { if (sharedInstance == nil) { sharedInstance = [[[self class] alloc] init]; } } return sharedInstance; } @end 

Outside of @implementation , static :

 static Game *sharedInstance; @implementation Game + (Game *)sharedGame { @synchronized(self) { if (sharedInstance == nil) { sharedInstance = [[[self class] alloc] init]; } } return sharedInstance; } @end 

Inside @implementation :

 @implementation Game static Game *sharedInstance; + (Game *)sharedGame { @synchronized(self) { if (sharedInstance == nil) { sharedInstance = [[[self class] alloc] init]; } } return sharedInstance; } @end 

Inside +sharedGame :

 @implementation Game + (Game *)sharedGame { static Game *sharedInstance; @synchronized(self) { if (sharedInstance == nil) { sharedInstance = [[[self class] alloc] init]; } } return sharedInstance; } @end 

The only difference is that in the first version, without the static , sharedInstance not displayed in the "File statistics" section in gdb . And, obviously, in the latter version, sharedInstance not displayed outside the +sharedGame method. But almost all of them claim that when you call [Game sharedInstance] , you will return sharedInstance and that sharedInstance is created only once. (Note, however, that further precautions are needed so that someone cannot create an instance other than a single one using something like Game *game = [[Game alloc] init]; ).

+44
Jun 04 2018-11-11T00:
source share
— -

Step by step explanation ...

 // A static variable guarantees there only 1 instance of it ever, // even accross multiple instances of the same class, this particular // variable will store the class instance, so it can be returned whenever // a client-class requests an instance of this class. static Game *sharedSingleton; // create a method that can always be called, even if there no instance yet // this method should create a new instance if there isn't one yet, otherwise // return the existing instance + (Game *) shared { // synchronized makes sure only 1 client class can enter this method at any time, // eg to prevent creating 2 instances whenever 2 client-classes try to // access the following code from different threads. @synchronized(self) { // if the static variable is called for the first time, // create an instance and return it, otherwise return the existing instance ... if (!sharedSingleton) { sharedSingleton = [[Game alloc] init]; } } return sharedSingleton; } 
+9
Jun 04 '11 at 7:50
source share

I know little of Objective-C, but it seems to be a method of getting a singleton copy of type Game * and should be thread safe .

Essentially, calling this method will return the same copy of the game * each time (no matter which thread), and will not allocate a new instance of the game until it is called (known as lazy-loading )

+3
Jun 04 2018-11-11T00:
source share

I would also override mutableCopyWithZone: and copyWithZone: methods to avoid singleton copying:

 // copy cannot be done - (Game *)copyWithZone:(NSZone *)zone { return self; } // mutablecopy cannot be done - (Game *)mutableCopyWithZone:(NSZone *)zone { return [self copyWithZone:zone]; } 

Since copy and mutableCopy (assuming that the singleton class does not inherit from another class that has overridden NSObject implementations by default), just call copyWithZone: and mutableCopyWithZone: do not need to override them either.

To prevent other developers from breaking a singleton pattern with init or new, two methods may be declared inaccessible:

 - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; 

Thus, the compiler will not allow the use of two methods, and other developers will be forced to use a documented method to obtain a common instance. Another, more rigorous method, is to override init and new and throw an exception.

+2
Feb 27 '17 at 0:44
source share



All Articles