Безопасный способ создания singleton с методом init в Objective-C

Я хотел бы использовать подход GCD для использования общих экземпляров для следующего шага, поэтому я создал следующий код:

@implementation MyClass

static id sharedInstance;

#pragma mark Initialization

+ (instancetype)sharedInstance {
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (instancetype)init {
    if (sharedInstance) {
        return sharedInstance;
    }
    @synchronized(self) {
        self = [super init];
        if (self) {
            sharedInstance = self;
        }
        return self;
    }
}

@end

Я предполагаю, что метод sharedInstance выглядит нормально, но я не уверен в методе init. Причина этого заключается в том, что я не хочу, чтобы люди использовали мой SDK, чтобы использовать метод init, и если они это сделают... сделайте это доказательством пули.

Ответ 1

Вместо прозрачного перенаправления вызовов на init на реализацию singleton, которая может вызвать очень запутывающее поведение для пользователей вашего SDK, я предлагаю не разрешать вообще вызвать init:

+ (instancetype)sharedInstance {
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] initPrivate];
    });
    return sharedInstance;
}

- (instancetype)init {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"..." userInfo:nil];
}

- (instancetype)initPrivate {
    if (self = [super init]) {
        ...
    }
    return self;
}

Ответ 2

Я хотел бы предложить новые способы решения вашей проблемы.

Вы можете использовать NS_UNAVAILABLE в файле заголовка так:

//Header file
@interface MyClass : NSObject
+ (instancetype)sharedInstance
- (instancetype)init NS_UNAVAILABLE;
//...
@end

В этом случае функция init не будет доступна извне, не будет предложена для автозаполнения, и вы сможете обычно использовать метод init внутри файла реализации.

Как вы делаете класс singleton, я предлагаю вам сделать метод new недоступным, добавив эту строку в заголовочный файл:

+ (instancetype)new NS_UNAVAILABLE;

Существует также старый способ недоступности методов (которые также могут использоваться в заголовке):

- (instancetype) init __attribute__((unavailable("Use 'sharedInstance' instead of 'init' as this class is singleton.")));

Это можно использовать, если вы хотите вызвать сообщение о недоступности.

Ответ 3

Общее мнение заключается в том, что попытка защитить ваш синглтон от такого рода ошибок бессмысленна. Тот, кто называет [[LUIMain alloc] init] и создает синглтон, получает то, что заслуживает.

И код, который вы написали, в любом случае не является потокобезопасным. Если я вызываю [[LUIMain alloc] init], а кто-то вызывает sharedInstance, sharedInstance возвращает другой объект, чем при следующем вызове. (@synchronized (self) в методе init бессмысленно, потому что второй вызывающий абонент будет иметь другое я).