Создайте синглтон, используя отправку GCD один раз в 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;
}
9 336

9 ответов:

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

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

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.м

@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

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

вы можете избежать того, что класс будет выделен с перезаписью метода 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;
}

Если вы хотите убедиться, что [[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 выполнит" нормальную " инициализацию и сохранит одноэлементный объект в методе init. Любые последующие вызовы init будут полностью игнорировать то, что alloc вернул, и возвращать тот же sharedInstance; результат alloc будет освобожден.

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

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

@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

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

вы спросите, является ли это "лучший способ создать синглтон".

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

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

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

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

    но static в этом методе представлены проблемы подклассов. А что если ImageCache и BlobCache синглтоны были оба подкласса от A Cache суперкласс без реализации собственных sharedCache способ?

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

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

    итог, ваш оригинал sharedInstanceвыглядит

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

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

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

    + (Foo *)sharedFoo { ... }
    

    преимущество этого заключается в том, что если пользователь Swift пойдет использовать его, они сделают что-то вроде:

    let foo = Foo.shared
    

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

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

    @property (class, readonly, strong) NSURLSession *sharedSession;
    
  4. еще одним, очень незначительным соображением быстрой совместимости было имя синглтона. Будет лучше, если вы можете включить имя типа, а не 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 мы не должны позволять быстрой совместимости перевешивать другие соображения дизайна, но все же, если мы можем написать код, который изящно поддерживает оба языка, это предпочтительнее.

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