Jakiś czas temu spotkałem się z sytuacją gdy jeden z moich kolegów zaimplementował klasę która w zamiarze maiła być singletonem. Sprawa wydawała by się prosta prywatny
//-----------------------------------------------------
singleton.h
@interface Singleton
+(Singleton*) instance
@end
//-----------------------------------------------------
//-----------------------------------------------------
singleton.m
@implementation Singleton
static Singleton* instance;
+(Singleton*) instance
{
if(!instance)
{
instance = [[Singleton alloc] init];
}
return instance;
}
+(id) init
{
self = [super init];
return self;
}
@end
//-----------------------------------------------------
Problem w tym że przy takiej implementacji mam do czynienia z umownym singletonem gdyż nic nie stoi na przeszkodzie aby w kodzie klienckim napisać
id fake_singleton = [[Singleton alloc] init];
Przez co istnieje możliwość utworzenia wielu instancji klasy Singleton.
Ponieważ do self przypisywać mogą jedynie metody z rodziny init można pokusić się o napisanie własnej metody np. initSingleton będąca jedynie metodą prywatną (jej prototyp nie znajduje się w pliku singleton.h). Implementacja będzie identyczna z prezentowaną wcześniej metodą init. I tu nasuwa się pierwsze pytanie, przecież prototyp metody init również nie znajduje się w pliku singleton.h zatem kod kliencki nie powinien móc się do niej odwoływać. Rzeczywiście tak jest w przypadku innych metod, niestety wszystkie klasy dziedziczą w sposób niejawny po klasie NSObject która to posiada publiczną wersje metody init wywoływanej w linijce self = [super init]. Oznacza to że pomimo iż nie udostępniamy prototypu metody init w pliku nagłówkowym jawnie, robi to za nas w sposób niejawny NSObject co prowadzi do opisanego zachowania. Implementując jedynie prywatną wersję initSintgleton klient będzie mógł wywołać metodę init dla klasy NSObject co w prezentowanym przypadku doprowadzi do utworzenie więcej niż jednej instancji naszej klasy. Jakie jest zatem poprawne rozwiązanie problemu. Oto ono:
//-----------------------------------------------------
singleton.h
@interface Singleton
+(Singleton*) instance
@end
//-----------------------------------------------------
//-----------------------------------------------------
singleton.m
@implementation Singleton
static Singleton* instance;
+(Singleton*) instance
{
if(!instance)
{
instance = [[Singleton alloc] initSingleton];
}
return instance;
}
+(id) initSingleton
{
self = [super init];
return self;
}
+(id) init
{
NSLog(@”This class is singleton, to get instance of it please use method instance”);
return nil;
}
@end
//-----------------------------------------------------
Jak widać do inicjalizacji obiektu wykorzystujemy metodę initSingleton, natomiast orginalną wersję metody init dostarczaną przez NSObject nadpisujemy własną wersją która zwraca zawsze nil. Oznacza to że za jej pomocą nie d się uzyskać instancji klasy Singleton. Ponadt grzecznościowo wypisujemy na konsoli logu informację o tym iż próba uzyskania obiektu tej klasy nie jest możliwe w ten sposób. Warto dodać iż NSLog jest dostępny jedynie dla kompilatu w trybie DEBUG, nie zostanie on skompilowany dla finalnej wersji kodu.
//-----------------------------------------------------
singleton.h
@interface Singleton
+(Singleton*) instance
@end
//-----------------------------------------------------
//-----------------------------------------------------
singleton.m
@implementation Singleton
static Singleton* instance;
+(Singleton*) instance
{
if(!instance)
{
instance = [[Singleton alloc] init];
}
return instance;
}
+(id) init
{
self = [super init];
return self;
}
@end
//-----------------------------------------------------
Problem w tym że przy takiej implementacji mam do czynienia z umownym singletonem gdyż nic nie stoi na przeszkodzie aby w kodzie klienckim napisać
id fake_singleton = [[Singleton alloc] init];
Przez co istnieje możliwość utworzenia wielu instancji klasy Singleton.
Ponieważ do self przypisywać mogą jedynie metody z rodziny init można pokusić się o napisanie własnej metody np. initSingleton będąca jedynie metodą prywatną (jej prototyp nie znajduje się w pliku singleton.h). Implementacja będzie identyczna z prezentowaną wcześniej metodą init. I tu nasuwa się pierwsze pytanie, przecież prototyp metody init również nie znajduje się w pliku singleton.h zatem kod kliencki nie powinien móc się do niej odwoływać. Rzeczywiście tak jest w przypadku innych metod, niestety wszystkie klasy dziedziczą w sposób niejawny po klasie NSObject która to posiada publiczną wersje metody init wywoływanej w linijce self = [super init]. Oznacza to że pomimo iż nie udostępniamy prototypu metody init w pliku nagłówkowym jawnie, robi to za nas w sposób niejawny NSObject co prowadzi do opisanego zachowania. Implementując jedynie prywatną wersję initSintgleton klient będzie mógł wywołać metodę init dla klasy NSObject co w prezentowanym przypadku doprowadzi do utworzenie więcej niż jednej instancji naszej klasy. Jakie jest zatem poprawne rozwiązanie problemu. Oto ono:
//-----------------------------------------------------
singleton.h
@interface Singleton
+(Singleton*) instance
@end
//-----------------------------------------------------
//-----------------------------------------------------
singleton.m
@implementation Singleton
static Singleton* instance;
+(Singleton*) instance
{
if(!instance)
{
instance = [[Singleton alloc] initSingleton];
}
return instance;
}
+(id) initSingleton
{
self = [super init];
return self;
}
+(id) init
{
NSLog(@”This class is singleton, to get instance of it please use method instance”);
return nil;
}
@end
//-----------------------------------------------------
Jak widać do inicjalizacji obiektu wykorzystujemy metodę initSingleton, natomiast orginalną wersję metody init dostarczaną przez NSObject nadpisujemy własną wersją która zwraca zawsze nil. Oznacza to że za jej pomocą nie d się uzyskać instancji klasy Singleton. Ponadt grzecznościowo wypisujemy na konsoli logu informację o tym iż próba uzyskania obiektu tej klasy nie jest możliwe w ten sposób. Warto dodać iż NSLog jest dostępny jedynie dla kompilatu w trybie DEBUG, nie zostanie on skompilowany dla finalnej wersji kodu.
Komentarze
Prześlij komentarz