Действительно ли это - категорическое касательно считаемой одноэлементной реализации Objective C?

Вот то, что я придумал после детально изучения одноэлементной литературы. Я забыл что-нибудь?

@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;    
}
6
задан Jacko 19 February 2010 в 14:36
поделиться

5 ответов

который избегает блокировки синхронизации в большинстве случаев

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

http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html

Таблица 4.1. Блокировка с двойной проверкой

Блокировка с двойной проверкой - это попытка уменьшить накладные расходы, связанные с взятием блокировки, путем проверки критериев блокировки до взятия блокировки. Поскольку блокировки с двойной проверкой потенциально небезопасны, система не обеспечивает их явной поддержки, и их использование не рекомендуется.

2
ответ дан 17 December 2019 в 18:15
поделиться

Несколько предложений (больше для Mac Cocoa, чем для iPhone, но они могут быть полезны другим людям, выполняющим поиск по тегу objective-c):

  • Don ' Не беспокойтесь с -allocWithZone: NULL, просто -alloc подойдет.
  • Рассмотрите возможность использования dispatch_once () или pthread_once () там, где это возможно.
  • Использование atexit разумно, но может быть несовместимо с быстрым завершением работы приложения (не уверен, что это применимо к iPhone), поскольку это эффективно убивает -9s приложения

Еще один забавный паттерн:

+ (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;
}
1
ответ дан 17 December 2019 в 18:15
поделиться

Функция singleton_remover ничего не сделает, потому что вы переопределили выпуск , чтобы ничего не делать. Ваш метод allocWithZone: также выполняет аналогичное бездействие, когда отправляет keep совместно используемому экземпляру (и полностью игнорирует выделение в указанной зоне). Возможно, у вас должен быть флаг, который переключает, является ли ваш общий экземпляр непобедимым (то есть невыполнимым) или нет.

В любом случае ОС все равно очистит всю память. В документации указано, что для ОС быстрее просто освободить всю память сразу, чем для вашего приложения, чтобы медленно возвращать ее по частям.

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

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(performCleanup:)
                                             name:UIApplicationWillTerminateNotification
                                           object:nil];
0
ответ дан 17 December 2019 в 18:15
поделиться

Ваша реализация потокобезопасна и, похоже, покрывает все основы (+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; \
}
0
ответ дан 17 December 2019 в 18:15
поделиться

РЕДАКТИРОВАТЬ

Я включаю это вверху, ниже вы можете увидеть мои исторические исходные вопросы и реализацию. Однако я думаю, что нашел оптимальный способ предоставить метод 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
}
0
ответ дан 17 December 2019 в 18:15
поделиться