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

#ios #objective-c #singleton #grand-central-dispatch

#iOS #objective-c #синглтон #grand-central-отправка

Вопрос:

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

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

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

Комментарии:

1. Есть ли способ запретить пользователям класса вызывать alloc / copy?

2. dispatch_once_t и dispatch_once, похоже, были введены в 4.0, а не 4.1 (см.: developer.apple.com/library/ios/#documentation/Performance /… )

3. Этот метод становится проблематичным, если инициализация требует использования объекта singleton. Код Мэтта Галлахера работал у меня более чем несколько раз. cocoawithlove.com/2008/11 /…

4. Я знаю, что в этом примере это несущественно; но почему люди больше не используют ‘new’. dispatch_once(amp;once, ^{sharedInstance=[self new];} просто выглядит немного аккуратнее. Это эквивалентно alloc init.

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

Ответ №1:

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

Комментарии:

1. Но как вы его выпускаете?

2. @samvermette ты этого не делаешь. смысл синглтона в том, что он всегда будет существовать. таким образом, вы не освобождаете его, и память освобождается при завершении процесса.

3. @Dave DeLong: На мой взгляд, целью создания singleton является не уверенность в его бессмертии, а уверенность в том, что у нас есть один экземпляр. Что, если этот синглтон уменьшит значение семафора? Вы не можете просто произвольно сказать, что он всегда будет существовать.

4. @hooleyhoop Да, в его документации . «При одновременном вызове из нескольких потоков эта функция синхронно ожидает завершения блока».

5. @WalterMartinVargas-Pena надежная ссылка хранится в статической переменной

Ответ №2:

instancetype

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

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

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

Смотрите здесь: instancetype


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

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

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

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

Комментарии:

1. отличный совет, спасибо! instancetype — это контекстное ключевое слово, которое может использоваться в качестве типа результата, чтобы сигнализировать, что метод возвращает связанный тип результата. … С помощью instancetype компилятор правильно определит тип.

2. Мне не ясно, что означают здесь два фрагмента, эквивалентны ли они друг другу? Один предпочтительнее другого? Было бы неплохо, если бы автор мог добавить немного объяснений по этому поводу.

Ответ №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(amp;pred, ^{
        shared = [[super alloc] initUniqueInstance];
    });
    return shared;
}

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

@end
  

Комментарии:

1. Почему инициализация недоступна? Разве это не доступно хотя бы для одного init ?

2. Синглтон должен иметь только одну точку доступа. И этот момент является sharedInstance. Если у нас есть метод init в файле *.h, вы можете создать другой экземпляр singleton. Это противоречит определению синглтона.

3. @asma22 __attribute__((недоступен())) делает недоступными для использования эти методы. Если другой программист хочет использовать метод, помеченный как недоступный, он получает сообщение об ошибке

4. Я полностью понимаю, и я рад, что узнал что-то новое, в вашем ответе нет ничего плохого, просто может быть немного запутанным для новичков…

5. Это работает только для MySingleton , например, в MySingleton.m я вызываю [super alloc]

Ответ №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(amp;p, ^{
        useinside = YES;
        _sharedObject = [[MyClass alloc] init];
        useinside = NO;
    });   
    // returns the same object each time
    return _sharedObject;
}
  

Комментарии:

1. Это отвечает на мой вопрос в комментариях выше. Не то чтобы я так уж сильно за защитное программирование, но…

Ответ №5:

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

Комментарии:

1. эх … это не лучший пример создания синглтона. Переопределять методы управления памятью не обязательно.

2. Это полностью недопустимо при использовании ARC.

3. С тех пор цитируемый документ был удален. Кроме того, ответы, которые являются исключительно ссылками на внешний контент, как правило, являются плохими ответами SO. Как минимум, выделите соответствующие части в своем ответе. Не беспокойтесь здесь, если вы не хотите сохранить старый способ для потомков.

Ответ №6:

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

 - (instancetype)init
{
    static dispatch_once_t once;
    static Class *sharedInstance;

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

    return sharedInstance;
}
  

Это позволяет любой комбинации [[MyClass alloc] init] и [MyClass sharedInstance] возвращать один и тот же объект; [MyClass sharedInstance] было бы просто немного эффективнее. Как это работает: [MyClass sharedInstance] вызовет [[MyClass alloc] init] один раз. Другой код также может вызывать его любое количество раз. Первый вызывающий инициализацию выполнит «обычную» инициализацию и сохранит объект singleton в методе init. Любые последующие вызовы init будут полностью игнорировать то, что вернул alloc, и возвращать тот же sharedInstance; результат alloc будет освобожден.

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

Ответ №7:

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

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

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

  2. Однако вы спросили, является ли это «лучшим» способом сделать это. Однако следует признать, что 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 выглядит так, как будто он будет поддерживать подклассы, но это не так. Если вы намерены поддерживать подклассы, по крайней мере, включите документацию, которая предупреждает будущих разработчиков, что они должны переопределить этот метод.

  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. Другим, очень незначительным соображением, касающимся совместимости Swift, было имя синглтона. Лучше всего, если вы можете включить имя типа, а не 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 перевешивать другие соображения дизайна, но все же, если мы можем написать код, который изящно поддерживает оба языка, это предпочтительнее.

  5. Я согласен с другими, которые указывают, что если вы хотите, чтобы это был настоящий синглтон, в котором разработчики не могут / не должны (случайно) создавать свои собственные экземпляры, 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. синглтоны в objc / cocoa

Комментарии:

1. вы ссылаетесь на очень старую статью, в то время как OP запрашивает характеристики самой современной реализации.

2. Вопрос касается конкретной реализации. Вы просто публикуете другую реализацию. Здесь вы даже не пытаетесь ответить на вопрос.

3. @vikingosegundo Спрашивающий спрашивает о погоде, GCD — лучший способ создать потокобезопасный синглтон, мой ответ дает другой выбор. Что-то не так?

4. запрашивающий спрашивает, является ли определенная реализация потокобезопасной. он не запрашивает параметры.

Ответ №9:

 @interface className : NSObject{
 (className*)SingleTonShare;
}

@implementation className

 (className*)SingleTonShare{

static className* sharedObj = nil;
static dispatch_once_t once = 0;
dispatch_once(amp;once, ^{

if (sharedObj == nil){
    sharedObj = [[className alloc] init];
}
  });
     return sharedObj;
}
  

Ответ №10:

 //Create Singleton  
   ( instancetype )defaultDBManager
    {

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

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

        return _sharedObject;
    }


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