Создать синглтон с помощью GCD dispatch_once в Objective-C

Если вы можете ориентироваться на iOS 4.0 или выше

Используя GCD, это лучший способ создать синглтон в Objective-C (потокобезопасный)?

+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

Ответ 1

Это совершенно приемлемый и потокобезопасный способ создания экземпляра вашего класса. Он не может быть технически "одиночным" (в нем может быть только один из этих объектов), но до тех пор, пока вы используете метод [Foo sharedFoo] для доступа к объекту, это достаточно хорошо.

Ответ 2

instancetype

instancetype является одним из множества расширений языка для Objective-C, причем с каждым новым выпуском добавляется больше.

Знай, люби его.

И рассмотрим это как пример того, как внимание к деталям на низком уровне может дать вам представление о мощных новых способах преобразования Objective-C.

См. здесь: instancetype


+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;

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

+ (Class*)sharedInstance
{
    static dispatch_once_t once;
    static Class *sharedInstance;

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

Ответ 3

MySingleton.h

@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

MySingleton.m

@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

Ответ 4

Вы можете избежать выделения класса с перезаписи метода alloc.

@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;
}

Ответ 5

Дейв прав, это прекрасно. Вы можете проверить Документы Apple по созданию одноэлементного для советов по реализации некоторых других методов, чтобы гарантировать, что только один может быть создан, если классы выбирают НЕ использовать метод sharedFoo.

Ответ 6

Если вы хотите убедиться, что [[MyClass alloc] init] возвращает тот же объект, что и sharedInstance (по моему мнению, это не обязательно, но некоторые люди этого хотят), это можно сделать очень легко и безопасно, используя второй dispatch_once:

- (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 полностью игнорируют возвращаемое значение и возвращают тот же sharedInstance; результат alloc будет освобожден.

Метод + sharedInstance будет работать так, как он всегда делал. Если первый вызывающий абонент не вызвал [[MyClass alloc] init], результат init не является результатом вызова alloc, но это нормально.

Ответ 7

Вы спрашиваете, является ли это "лучшим способом создания singleton".

Несколько мыслей:

  • Во-первых, да, это поточно-безопасное решение. Этот шаблон dispatch_once - это современный, потокобезопасный способ генерации одиночных чисел в Objective-C. Не беспокойтесь.

  • Вы спросили, действительно ли это "лучший" способ сделать это. Однако следует признать, что instancetype и [[self alloc] init] потенциально вводят в заблуждение при использовании в сочетании с одноточечными.

    Преимущество instancetype заключается в том, что это однозначный способ объявить, что класс может быть подклассифицирован, не прибегая к типу id, как мы должны были делать в прошлом.

    Но static в этом методе представляет проблемы подкласса. Что делать, если синглтоны ImageCache и BlobCache были оба подклассами из суперкласса Cache без реализации собственного метода sharedCache?

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

    Чтобы это сработало, вам нужно убедиться, что подклассы реализуют свой собственный метод sharedInstance (или то, что вы называете его для вашего конкретного класса).

    В нижней строке, ваш оригинальный sharedInstance выглядит так, что будет поддерживать подклассы, но это не так. Если вы намерены поддерживать подклассу, по крайней мере, включайте документацию, которая предупреждает будущих разработчиков о том, что они должны переопределить этот метод.

  • Для лучшей совместимости с Swift вы, вероятно, хотите определить это как свойство, а не метод класса, например:

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

    Затем вы можете пойти и написать getter для этого свойства (реализация будет использовать шаблон dispatch_once, который вы предложили):

    + (Foo *)sharedFoo { ... }
    

    Преимущество этого в том, что если пользователь Swift использует его, они бы сделали что-то вроде:

    let foo = Foo.shared
    

    Обратите внимание: нет (), потому что мы реализовали его как свойство. Начиная с Swift 3, это то, как в общем доступе к синглонам. Поэтому определение его как свойства помогает облегчить взаимодействие.

    В стороне, если вы посмотрите, как Apple определяет свои синглтоны, это шаблон, который они приняли, например. их NSURLSession singleton определяется следующим образом:

    @property (class, readonly, strong) NSURLSession *sharedSession;
    
  • Другим, очень незначительным соображением совместимости Swift было название singleton. Лучше всего, если вы можете включить имя типа, а не sharedInstance. Например, если класс был Foo, вы можете определить свойство singleton как sharedFoo. Или, если класс был DatabaseManager, вы можете вызвать свойство sharedManager. Тогда пользователи Swift могли бы делать:

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

    Очевидно, что если вы действительно хотите использовать sharedInstance, вы всегда можете объявить имя Swift, если хотите:

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

    Очевидно, что при написании кода Objective-C мы не должны допускать, чтобы совместимость Swift перевешивала другие конструктивные соображения, но все же, если мы можем написать код, который изящно поддерживает оба языка, это предпочтительнее.

  • Я согласен с другими, которые отмечают, что если вы хотите, чтобы это был настоящий синглтон, где разработчики cant/shouldnt (случайно) создавали экземпляры своих собственных экземпляров, квалификатор unavailable на init и new благоразумный.

Ответ 8

Чтобы создать потокобезопасный синглтон, вы можете сделать следующее:

@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

Ответ 9

//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;
}