Создать синглтон, используя dispatch_once в GCD в Objective C

349

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

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

+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
  • 2
    Есть ли способ запретить пользователям класса вызывать alloc / copy?
  • 0
    @ranReloaded: вы можете определить метод класса для alloc и copy, который просто возвращает синглтон или что-то вроде этого.
Показать ещё 10 комментариев
Теги:
singleton
grand-central-dispatch

9 ответов

219
Лучший ответ

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

  • 4
    Как вы это отпустите?
  • 65
    @ samvermette нет. Дело в том, что синглтон всегда будет существовать. таким образом, вы не освобождаете его, и память возвращается после завершения процесса.
Показать ещё 7 комментариев
35

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;
}
  • 4
    потрясающий совет, спасибо! instancetype - это контекстное ключевое слово, которое можно использовать в качестве типа результата, чтобы указать, что метод возвращает связанный тип результата. ... С instancetype компилятор будет правильно выводить тип.
31

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
  • 0
    Как инициализация недоступна? Разве это не доступно хотя бы для одного init ?
  • 0
    @ asma22, если у вас есть метод init в файле * .h, у вас будет что-то вроде синглтона, но это не синглтон, потому что у синглтона должна быть одна точка доступа
Показать ещё 8 комментариев
6

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

  • 8
    эх ... это не самый лучший пример создания синглтона. Переопределять методы управления памятью не обязательно.
  • 19
    Это совершенно неверно при использовании ARC.
Показать ещё 1 комментарий
5

Вы можете избежать выделения класса с перезаписи метода 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;
}
  • 1
    Это отвечает на мой вопрос в комментариях выше. Не то чтобы я так за защитное программирование, но ...
4

Если вы хотите убедиться, что [[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, но это нормально.

0

Вы спрашиваете, является ли это "лучшим способом создания 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 благоразумный.

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

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

@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

  • 0
    вы ссылаетесь на очень старую статью, в то время как OP запрашивает характеристики о самой современной реализации.
  • 0
    @vikingosegundo да, статья старая, но она действительно работает.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню