有人可以帮我理解以下方法的作用吗?
+ (Game *) shared
{
static Game *sharedSingleton;
@synchronized(self)
{
if (!sharedSingleton)
{
sharedSingleton = [[Game alloc] init];
}
}
return sharedSingleton;
}
答案 0 :(得分:46)
显然,单身人士背后的想法是只创建一个实例。实现此目标的第一步是通过行static Game *sharedSingleton;
声明类的静态实例。
第二步是检查是否已经创建了单个实例,如果不是,则创建它,或者如果是,则返回现有的单个实例。但是,如果2个单独的线程尝试在同一时刻调用+shared
方法,则第二步可能会出现问题。您不希望一个线程修改单个sharedSingleton
变量,而另一个线程正在尝试检查它,因为它可能会产生意外结果。
此问题的解决方案是使用@synchronized()
编译器指令来同步对括号之间指定的对象的访问。例如,假设Game
类的这个单个共享实例有一个名为players
的实例变量,它是NSMutableArray
类的Player
个实例。假设Game
类有一个-addPlayer:
方法,它可以通过添加指定的播放器来修改players
实例变量。重要的是,如果从多个线程调用该方法,则一次只允许一个线程修改players
数组。因此,该方法的实现可能如下所示:
- (void)addPlayer:(Player *)player {
if (player == nil) return;
@synchronized(players) {
[players addObject:player];
}
}
使用@synchronized()
指令确保一次只有一个线程可以访问players
变量。如果一个线程在另一个线程当前正在访问它时尝试,则第一个线程必须等到另一个线程完成。
虽然当你谈论实例变量时它更直接,但是如何在类本身的单个创建方法中实现相同类型的结果可能不太清楚。以下代码中self
行中的@synchronized(self)
基本上等同于Game
类本身。通过在Game
类上进行同步,可以确保sharedSingleton = [[Game alloc] init];
行只被调用一次。
+ (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;
}
[编辑]:更新。基于我的测试一段时间(我现在只是重新测试),以下所有看起来都是等价的:
外@implementation
:
Game *sharedInstance;
@implementation Game
+ (Game *)sharedGame {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[[self class] alloc] init];
}
}
return sharedInstance;
}
@end
外@implementation
,static
:
static Game *sharedInstance;
@implementation Game
+ (Game *)sharedGame {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[[self class] alloc] init];
}
}
return sharedInstance;
}
@end
内部@implementation
:
@implementation Game
static Game *sharedInstance;
+ (Game *)sharedGame {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[[self class] alloc] init];
}
}
return sharedInstance;
}
@end
内部+sharedGame
:
@implementation Game
+ (Game *)sharedGame {
static Game *sharedInstance;
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[[self class] alloc] init];
}
}
return sharedInstance;
}
@end
唯一的区别是,在第一个版本中,如果没有static
关键字,sharedInstance
不会显示在gdb
中的文件静态下。很明显,在上一个版本中,sharedInstance
在+sharedGame
方法之外是不可见的。但实际上,他们都确保当你致电[Game sharedInstance]
时,你会回到sharedInstance
,而sharedInstance
只会被创建一次。 (但请注意,需要采取进一步的预防措施,以防止某人使用Game *game = [[Game alloc] init];
等内容创建非单例实例。
答案 1 :(得分:9)
逐行说明......
// A static variable guarantees there's 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's 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,
// e.g. 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;
}
答案 2 :(得分:3)
我不太了解Objective-C,但它似乎是一种获取Game *类型的singleton副本的方法,目的是thread-safe。
基本上调用该方法将每次返回相同的Game *副本(无论哪个线程),并且在第一次调用它之前不会分配新的Game实例(称为lazy-loading)< / p>
答案 3 :(得分:3)
我还会覆盖'mutableCopyWithZone:'和'copyWithZone:'方法,以避免单例被复制:
// copy cannot be done
- (Game *)copyWithZone:(NSZone *)zone {
return self;
}
// mutablecopy cannot be done
- (Game *)mutableCopyWithZone:(NSZone *)zone {
return [self copyWithZone:zone];
}
由于'copy'和'mutableCopy'(假设单例类不从另一个已经覆盖默认NSObject实现的类继承),只需调用'copyWithZone:'和'mutableCopyWithZone:',也不需要覆盖它们
为了避免其他开发人员使用'init'或'new'打破单例模式,可以将这两种方法声明为不可用:
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
通过这种方式,编译器将不允许使用这两种方法,并且其他开发人员将被迫使用记录的方法来获取共享实例。 另一个更严格的技术是覆盖'init'和'new'并引发异常。