Правильная цель 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
Пожалуйста, не стесняйтесь комментировать и говорить мне, если я что-то упустил или что-то совершенно не так;)
Приветствия
Стефан
Ответы
Ответ 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