Obiekty, klasy i metody w Objective-C
24 lutego 2009
Programowanie zorientowane obiektowo w czystym C, mimo że możliwe, rzadko kiedy jest proste i przyjemne, a powstały kod jest zwykle bardzo zagmatwany. Dlatego też na bazie C powstały kompatybilne z nim języki dodające wygodne w użyciu wsparcie dla kodu zorientowanego obiektowo. Pierwszy z nich to niezwykle popularny C++, w którym większość tych rozszerzeń to różnego rodzaju dodatkowe operacje wykonywane podczas kompilacji. Drugi to niszowy Objective-C, który (oprócz składni) od C++ różni się tym, że dodatkowe elementy jakie zostały do niego wprowadzone są odpowiednimi operacjami wykonywanymi podczas działania programu. Dzięki temu udaje się uzyskać w języku kompilowanym do kodu natywnego część udogodnień znanych z technologii takich jak .NET czy Java.
Klasy
W języku Objective-C każda klasa jest także obiektem zawierające podstawowe informacje wymagane przez pozostałe funkcje wsparcia runtime. Najważniejsze z danych przechowywanych przez obiekty opisujące klasy to:
- nazwa klasy
- rozmiar instancji klasy (z uwzględnieniem klas bazowych)
- lista zmiennych składowych klasy
- lista funkcji składowych klasy
- wskaźnik na klasę bazową
- lista klas pochodnych
- realizowane protokoły
Traktowanie klasy jako obiektu (inspirowane Smalltalkiem) pozwala znacząco poszerzyć możliwości języka szczególnie jeżeli chodzi o refleksyjność. Struktury opisujące klasy najczęściej są przechowywane w wewnętrznych danych runtime. Odpowiednie informacje są wydobywane przy okazji tworzenia instancji danej klasy, a następnie wiązane nowym obiektem. Dlatego też poniższy kod:
array = [NSArray new]
zostanie w większości implementacji, już przez kompilator zamieniony na konstrukcję podobną do tej:
array = [(objc_get_class("NSArray")) new]
Funkcja objc_get_class przegląda listę wszystkich klas w poszukiwaniu żądanej i w przypadku jej odnalezienia zwraca wskaźnik do niej. Cała operacja ma miejsce już podczas działania programu, dlatego też istotna jest szybkość działania takich podstawowych operacji. Jednym ze sposobów na przyśpieszenie kodu jest cachowanie wskaźników na obiekty opisujące klasy. W rezultacie funkcja objc_get_class jest wykonywana jeden raz dla każdej klasy, a w każdym kolejnym przypadku używany jest zwrócony przez nią wskaźnik.
Obiekty
Wszystkie klasy w Objective-C dziedziczą po NSObject. Jest to klasa która realizuje większość operacji pozwalających na korzystanie przez programistę z informacji jakie są przechowywane o każdej klasie. Oferowana funkcjonalność to między innymi:
- sprawdzenie czy klasa odpowiada na selektor
- sprawdzenie czy klasa realizuje protokół
- podanie funkcji odpowiadającej na selektor
- wykonanie podanego selektora
- podanie nazwy klasy
Selektory
Selektory identyfikują wysyłaną do obiektu wiadomość, czyli innymi słowy, nazywają funkcję składową która powinna zostać wywołana. W większości jest to po prostu ciąg znaków odpowiadających nazwie funkcji, chociaż w niektórych implementacjach zawiera także unikalny numer identyfikacyjny. W celu usprawnienia operacji tłumaczenia ciągu znaków na selektor i odwrotnie istnieją odpowiednie tablice przechowujące listy wszystkich zarejestrowanych danych. Jako że mimo tego, może to być kosztowna operacja, umożliwiane jest (wręcz zalecane) tłumaczenie nazw do selektorów podczas kompilacji.
Wywołanie funkcji składowych
Wywołanie funkcji składowych, a właściwie zgodnie z nomenklaturą Objective C: wysyłanie wiadomości obiektom jest przeprowadzany w całkowicie dynamiczny sposób. Warto także zaznaczyć, że ponieważ klasy także są traktowane jako obiekty, statyczne funkcje składowe wywoływane są w taki sam sposób. Oto przykładowy kod wysyłania komunikatu do obiektu:
[obiekt metoda]
zostaje zamieniony na:
objc_msgSend(obiekt, @selector(metoda))
Funkcja objc_msgSend, na podstawie informacji zawartych w opisie klasy której instancją jest object, wywołuje odpowiednią metodę. Każda klasa zawiera listę selektorów oraz przypisanych im adresów funkcji. Procedura przekazująca wiadomość przeszukuje tę listę (z uwzględnieniem klas bazowych) w poszukiwaniu adresu odpowiedniej metody, która następnie zostaje wywołana.
Często, w celu przyśpieszenia tej bardzo częstej operacji, wykorzystywane są tak zwane dispatch tables. Właściwie pod każdym względem przypominają one tablice funkcji wirtualnych wykorzystywane np. w C++. Każda taka tablica zawiera jedynie adresy kolejnych funkcji składowych klasy. W tej implementacji unikalny numer identyfikacyjny selektora służy także jako indeks wskazujący na odpowiedni adres w tej tablicy. Najważniejszą różnicą między dispatch table a tablicami funkcji wirtualnych jest fakt, że dispatch tables najczęściej są tworzone już podczas działania programu na podstawie listy funkcji składowych zawartej w obiekcie opisującym klasę.
Pozostałe dane dotyczące klasy
Często zdarza się, że wpisy z adresami funkcji składowych i odpowiadającymi im selektorami zawierają także informację o przyjmowanych przez metodę argumentach. Jest to przydatne przy debuggingu. Podobnie rzecz się ma z wpisami dotyczącymi zmiennych składowych. Obiekt opisujący klasę przechowuję listę struktur zawierających nazwę każdej takiej zmiennej, jej typ (dla ułatwienie debuggingu) i przesunięcie w stosunku do bazowego adresu instancji.
Podsumowanie
Objective-C prezentuje inne podejście co do sposobu implementacji rozszerzeń związanych z OOP w C niż C++. Opiera się bardziej na wykonywaniu operacji podczas działania programu, co zmniejsza jego wydajność, ale z drugiej strony oferuje szereg dodatkowych możliwości. Niech za przykład posłużą tutaj choćby systemy rozproszone. W Objective-C przy odrobinie wysiłku ze strony osoby implementującej taki system, można tworzyć obiekty rozproszone w banalnie prosty sposób. Możliwe jest to właśnie dzięki dużej ilości informacji które zostają w kodzie po kompilacji, oraz wielu możliwościach wstawienia dodatkowego kodu podczas działania programu. Oczywiście takie rozwiązania są też możliwe w C++ czy innych językach, ale nie zawsze jest to aż takie proste.
Główną wadą Objective-C jest jego niewielka popularność. Przed śmiercią chroni go właściwie jedynie OpenStep. Warto jednak zwrócić uwagę na jego możliwości, ponieważ jest on gdzieś pomiędzy innymi językami kompilowanymi do kodu natywnego a kompilowanymi do kodu pośredniego. Z jednej strony wsparcie runtime jest bardzo rozbudowane, ale wciąż istnieje możliwość uzyskania maksymalnej możliwej wydajności w krytycznych miejscach, przez ograniczenie używania niektórych z jego funkcji.
Komentarze do wpisu "Obiekty, klasy i metody w Objective-C":
1.
hm napisał(a):
24 lutego 2009, 22:23:28
> Przed śmiercią chroni go właściwie jedynie OpenStep.
Cocoa?
2.
Paweł Dziepak napisał(a):
24 lutego 2009, 22:23:49
Cocoa jest implementacją OpenStep.
3.
hm napisał(a):
24 lutego 2009, 22:43:36
hmhmh. implementacja++, ale racja, Dzięki.
Dodaj komentarz: