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


341

Если вы можете ориентироваться на 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;
}

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

3
dispatch_once_t и dispatch_once, кажется, были введены в 4.0, а не в 4.1 (см. developer.apple.com/library/ios/#documentation/Performance/… )
Бен Флинн,

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

1
Я знаю, что это несущественно в этом примере; но почему люди не используют «новое» больше? dispatch_once (& Once, ^ {sharedInstance = [self new];} просто выглядит немного точнее. Это эквивалентно alloc + init.
Крис Хаттон,

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

Ответы:


215

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


4
Как вы это отпустите?
Самверметт

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

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

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

3
@ WalterMartinVargas-Pena сильная ссылка поддерживается статической переменной
Дейв Делонг

36

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 компилятор будет правильно выводить тип.
Толстяк

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

33

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

Как инициализация недоступна? Разве это не доступно хотя бы для одного init?
Мед

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

1
@ asma22 __attribute __ ((unavailable ()) делает недоступными для использования эти методы. Если другой программист хочет использовать метод, помеченный как недоступный, он получает ошибку
Сергей Петрук

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

1
Это работает только для MySingleton, например, MySingleton.mя звоню[super alloc]
Сергей Петрук

6

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

5

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


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

19
Это совершенно неверно при использовании ARC.
logancautrell

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

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

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


2

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

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

  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разумен.


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


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

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

1
@vikingosegundo Задать вопрос о том, является ли GCD лучшим способом создания синглтона с безопасным потоком, мой ответ дает другой выбор.
Hancock_Xu

спрашивающий спрашивает, является ли определенная реализация поточно-ориентированной. он не просит вариантов.
Викингосегундо

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 className : NSObject{
+(className*)SingleTonShare;
}

@implementation className

+(className*)SingleTonShare{

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

if (sharedObj == nil){
    sharedObj = [[className alloc] init];
}
  });
     return sharedObj;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.