2015-04-20 1 views
10

Я хотел бы взять НОД подход с использованием общих экземпляров к следующему шагу, так что я создал следующий код:Безопасный способ создать синглтон с методом инициализации в Objective-C

@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, и если они это сделают ... сделайте это пуленепробиваемым.

ответ

10

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

+ (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; 
} 
+1

(self = [super init]) выдает предупреждение и инициализирует без возврата. Nil остановит xcode от компиляции ... это было в моем edit – Ondrej

+1

@Ondrej. Отсутствующий возврат можно легко «зафиксировать» с помощью '@ throw' вместо '[NSException raise:]'. И '' (self = [super init]) 'pattern не создает предупреждения, вы должны что-то еще испортить. – Leo

+1

Почему бы не использовать '+ initialize' для инициализации экземпляра singleton? – Hyperbole

0

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

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

2

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

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

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

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

Как вы делаете класс одноплодным я хотел бы предложить вам сделать new метод недоступны слишком добавив следующую строку в файл заголовок:

+ (instancetype)new NS_UNAVAILABLE; 

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

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

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

+0

Ha! очень умно!!! Гораздо лучше, чем решение с исключениями. Спасибо! – Ondrej

+1

Это неверно в Xcode 7.3: независимо от того, NS_UNAVAILABLE или старый способ, Xcode предложит ошибку компилятора, говоря, что реализация 'sharedInstance' не может использовать' init'. – Wingzero