Вот то, что я придумал после детально изучения одноэлементной литературы. Я забыл что-нибудь?
@implementation MySingleton
static MySingleton *mySharedInstance = nil;
//called by atexit on exit, to ensure all resources are freed properly (not just memory)
static void singleton_remover()
{
//free resources here
}
+ (MySingleton*) sharedInstance{
return mySharedInstance;
}
+ (void)initialize {
if (self == [MySingleton class]) {
mySharedInstance = [[super allocWithZone:NULL] init];
atexit( singleton_remover );
}
}
+ (id)allocWithZone:(NSZone *)zone
{
return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain
{
return self;
}
- (NSUInteger)retainCount
{
return NSUIntegerMax; //denotes an object that cannot be released
}
- (void)release
{
//do nothing
}
- (id)autorelease
{
return self;
}
который избегает блокировки синхронизации в большинстве случаев
Если вы хотите, чтобы ваше программное обеспечение было надежным, избегайте конструкций, которые работают "больше всего времени "
Таблица 4.1. Блокировка с двойной проверкой
Блокировка с двойной проверкой - это попытка уменьшить накладные расходы, связанные с взятием блокировки, путем проверки критериев блокировки до взятия блокировки. Поскольку блокировки с двойной проверкой потенциально небезопасны, система не обеспечивает их явной поддержки, и их использование не рекомендуется.
Несколько предложений (больше для Mac Cocoa, чем для iPhone, но они могут быть полезны другим людям, выполняющим поиск по тегу objective-c):
Еще один забавный паттерн:
+ (Foo *)sharedFoo {
static Foo *sharedInstance = NULL;
if (!sharedInstance) {
Foo *temp = [[Foo alloc] init]; //NOTE: This MUST NOT have side effects for it to be threadsafe
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &sharedInstance)) {
[temp release];
}
}
return sharedInstance;
}
Функция singleton_remover
ничего не сделает, потому что вы переопределили выпуск
, чтобы ничего не делать. Ваш метод allocWithZone:
также выполняет аналогичное бездействие, когда отправляет keep
совместно используемому экземпляру (и полностью игнорирует выделение в указанной зоне). Возможно, у вас должен быть флаг, который переключает, является ли ваш общий экземпляр непобедимым (то есть невыполнимым) или нет.
В любом случае ОС все равно очистит всю память. В документации указано, что для ОС быстрее просто освободить всю память сразу, чем для вашего приложения, чтобы медленно возвращать ее по частям.
Если ваш общий экземпляр управляет ресурсами, которые всегда необходимо очищать после завершения работы вашего приложения, вы должны зарегистрировать его для получения UIApplicationWillTerminateNotification
и выполнить очистку там. .
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(performCleanup:)
name:UIApplicationWillTerminateNotification
object:nil];
Ваша реализация потокобезопасна и, похоже, покрывает все основы (+initialize отправляется потокобезопасной средой исполнения)
edit: Много кода будет небезопасно вызывать во время atexit
функции. Регистрация UIApplicationWillTerminateNotification
в главном потоке более безопасна.
edit2: Я переработал и усовершенствовал используемый мной паттерн в макрос. -init
вызывается один раз при первом вызове +sharedInstance
, а -dealloc
будет вызываться при завершении приложения.
#define IMPLEMENT_UIAPP_SINGLETON(class_name) \
static class_name *shared ## class_name; \
+ (void)cleanupFromTerminate \
{ \
class_name *temp = shared ## class_name; \
shared ## class_name = nil; \
[temp dealloc]; \
} \
+ (void)registerForCleanup \
{ \
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanupFromTerminate) name:UIApplicationWillTerminateNotification object:nil]; \
} \
+ (void)initialize { \
if (self == [class_name class]) { \
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \
if ([NSThread isMainThread]) \
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanupFromTerminate) name:UIApplicationWillTerminateNotification object:nil]; \
else \
[self performSelectorOnMainThread:@selector(registerForCleanup) withObject:nil waitUntilDone:NO]; \
shared ## class_name = [[super allocWithZone:NULL] init]; \
[pool drain]; \
} \
} \
+ (class_name *)sharedInstance \
{ \
return shared ## class_name; \
} \
+ (id)allocWithZone:(NSZone *)zone \
{ \
return shared ## class_name; \
} \
- (id)copyWithZone:(NSZone *)zone \
{ \
return self; \
} \
- (id)retain \
{ \
return self; \
} \
- (NSUInteger)retainCount \
{ \
return NSUIntegerMax; \
} \
- (void)release \
{ \
} \
- (id)autorelease \
{ \
return self; \
}
РЕДАКТИРОВАТЬ
Я включаю это вверху, ниже вы можете увидеть мои исторические исходные вопросы и реализацию. Однако я думаю, что нашел оптимальный способ предоставить метод sharedInstance без накладных расходов на блокировку, я хотел бы услышать потенциальные опасения по этому поводу:
// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;
// There's no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
return (MySingleton *)sharedInstance;
}
+ (MySingleton*)sharedInstance
{
@synchronized(self)
{
if (sharedInstance == nil)
{
sharedInstance = [[MySingleton alloc] init];
// Replace expensive thread-safe method
// with the simpler one that just returns the allocated instance.
SEL orig = @selector(sharedInstance);
SEL new = @selector(simpleSharedInstance);
Method origMethod = class_getClassMethod(self, orig);
Method newMethod = class_getClassMethod(self, new);
method_exchangeImplementations(origMethod, newMethod);
}
}
return (MySingleton *)sharedInstance;
}
И историческое обсуждение инициализации:
Теперь я вижу, что исходный код на самом деле был довольно как у меня (ниже), за исключением проверки экземпляра за пределами блокировки.
Хотя новый метод инициализации + (void) интересен, я не уверен, что он мне больше нравится. Похоже, что теперь для получения экземпляра синглтона вы должны всегда вызывать:
MySingleton instance = [[MySingleton alloc] init];
Разве это не правильно? Это кажется странным, и будет ли это более эффективным, если вызов для инициализации уже заблокирован для вас? Метод двойной блокировки, кажется, работает нормально для этого варианта использования, но при этом избегает блокировки (я думаю, это может быть связано с потенциальной ценой двойного распределения, поскольку более одного потока могут пройти через if).
Еще одна вещь, которая кажется странной, если, если метод инициализации действительно был предпочтительнее, почему мы не видим его в другом месте? Objective-C существует уже давно, и я с осторожностью отношусь к фундаментальным механизмам, которые отличаются практически от всех опубликованных примеров.
Мой код, который я использую в настоящее время (который отражает то, что я видел в других местах, включая этот ответ):
+ (MySingleton *)sharedInstance
{
@synchronized(self)
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone
{
@synchronized(self)
{
if (sharedInstance == nil)
{
sharedInstance = [super allocWithZone:zone];
return sharedInstance; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}