Poniższy artykuł jest tłumaczeniem artykułu Learning Objective-J
Objective-J jest nowym językiem programowania bazującym na Objective-J. Jest rozszerzeniem JavaScript, co oznacza że każdy poprawny kod JavaScript jest również poprawnym kodem Objective-J. Każdy zaznajomiony z JavaScript i koncepcji programowania obiektowego, w szególności klasycznego dziedziczenia, nie powienien miec trudności z nauką Objective-J. Znajomość Objective-C będzie pomocna ale nie jest wymagana
Objective-J posiada dwa typy obiektów, natywne obiekty JavaScript i obiekty Objective-J. Natywne obiekty JS są dokładnie tym jak brzmi, czyli obiektami natywnymi dla JavaScript. Obiekty Objective-J są specjalnym typem obiektów dodanych przez Objective-J. Te nowe obiekty są oparte na klasach i klasycznym dziedziczeniu, jak w C++ lub w Javie, zamiast prototypal model
Tworzenie klas w Objective-J jest proste. Przykład klasy Person, która zawiera jedną zmienną - name:
@implementation Person : CPObject { CPString name; } @end
Początkiem klasy jest zawsze słowo kluczowe @implementation, po nim występuje nazwa klasy. Trzecim wyrażeniem, po dwukropku, jest klasa po której chcesz dziedziczyc. W tym przypadku, dziedziczymy po CPObject, który jest klasą bazową dla większości klas. Nie musisz dziedziczyć, ale niemalże cały czas będziesz chciał.
Po deklaracji, blok zamknięty nawiasami jest używany do definiowania wszystkich zmiennych klasowych. Każda zmienna jest deklarowana w osobnej linii w następujący sposób: typ, nazwa i średnik. Technicznie typ jest opcjonalny ale jest wysoce rekomendowany. Deklarowanie Twoich zmiennych jest ważne, ponieważ każda zmienna użyta w Twojej klasie, która nie jest zdeklarowana automatycznie, stanie się zmienną globalną.
Klasa zamykana jest poprzez dodane słowa kluczowe @end
Tak jak z obiektami, natywne funkcje JavaScript działają bez zmian w Objective-J. Oprócz natywnych funkcji, Objective-J dodaje metody, które stają się częścią nowego systemu klas. Dodajmy zestaw metod dostępowych do naszej klasy Person:
- (void)setName:(CPString)aName { name = aName; } - (CPString)name { return name; }
Powyższy kod jest wstawiany po linii początkowej @implementation i po bloku deklaracji zmiennych, ale przed słowem kluczowym @end. Ta składnia powinna być znajoma wszystkim, którzy programowali w języku podobnym do C, w tym JavaScript. Jedyną interesujcą rzeczą jest deklaracja metod.
Każda sygnatura metody rozpoczyna się albo minusem (-) albo plusem (+). Minus jest używany dla metod instancji, które są metodami, które wywołujesz na zmiennej instancji (tj. zmiennej, która przechowuje referencje do nowego obiektu). Dwie powyższe metody są metodami instancji, co ma sens ponieważ ustawiają (setName:) i zwracają(name) wartość zmiennej obiektu Person.
Występujący po minus/plus element w nawiasie jest zwracanym przez metodę typem. Nic specjalnego na ten temat. Ponownie, zdeklarowany typ jest opcjonalny ale wysoce zalecany, gdyż pomagają dokumentować Twój kod. Wreszcie, deklarujemy nazwę metody. W Objective-J, parametry metody są przeplatane z nazwą metody. W powyższym przykładzie mamy dwie metody: name i setName:. Zwróć uwagę dwukropek po ”setName”; wskazuje on że po etykiecie ”setName:” następuje parametr typu CPString i o nazwie aName.
Gdy metody mają więcej niż jeden parametr, każdy parametr jest rozdzielony dwukropkiem. Deklarując taką metodę, parametry są rozdzielone etykietą (setJobTitle - etykieta dla parametru pierwszego; company - etykieta dla parametru drugiego) i występującym po każdej z nich dwukropkiem, typem oraz nazwą parametru.
- (void)setJobTitle:(CPString)aJobTitle company:(CPString)aCompany { }
W Objective-J, nazwy metod są rozdzielone przez wszystkie argumenty dając właściwą nazwę metody. To nie są technicznie nazwane argumenty, Powyższa metoda jest nazwana setJobTitle:company:. Jest to osiągnięte poprzez złączenie pierwszej części metody z wszystkimi kolejnymi etykietami (w kolejności ich występowania).
Parametry do metody muszą być przekazywane w kolejności i wszystkie parametry są wymagane. By wywołać taką wieloparametrową metodę, przekazujemy nasze dane do każdej z etykiet.
// Tworzenie nowego obiektu przypisanego do zmiennej 'myPerson' var myPerson = [[Person alloc] init]; // Wywoływanie metody wieloparametrowej z przekazaniem do parametrów wartości (input) [myPerson setJobTitle:"Founder" company:"280 North"];
Jak możesz zobaczyć, po każdym z dwukropków następuje wejście, które będzie mapowane do nazwy parametru. Taka kolejność: etykieta, dwukropek, wejście; jest powtarzane dla każdego parametru.
(poprawić tłumaczenie fragmentu poniżej)
Możesz się zastanawiać dlaczego jest to ważne jaka jest aktualna nazwa metody. Odnajdziesz jeden wzór w Objective-J i Cappuccino, jest to pomysł przekazywania parametrów jako argumentów do innej metody. Jest to używane powszechnie w delegacji i w systemie zdarzeń (event system). Odkąd metody nie są first class objects w ten sam sposób jak w JavaScript, używamy specjalną notację do odwoływania się do nich, @selector. Jeżeli będę chciał przekazać poprzednią metodę jako argument do innej metody użyję następującego kodu:
[fooObject setCallbackSelector:@selector(setJobTitle:company:)];
Jak możesz zobaczyć, nazwa matody jest przekazana do @selector wraz z dwukropkami i etykietami
Teraz gdy poznaliśmy podstawy obiektów i klas Objective-J, zobaczmy jak je zastosować. Poniższy blok kodu tworzy nowy obiekt Person i ustawia nazwę:
var myPerson = [[Person alloc] init]; [myPerson setName:"John"];
Wywoływane metody w Objective-J nazywane są „wiadomościami” i ty wysyłasz do obiektu wiadomość używając notacji nawiasów w ten sposób [object message]. Wspomniałem wcześnie że niektóre metody są metodami klasowymi (statycznymi?), które maja na celu zostać wywołane na klasie samej w sobie - alloc - jest jedną z tych metod. Każda klasa w Objective-J ma specjalną metodę klasową alloc, która zwraca nową instancję tej klasy.
W przykładzie powyżej, my wywołujemy metodę alloc na klasie Person, która zwraca instancję Person. Następnie, wywołujemy na utworzonej instancji metodę init. Obydwie metody alloc i init zwracają referencje do obiektu, która jest przypisana do naszej zmiennej myPerson. Tak jak alloc, każda klasa dziedziczy metodę init z CPObject.
Metoda klasowa alloc jest analogiczna do słowa kluczowego „new” w takich językach jak JavaScript, C++, i Java, w tym że tworzy ona nową instancję. Metoda init jest podobna do konstruktora w tych językach, w tym że wykonuje inicjowanie nowo utworzonej instancji.
Niektóre klasy specjalizują swoje własne niestandardowe metody init, używają one następującego oznaczenia:
- (id)initWithFrame:(CGRect)aFrame
Każda podklasa powinna pamiętać by wywołać metodę init rodzica.
Niestandardowa metoda init dla naszej klasy Person która przyjmuje nazwę bezpośrednio jest zaprezentowana poniżej:
- (id)initWithName:(CPString)aName { self = [super init]; if (self) { name = aName; } return self; }
Najpierw wywołujemy nadrzędną metodę init, która zwraca referencje do nowo zainicjowanej instancji. My musimy przypisać tą referencję do zmiennej „self” (w przypadku gdy metoda init nad-klasy zamienia orginalną instancję na nową). Sprawdzamy by się upewnić czy self został zwrócony prawidłowo i jeżeli tak możemy wykonać nasze przywiązanie parametru aName do zmiennej name, zwracamu self by wywołujący kod posiadał referencje do nowo zainicjowanego obiektu.
„self” równoważne w Objective-J z JavaScript's „this”. Tak jak „this” jest referencją do obiektu JavaScript, „self” jest referencją do obiektu Objective-J. Jak w JavaScript, self.foo jest referencją do zmiennej instancji foo, ale w przeciwieństwie do JavaScript self nie jest wymagane, możesz używać foo wewnątrz metody instancji.
Wiele klas w Cappuccino oferuje delikatnie różne sposoby tworzenia obiektów, co może być bardziej wygodne. Zamiast wywoływania alloc i init, te klasy implementują swoje własne metody zwracające nowy obiekt. Zauważ że w metodach klasy, self jest referencją do samej klasy.
+ (id)personWithName:(CPString)aName { return [[self alloc] initWithName:aName]; }
Co będzie używane tak:
var joe = [Person personWithName:"Joe"];
W oryginalnym tekście nic nie wspomniano że można dużo prościej utworzyć nowy obiekt:
var myPerson = [Person new];
Powszechnie pożądaną i brakującą techniką w JavaScript jest możliwość do importowania kodu w taki sam sposób jak na to się robi w C lub Java. By na to pozwolić Objective-J dodało wyrażenie @import.
@import <Foundation/CPObject.j> @import <AppKit/CPView.j> @import "MyClass.j"
Występują dwa typy wyrażenia import. Nawias ”< >” wskazuje na kod frameworka, natomiast znak cytatu ” ” wskazuje na lokalny kod w projekcie. Framework pozwala na wykorzystanie mechanizmu przeszukiwania ścieżek w celu szukania pożądanych plików w zdefiniowanych położeniach. Lokalny import szuka wyłącznie w położeniu relatywnym do importowanego pliku.
JavaScript jest językiem zbierającym nieużytki pamięci (?) i tym samym Objective-J, więc nie będziesz widział żadnych wywołań by zachować lub zwolnić kod z pamięci w Objective-J tak jak w Objective-C. Wiele wspólnych wycieków spowodowanych przez manipulację DOM jest wychwytywane przez Cappuccino framework.
Nie jest powiedziane że jest niemożliwy wyciek obiektu. Tak jak w każdym języku zbierającym nieużytki (garbage collected language), jest możliwe przypadkowe złapanie referencji do obiektu tak że nie będą uwolnione, więc trzymaj to w pamięci.
poprawność logiczna zdań.
Kategorie pozwalają Ci dodać metody do klasy bez potrzeby tworzenia podklasy lub modyfikowania jej kodu źródłowego. Nowa metoda (lub metody) stają się częścią wszystkich instancji tej klasy, wtedy gdy kategoria zostanie załadowana.
Jest to przydatne w wielu różnych scenariuszach np.: dodanie metod do wbudowanych klas. Jeżeli będziesz chciał żeby wszystkie obiekty CPString posiadały metodę, która zwróci string w odwrotnej kolejności, możesz zdefiniować kategorię tają jak ta:
@import <Foundation/CPString.j> @implementation CPString (Reversing) - (CPString)reverse { var reversedString = "", index = [self length]; while(index--) reversedString += [self characterAtIndex:index]; return reversedString; } @end
Teraz możesz wywołać reverse na każdym obiekcie typu string i otrzymać odwrócony tekst.
var myString = "hello world"; var reversed = [myString reverse]; alert(reversed); // alerts "dlrow olleh"
Składnia dla kategorii rozpoczyna się od słowa kluczowego @implementation, po którym występuje nazwa klasy i nazwa kategorii w nawiasach. Każda metoda dodana przed słowem kluczowym @end będzie częścią kategorii.
Zauważ że nie możesz dodać zmiennych instancji poprzez kategorie, choć ze względu na dynamiczną naturę obiektu JavaScript jest możliwe poprzez prostą bezpośrednią modyfikację w właściwości (properties) obiektu:
instance.newProperty = "foo";
Warto zauważyć wykorzystanie pewnych technik w implementacji metody reverse w przykładzie powyżej. Dla przykładu, reversedString jest zdeklarowane jak zwyczajny „string” JavaScript. Jest to dzięki technice zwanej toll-free bridging, która pozwala każdemu obiektowi JavaScript jak array lub string zachowywać się jak obiekt JavaScript i object Cappuccino w tym samym czasie. Jest to odpowiedz dla metody CPString „lenght” i „characterAtIndex:”, jak również istniejące metody JavaScript i operatory jak ”+”.
Większość czasu, Objective-J ma ten sam zakres zasad jak JavaScript. Zmienne nie zdeklarowane szczegółowo z var stają się zmiennymi globalnymi podczas gdy zmienna poprzedzona var ma poziom zasięgu do metod i funkcji. Dwie zmiany do tych przepisów są zmiennymi instancji i zmiennymi o zasięgu pliku.
Zmienne instancji są zdeklarowane w bloku @implementation(przedstawione wcześniej w samouczku). Kiedy używasz tych zmiennych zw swojej klasie, mają zasięg do poziomu obiektu - nie są one globalne, należą one do każdego obiektu instancji. Jeżeli zapomnisz zdeklarować jedną z Twoich zmiennych instancji wtedy taka zmienna jest traktowana jak zmienna globalna, jak każda inna w kodzie JavaScript.
Zmienne o zasięgu tylko w pliku są czymś wprowadzonym w Objective-J. Kiedy deklarujesz zmienną z słowem kluczowym var poza funkcją lub implementacja metody, te zmienne (czasami nazywane statycznymi) mają zasięg do poziomu pliku. Można mieć do nich dostęp przez inny kod ale w tym samym pliku. To może być przydatne dla implementacji wielu wspólnych obiektowych technik bez potrzeby uciekania się do zmiennych globalnych. Jeżeli plik posiada pojedynczą klasę można o nich myśleć jak o zmiennych klasowych.
Poniżej jest przykład głównych zasad zasięgu w Objective-J:
// zmienna globalna globalScoped = "this becomes global"; // zmienna o zasięgu tylko dla tego pliku var fileScoped = "this stays scoped in the file"; @implementation Foo : CPObject { // zmienna obiektu CPString objectScoped; } - (void)baz { // zmienna lokalna dostępna i widoczna tylko w tej metodzie var methodScoped; methodScoped = "function scope, declared with var"; // zmienna globalna anotherGlobal = "global scope, no var"; // zmienna obiektu - zadeklarowana powyżej objectScoped = "still object scoped"; // zmienna o zasięgu tego pliku - zadeklarowana powyżej fileScoped = "still file scoped"; } @end
Podsumowując przegląd postaw Objective-J. Język jest porosty i prostym dodatkiem do JavaScript, większość developerów nie powinna mieć żadnych problemów z zapoznaniem się z nim.
Pełen kod zaprezentowany w artykule do pobrania poniżej:
@import <Foundation/CPObject.j> @implementation Person : CPObject { CPString name; } + (id)personWithName:(CPString)aName { return [[self alloc] initWithName:aName]; } - (id)initWithName:(CPString)aName { self = [super init]; if (self) { name = aName; } return self; } - (void)setName:(CPString)aName { name = aName; } - (CPString)name { return name; } - (void)setJobTitle:(CPString)aJobTitle company:(CPString)aCompany { } @end
@import <Foundation/CPString.j> @implementation CPString (Reversing) - (CPString)reverse { var reversedString = "", index = [self length]; while(index--) reversedString += [self characterAtIndex:index]; return reversedString; } @end
globalScoped = "this becomes global"; var fileScoped = "this stays scoped in the file"; @implementation Foo : CPObject { CPString objectScoped; } - (void)baz { var methodScoped; methodScoped = "function scope, declared with var"; anotherGlobal = "global scope, no var"; objectScoped = "still object scoped"; fileScoped = "still file scoped"; } @end