关于ios:在Objective C中使用GCD的dispatch_once创建单例

Create singleton using GCD's dispatch_once in Objective C

如果您可以瞄准iOS 4.0或更高版本

使用GCD,这是在目标C(线程安全)中创建单例的最佳方法吗?

1
2
3
4
5
6
7
8
9
+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}


这是创建类实例的一种完全可接受且线程安全的方法。从技术上讲,它可能不是"单例"(因为这些对象中只能有1个),但只要您只使用[Foo sharedFoo]方法访问对象,这就足够好了。


实例化

instancetype只是Objective-C的许多语言扩展中的一个,每个新版本都添加了更多的语言扩展。

知道它,爱它。

把它作为一个例子,说明如何关注低层次的细节,可以让你洞察到转换Objective-C的强大的新方法。

请参阅此处:InstanceType

1
2
3
4
5
6
7
8
9
10
11
+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}
1
2
3
4
5
6
7
8
9
10
11
+ (Class*)sharedInstance
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}


米辛尔顿

1
2
3
4
5
6
7
8
9
10
@interface MySingleton : NSObject

+(instancetype)sharedInstance;

+(instancetype)alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
-(instancetype)init __attribute__((unavailable("init not available, call sharedInstance instead")));
+(instancetype)new __attribute__((unavailable("new not available, call sharedInstance instead")));
-(instancetype)copy __attribute__((unavailable("copy not available, call sharedInstance instead")));

@end

米辛尔顿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@implementation MySingleton

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

-(instancetype)initUniqueInstance {
    return [super init];
}

@end


您可以避免通过重写alloc方法来分配类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@implementation MyClass

static BOOL useinside = NO;
static id _sharedObject = nil;


+(id) alloc {
    if (!useinside) {
        @throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil];
    }
    else {
        return [super alloc];
    }
}

+(id)sharedInstance
{
    static dispatch_once_t p = 0;
    dispatch_once(&p, ^{
        useinside = YES;
        _sharedObject = [[MyClass alloc] init];
        useinside = NO;
    });  
    // returns the same object each time
    return _sharedObject;
}


戴夫是对的,那很好。您可能希望查看Apple关于创建singleton的文档,了解有关实现其他一些方法的提示,以确保如果类选择不使用sharedfoo方法,则只能创建一个方法。


如果要确保[[myclass alloc]in it]返回与sharedinstance相同的对象(在我看来,这不是必需的,但有些人希望如此),则可以非常轻松、安全地使用第二个分派u一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (instancetype)init
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        // Your normal init code goes here.
        sharedInstance = self;
    });

    return sharedInstance;
}

这允许[[MyClass alloc]init]和[MyClass sharedInstance]的任何组合返回相同的对象;[MyClass sharedInstance]只会更有效率。工作原理:【myclass sharedinstance】会调用一次[[myclass alloc]init]。其他代码也可以任意多次调用它。init的第一个调用程序将进行"正常"初始化,并将singleton对象存储在init方法中。稍后对init的任何调用都将完全忽略alloc返回的内容并返回相同的sharedInstance;alloc的结果将被释放。

+sharedInstance方法将一如既往地工作。如果它不是第一个调用[[myclass alloc]init]的调用方,那么init的结果不是alloc调用的结果,但这是可以的。


您会问这是否是"创建singleton的最佳方法"。

一些想法:

  • 首先,是的,这是一个线程安全的解决方案。这种dispatch_once模式是在objective-c中生成单例的现代、线程安全的方法,不用担心。

  • 但你问,这是否是最好的方法。不过,我们应该承认,当与单体结合使用时,instancetype[[self alloc] init]可能会产生误导。

    instancetype的好处在于,它是一种明确的方法,可以声明类可以在不使用id类型的情况下进行子类划分,就像我们在去年所做的那样。

    但这种方法中的static存在子类化的挑战。如果ImageCacheBlobCache单子都是Cache超类的子类,而没有实现自己的sharedCache方法呢?

    1
    2
    ImageCache *imageCache = [ImageCache sharedCache];  // fine
    BlobCache *blobCache = [BlobCache sharedCache];     // error; this will return the aforementioned ImageCache!!!

    为了实现这一点,您必须确保子类实现它们自己的sharedInstance方法(或者为您的特定类调用它的任何方法)。

    总之,您的原始sharedInstance看起来支持子类,但它不支持。如果您打算支持子类化,至少要包括警告未来开发人员必须重写此方法的文档。

  • 为了与swift实现最佳互操作性,您可能希望将其定义为属性,而不是类方法,例如:

    1
    2
    3
    @interface Foo : NSObject
    @property (class, readonly, strong) Foo *sharedFoo;
    @end

    然后您可以继续为此属性编写getter(实现将使用您建议的dispatch_once模式):

    1
    + (Foo *)sharedFoo { ... }

    这样做的好处是,如果一个快速用户使用它,他们会做如下的事情:

    1
    let foo = Foo.shared

    注意,没有(),因为我们将它作为一个属性实现。从Swift 3开始,这就是单例访问的一般方式。因此,将其定义为属性有助于促进互操作性。

    顺便说一句,如果你看看苹果如何定义他们的单件产品,这就是他们采用的模式,例如他们的NSURLSession单件产品定义如下:

    1
    @property (class, readonly, strong) NSURLSession *sharedSession;
  • 另一个非常微小的互操作性考虑因素是单例的名称。最好是您可以合并类型的名称,而不是sharedInstance。例如,如果类是Foo,则可以将singleton属性定义为sharedFoo。或者,如果类是DatabaseManager,您可以称为sharedManager属性。那么,Swift用户可以做到:

    1
    2
    let foo = Foo.shared
    let manager = DatabaseManager.shared

    显然,如果您真的想使用sharedInstance,您可以随时声明swift名称,如果您想:

    1
    @property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);

    显然,在编写Objective-C代码时,我们不应该让快速的互操作性超过其他设计考虑,但是,如果我们能够编写优雅地支持这两种语言的代码,那就更好了。

  • 我同意其他人的观点,他们指出,如果您希望这是一个真正的单一实例,开发人员不能/不应该(不小心)实例化自己的实例,那么initnew上的unavailable限定符是谨慎的。


  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //Create Singleton  
      +( instancetype )defaultDBManager
        {

            static dispatch_once_t onceToken = 0;
            __strong static id _sharedObject = nil;

            dispatch_once(&onceToken, ^{
                _sharedObject = [[self alloc] init];
            });

            return _sharedObject;
        }


    //In it method
    -(instancetype)init
    {
        self = [super init];
      if(self)
         {
       //Do your custom initialization
         }
         return self;
    }

    要创建线程安全的singleton,可以这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @interface SomeManager : NSObject
    + (id)sharedManager;
    @end

    /* thread safe */
    @implementation SomeManager

    static id sharedManager = nil;

    + (void)initialize {
        if (self == [SomeManager class]) {
            sharedManager = [[self alloc] init];
        }
    }

    + (id)sharedManager {
        return sharedManager;
    }
    @end

    这个博客很好地解释了objc/cocoa中的singleton。