Как Objective-C singleton должен реализовать метод init?

Я прочитал пару удивительных ресурсов в одиночных играх в Obj-C:

но ни один из этих ресурсов не рассмотрел концепцию метода init явно и все еще был новичком в Obj-C. Я смущен, как его реализовать.

До сих пор я знаю, что иметь init private невозможно в Obj-C, поскольку он не предлагает истинные частные методы... поэтому возможно, что пользователь может вызвать [[MyClass alloc] init] вместо моего [MyClass sharedInstance].

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

Ответы

Ответ 1

Ну, простой способ вокруг init состоит в том, чтобы просто не написать его, чтобы он вызывал реализацию NSObject по умолчанию (которая возвращает только self). Затем, для вашей функции sharedInstance, определите и вызовите закрытую функцию, которая выполняет работу, подобную init, когда вы создаете экземпляр вашего синглета. (Это позволяет избежать случайной повторной инициализации вашего синглтона.)

Однако!!! Основная проблема заключается в том, что alloc вызывается пользователем вашего кода! Для этого я лично рекомендую маршрут Apple для переопределения allocWithZone:...

+ (id)allocWithZone:(NSZone *)zone
{
    return [[self sharedInstance] retain];
}

Это означает, что пользователь все равно получит ваш экземпляр singleton, и они могут ошибочно использовать, как если бы они его выделили, и безопасно отпустить его один раз, поскольку этот пользовательский alloc выполняет сохранение в singleton. (Примечание: alloc вызывает allocWithZone: и не требует отдельного переопределения.)

Надеюсь, что это поможет! Дайте мне знать, если вы хотите получить дополнительную информацию ~

EDIT: расширенный ответ, чтобы предоставить пример и более подробную информацию -

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

Однако в моем случае мне нужен потокобезопасный lazy-load singleton, то есть он не выделяется до тех пор, пока он не будет использоваться, вместо того, чтобы автоматически выделяться при запуске приложения. Узнав, как это сделать безопасно, я подумал, что, возможно, так и пройду.

РЕДАКТИРОВАТЬ № 2: Теперь я использую GCD dispatch_once(...) для поточно-безопасного подхода выделения одноэлементного объекта только один раз для срока службы приложения. См. Apple Docs: GCD dispatch_once. Я также добавляю allocWithZone: бит переопределения из примера Apple old singleton и добавляет закрытый init с именем singletonInit, чтобы предотвратить его случайное вызов несколько раз:

//Hidden/Private initialization
-(void)singletonInit 
{
   //your init code goes here
}

static HSCloudManager * sharedInstance = nil;   

+ (HSCloudManager *) sharedManager {                                   
    static dispatch_once_t dispatchOncePredicate = 0;                  
    dispatch_once(&dispatchOncePredicate, ^{                           
        sharedInstance = [[super allocWithZone:NULL] init];          
        [sharedInstance singletonInit];//Only place you should call singletonInit 
    });                                                                
    return sharedInstance;                                                       
}

+ (id) allocWithZone:(NSZone *)zone {
    //If coder misunderstands this is a singleton, behave properly with  
    // ref count +1 on alloc anyway, and still return singleton!
    return [[HSCloudManager sharedManager] retain];
}

HSCloudManager подклассы NSObject и не переопределяет init, оставляя только реализацию по умолчанию в NSObject, которая, согласно документации Apple, возвращает только self. Это означает, что [[HSCloudManager alloc] init] совпадает с [[[HSCloud Manager sharedManager] retain] self], что делает его безопасным как для запущенных пользователей, так и для многопоточных приложений в качестве лёгкой загрузки singleton.

Что касается вашей обеспокоенности в отношении пользовательского подкласса вашего синглтона, я бы сказал, что это просто комментарий/документ. Любое слепое подклассирование без чтения в классе требует боли!

РЕДАКТИРОВАТЬ № 3: Для совместимости с ARC просто удалите часть сохранения из переопределения allocWithZone:, но сохраните переопределение.

Ответ 2

Честно? Вся причуда написания пуленепробиваемых одноэлементных классов кажется довольно раздутой для меня. Если вы серьезно обеспокоены этим, просто прикрепите assert (sharedInstance == nil) туда, прежде чем назначать ему первый раз. Таким образом, это сбой, если кто-то использует его неправильно, сразу же сообщив им, что они идиот.

Ответ 3

Не следует влиять на метод init. Это будет одно и то же в одноэлементном классе, как в обычном классе. Вы можете переопределить allocWithZone: (который вызывается alloc), чтобы избежать создания нескольких экземпляров вашего класса.

Ответ 4

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

- (id)init NS_UNAVAILABLE; 
+ (id)new NS_UNAVAILABLE; 

+ (instancetype)sharedInstance;

Ответ 5

Попробуйте это

+(id)allocWithZone:(struct _NSZone *)zone{
    static dispatch_once_t onceToken_alloc;
    static id __alloc_instance;
    dispatch_once(&onceToken_alloc, ^{
         __alloc_instance = [super allocWithZone:zone];
    });
    return __alloc_instance;
}

-(id)init{
    static id __instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        __instance = [super init];
    });
    return __instance;
}