Подтвердить что ты не робот

Правильная цель Singleton Pattern C (iOS)?

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

@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 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

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

Приветствия Стефан

4b9b3361

Ответ 1

Держите его простым:

+(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) продолжают поддерживать режим singleton, а также поддерживают произвольное создание экземпляра.

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

Ответ 2

// 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().

Метод singleton factory всегда возвращает один из этих трех типов:

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

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

#define coreDataManager() \
        ((AppDelegate*)[[UIApplication sharedApplication] delegate]).coreDataManager

Ответ 3

Если вы хотите unit test ваш синглтон, вы также должны сделать это так, чтобы вы могли заменить его макетным синглетом и/или reset на обычный:

@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