Przejdź do głównej zawartości

iOS – singleton

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.

Komentarze

Popularne posty z tego bloga

Makra i preprocesor

Jako programista klasycznego C przyszło mi wielokrotnie ścierać się z makrami. Makro to zestaw instrukcji umieszczanych w kodzie są jednak interpretowane nie przez kompilator ale przez preprocesor. Preprocesor jest „pomocnikiem” kompilatora, zajmuje się on np. wstawianiem treści plików nagłówkowych do plików z kodem za pomocą instrukcji #include. Preprocesor należy rozumieć jako prymitywny edytor tekstu dokonujący „korekcji” plików z kodem źródłowym przed rozpoczęciem ich przetwarzania przez kompilator. Jakie są zalety wykorzystywania tych rozwiązań w kodzie? Tak naprawdę w języku C w czasach przed wprowadzeniem słowa kluczowego inline umożliwiały wstawianie kodu we wskazane miejsce. Należy bowiem pamiętać że każdorazowe wstawienie makroinstrukcji powoduje ingerencję w kod źródłowy (innymi słowy we wskazanym miejscu zostanie wstawiony stosowny fragment kodu). Łatwo obserwowalnym efektem częstego wykorzystywania makr w plikach z kodem jest rozrost pliku binarnego oraz jego szybsze d...

C++ metaprogramowanie z wykorzystaniem szablonów cz. 1

Ten wpis rozpoczyna cykl dotyczący metaprogramowania, ilość oraz częstotliwość publikacji kolejnych części pozostaje nieustalona. Poniżej zaprezentowane zostanie absolutny elementarz, czyli wyznaczanie dowolnego elementów wyrazu ciągu Fibonacciego w czasie kompilacji. #include <iostream> template<unsigned int N> struct Fib {   static const unsigned int el = Fib<N-1>::el + Fib<N-2>::el; }; template<> struct Fib<0> {   static const unsigned int el = 0; }; template<> struct Fib<1> {   static const unsigned int el = 1; }; int main(void) {   const unsigned int N = 5;   std::cout << "fib( " << N << " ) = " << Fib<N>::el << std::endl;   return 0; } Tych parę linijek zmusza do wyliczenia 5 elementu ciągu Fibonacciego kompilator, co oznacza że jedynymi instrukcjami jakie zostaną wykonane przez nasz progr...

C++11 Iterowanie

Zasadniczo iterowanie po elementach tablicy/vectora/listy itp. nie jest niczym nowym, interesującym ani pasjonującym, ot szara codzienność. Przyjrzyjmy się zatem jak robimy to najczęściej: class Image { public:   Image();   void rotate(float angle);   void display();   void* serialize(); }; class ShowImage { public:   void operator()(Image image)   {     image.display();   } }; //... std::vector<Image> imageCollection(10); for(int i=0; i<imageCollection.size(); ++i) {   imageCollection[i].display(); } for( std::vector<Image>::iterator                          currentImage = imageCollection.begin();                         currentImage < imageCollection.end();               ...