Правильно ли задание синглтон-паттерна C (iOS)? - PullRequest
29 голосов
/ 29 сентября 2011

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

@implementation MySingleton

// MARK: -
// MARK: Singleton Pattern using GCD

+ (id)allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; }
- (id)copyWithZone:(NSZone *)zone { return self; }
- (id)autorelease { return self; }
- (oneway void)release { /* Singletons can't be released */ }
- (void)dealloc { [super dealloc]; /* should never be called */ }
- (id)retain { return self; }
- (NSUInteger)retainCount { return NSUIntegerMax; /* That's soooo non-zero */ }

+ (MySingleton *)sharedInstance
{
    static MySingleton * instance = nil;

    static dispatch_once_t predicate;   
    dispatch_once(&predicate, ^{
        // --- call to super avoids a deadlock with the above allocWithZone
        instance = [[super allocWithZone:nil] init];
    });

    return instance;
}

// MARK: -
// MARK: Initialization

- (id)init
{
    self = [super init];
    if (self) 
    {
        // Initialization code here.
    }
    return self;
}

@end

Пожалуйста, не стесняйтесь комментировать и сообщать мне, если я что-то упустил или что-то делаю совершенно неправильно;)

Приветствия Stefan

Ответы [ 3 ]

82 голосов
/ 29 сентября 2011

Проще говоря:

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

- (void)dealloc
{
    // implement -dealloc & remove abort() when refactoring for
    // non-singleton use.
    abort();
}

Вот и все.Переопределение retain, release, retainCount, а остальное просто скрывает ошибки и добавляет кучу ненужного кода.Каждая строка кода - это ошибка, ожидающая своего появления.В действительности, если вы вызываете dealloc для вашего общего экземпляра, у вас есть очень серьезная ошибка в вашем приложении .Эта ошибка должна быть исправлена, а не скрыта.

Этот подход также поддается рефакторингу для поддержки не-одиночных режимов использования.Практически каждый синглтон, выживший после нескольких выпусков, в конечном итоге будет преобразован в не-синглтонную форму.Некоторые (например, NSFileManager) продолжают поддерживать одноэлементный режим, в то же время поддерживая произвольную реализацию.

Обратите внимание, что вышеприведенное также "просто работает" в ARC.

19 голосов
/ 29 сентября 2011
// See Mike Ash "Care and Feeding of Singletons"
// See Cocoa Samurai "Singletons: You're doing them wrong"
+(MySingleton *)singleton {
    static dispatch_once_t pred;
    static MySingleton *shared = nil;
    dispatch_once(&pred, ^{
        shared = [[MySingleton alloc] init];
        shared.someIvar = @"blah";
    });
    return shared;
}

Имейте в виду, что dispatch_once не является повторно входящим , поэтому вызов себя изнутри блока dispatch_once приведет к блокировке программы.

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

Не беспокойтесь об использовании allocWithZone:

  • Он игнорирует свои аргументы и ведет себя точно так же, как alloc.Зоны памяти больше не используются в Objective-C, поэтому allocWithZone: сохраняется только для совместимости со старым кодом.
  • Это не работает.Вы не можете применить одноэлементное поведение в Objective-C, потому что всегда можно создать больше экземпляров, используя NSAllocateObject() и class_createInstance().

Метод фабрики-одиночки всегда возвращает один из этих трех типов:

  • id, чтобы указать, что возвращаемый тип не полностью известен (случай, когда вы создаете классcluster).
  • instancetype, чтобы указать, что возвращаемый тип является экземпляром включающего класса.
  • Само имя класса (MySingleton в примере), чтобы упростить его.

Поскольку вы пометили эту iOS, альтернативой синглтону является сохранение ивара в приложение.делегировать, а затем использовать вспомогательный макрос, который вы можете переопределить, если передумаете:

#define coreDataManager() \
        ((AppDelegate*)[[UIApplication sharedApplication] delegate]).coreDataManager
1 голос
/ 25 января 2013

Если вы хотите провести модульное тестирование вашего синглтона, вы также должны сделать его так, чтобы вы могли заменить его на пробный синглтон и / или сбросить его на обычный:

@implementation ArticleManager

static ArticleManager *_sharedInstance = nil;
static dispatch_once_t once_token = 0;

+(ArticleManager *)sharedInstance {
    dispatch_once(&once_token, ^{
        if (_sharedInstance == nil) {
            _sharedInstance = [[ArticleManager alloc] init];
        }
    });
    return _sharedInstance;
}

+(void)setSharedInstance:(ArticleManager *)instance {
    once_token = 0; // resets the once_token so dispatch_once will run again
    _sharedInstance = instance;
}

@end
...