Безопасный способ создания 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 бессмысленно, потому что второй вызывающий абонент будет иметь другое я).