Является ли это окончательной ссылкой, подсчитанной реализацией Objective C? - PullRequest
6 голосов
/ 19 февраля 2010

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

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

Ответы [ 5 ]

2 голосов
/ 19 февраля 2010

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

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

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

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

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

1 голос
/ 19 февраля 2010

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

  • Не беспокойтесь о -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;
}
0 голосов
/ 19 февраля 2010

Ваша реализация является поточно-ориентированной и, кажется, охватывает все базы (+ инициализация отправляется поточно-ориентированной средой выполнения)

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 голосов
/ 19 февраля 2010

EDIT

Я включил это вверху, ниже вы можете увидеть мои исторические оригинальные вопросы и реализацию. Тем не менее, я думаю, что нашел оптимальный способ предоставления метода 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 голосов
/ 19 февраля 2010

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

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

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

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(performCleanup:)
                                             name:UIApplicationWillTerminateNotification
                                           object:nil];
...