Strona główna Kontakt Jogger

Bazylowy Jogg

O makach, życiu i innych pierdołach

DISKLEJMER

Chwilowo brak disklejmera :P

-- Bazyl

Marzenia się spełniają

I tak właśnie w tej chwili siedzę przed swoim nowym, firmowym komputerem. Nie jest to oczywiście nic wyszukanego. Ot - zwykły MacMini z powiększoną ilością pamięci.

Ale jest! A ja mam tylko jedno zadanie - przystosować naszą firmową aplikację do pracy na iPhone.

Firma właśnie wzbogaciła się o jednego, strasznie szczęśliwego programistę... :D

7 komentarzy. Poziom: 0; Kategorie: Cocoa Emigracja Obj-C Programowanie ; 10 marca 2008 14:00:31.

Marzenia się spełniają

Na prawdę się spełniają.

Odkąd pamiętam, zawsze lubiłem programować w zamkniętych systemach. Głównie na urządzenia mobilne. Telefony, PDA. Dlatego wybrałem taką firmę. Praca z tego rodzaju aplikacjami sprawia mi radość. Jest przyjemna. Trzeba zwracać uwagę na wszystkie aspekty. Wydajność, zarządzanie pamięcią. Ciężka praca, ale dająca efekty.

Kiedy widzisz, jak ktoś używa Twojej aplikacji na telefonie, krzyczysz: "O! To ja napisałem!".

Moją drugą pasją są maczki. Kocham te komputery. Lubię na nie programować, choć często mam na to mało czasu. Zawsze więc chciałem pisać NA maczku i NA maczki.

Wczoraj Apple ogłosiło oficjalne SDK do iPhone'a. Innymi słowy opublikowało zestaw narzędzi do pisania oprogramowania na ich telefon, który powoli staje się kultowy. Może nie dla wszystkich, ale na pewno dla mnie.

Stworzyło to dogodną sytuację. Możliwość połączenia obu pasji. Maczków i telefonów.

I stało się.

Dziś zostałem zaproszony na dywanik do szefa. Po krótkiej konsultacji dostałem przykaz wyznaczenia maczka potrzebnego do pracy. Jeszcze dziś po południu na moim firmowym biurku stanie maczek. Od przyszłego tygodnia pracuję nad wersją naszego oprogramowania pracującą na iPhone.

Toż to prawdziwe spełnienie marzeń!

Orgazm normalnie!

14 komentarzy. Poziom: 0; Kategorie: Emigracja Obj-C Pieprzenie Programowanie ; 07 marca 2008 12:21:08.

Cocoa i wątki

Przez dłuższy czas zastanawiałem się, czy nadal używać POSIXowych wątków w swojej bibliotece, czy rzucić się na jakieś ciekawsze zastosowanie oryginalnych wątków Cocoa.

Dokumentacja na stronach Apple jest trochę lakoniczna, a i gugiel nie do końca pomaga. Zdecydowałem się więc rzucić trochę światła na ten banalniacki mechanizm.

Żeby odpalić nowy wątek musimy zrobić dwie podstawowe rzeczy. Mieć klasę która będzie miała metodę tego wątku (czyli takie coś, co będzie się pętlić i robić dziwne rzeczy, które są nam potrzebne) oraz użyć magicznej klasy NSThread.

Powiedzmy więc, że mamy klasę MojaKlasa i w niej metodę metodaWatku: . Nasza metoda powinna nie zwracać nic i przyjmować tylko jeden parametr typu id. Generalnie rzecz biorąc jest to strasznie podobniackie do funkcji puszczanej do pthread'a.

Żeby odpalić nasz nowy wątek wystarczy użyć jednej metody z klasy NSThread (metoda klasy, a nie obiektu, więc nic nie musimy tworzyć. Kod banalniacki podaję tutaj:



  MojaKlasa *obiekcik = [[MojaKlasa alloc] init];

  [NSThread detachNewThreadSelector: @selector(metodaWatku:) toTarget:obiekcik withObject: nil];



Co to zrobi? A mianowicie odpali w nowym wątku naszą metodę w podanym obiekcie obiekcik przekazując metodzie nil'a. Jeśli chcemy coś jej przekazać to podajemy to w ostatnim parametrze do metody detachNewThreadSelector:toTarget:withObject.

Mówiłem, że banalniackie :)

Jak zabić wątek? Zakończyć naszą metodę, albo wykonać metodę exit na obiekcie wątku (NSThread).

Oczywiście NSThread ma dodatkowo parę innych metod. Zarówno klasy jak i instancji. Pozwala na zidentyfikowanie w jakim wątku się aktualnie znajdujemy (metoda klasy currentThread) ustawianie priorytetu wątku i tym podobne pierdoły. Ale ogólnie rzecz biorąc całość jest przepotwornie prostacka.

Mimo wszystko spodziewałem się po kolegach z Apple trochę bardziej wyszukanych sposobów. Z drugiej strony oni się bardzo cieszą z tych selektorów, mimo że często dziedziczenie po ładnej klasie było by znacznie fajniejsze.

3 komentarze. Poziom: 0; Kategorie: Cocoa Obj-C Programowanie ; 26 września 2007 14:07:34.

XMPP.Framework - wielki comeback

Ja chyba nigdy nie będę w stanie być słowny.

Tym razem postaram się po raz kolejny. Projekt iTalk'a nadal w powietrzu, czeka nieustannie, aż skończę XMPP.Frameworka do jako takiej sytuacji, żeby można było przynajmniej gadać normalnie.

I chyba w końcu prawie mi się to udało. Wszystko co miałem przepisałem praktycznie od zera. Zamiast swoich rozwiązań XMLowych użyłem NSXMLElement. Ponieważ od Tigera również jest libsasl, jego również tam znajdziecie, zamiast moich wypocin, które mogą być pełne wad. Oczywiście wziąłem do siebie ideę "KISS". Podszedłem do sprawy również na tyle inteligentnie, że najpierw zaprojektowałem co chcę zrobić. Efekt to doxygenowa dokumentacja interfejsów wersji 0.1. I trochę moich prywatnych wypocin jak co zrobić pod spodem.

Przy okazji podszkoliłem już mój Objective-C na tyle, że nauczyłem się pisać metody prywatne (korzystając z ładnego ukrywania rozszerzeń), co też odśmieciło kod.

Pod spodem oczywiście Expat. Jestem zadowolony. Zaczyna działać. A jeśli będzie tak dobrze, jak już wygląda, to niebawem wersja ta wyjdzie i będę mógł dołożyć jakiś interfejs graficzny na szybko :)

Ale nic nie obiecuję. Nie wychodzi mi to najlepiej.

5 komentarzy. Poziom: 1; Kategorie: Cocoa Mac OS X Obj-C Programowanie ; 24 września 2007 16:20:38.

XMPP.Framework

Pracowałem nad tym już wcześniej, publikując Jabbaha, który w jakimś tam stopniu był funkcjonalny. Przeszło mi, bo zapotrzebowanie było.. hm.. żadne? :)

Teraz sprawa ma się zupełnie inaczej. Staram się za wszelką cenę zaimplementować pełen protokół XMPP. Zarówno XMPP Core jak i XMPP IM. Oczywiście będzie też implementacja kolejnych rzeczy. Chwilowo jednak te elementy są dla mnie najważniejsze. Oznacza to na przykład pełne wsparcie dla języków i przestrzeni nazw XMLa. Z autoryzacją opisaną w protokole, czyli SASL. W planach jest obsługa jedynie dwóch sposobów logowania. Oczywiście są to SASL Digest i SASL PLAIN. Założeniem jest również utrzymanie biblioteki w bardzo czystej i wygodnej do wykorzystania formie. Nie będzie to jednak zwykły projekt Open Source. Na pewno jednak będzie można z niego za darmo korzystać.

Cała biblioteka przygotowana jest do projektu, który już powstał i powoli się rozwija. Jest jakby jego częścią, ponieważ na czymś funkcjonalność trzeba testować. Na razie idzie mi silnie pod górkę. Okazuje się bowiem, że tak jak i GSASL nie działa pod Maczkami jak trzeba, tak i obsługa zwykłego, głupiego TLS jest tam troszkę niedopracowana. Nie można dokonać "przerzutu" ze zwykłego połączenia na połączenie TLS. Oznacza to dodatkowy nakład pracy w zaimplementowanie własnej wersji Socketów. BXStream, BXInputStream i BXOutputStream.

Bardzo jednak zależy mi na uniknięciu wynajdowania koła od nowa, więc będę się starał wbudować znane biblioteki obu tych elementów do frameworka i nadbudować nad nimi jedynie interfejs w Objective-C.

Jedno jest pewne. Jestem silnie zdeterminowany, żeby poprowadzić dobrze ten projekt. Nie mam zamiaru odpierdzielać fuszerki. Pewnie dlatego zajmie mi to stanowczo za dużo czasu. Cieszę się bardzo, że są ludzie, którzy wspierają mnie przy tym. Smoku daje z siebie wszystko co tylko może, najbliźsi rozumieją, kiedy chowam się w kącie ze swoim komputerem i klnę cicho pod nosem, kiedy coś po raz setny nie chodzi jak trzeba. Jajcuś zapytany, zawsze stara się odpowiedzieć. Wytykają mi nieumiejętne czytanie specyfikacji protokołu. Ale to dobrze. Bo to musi być solidnie zrobione. Źródła już są na sieci. Nie są jednak publiczne. Mają dostęp do tego jedynie wybrańcy. Więc jeśli ktoś będzie zainteresowany obejrzeniem samego kodu - zapraszam. Dokumentacja korzystania z frameworka zostanie opublikowana bardzo niebawem. Niestety jeszcze przed samym frameworkiem. No ale cóż. Plan już jest od dawna. Tylko doba za krótka, żeby wszystko zrobić dostatecznie szybko.

Dodaj komentarz. Poziom: 0; Kategorie: Cocoa Mac OS X Obj-C Programowanie Technikalia ; 21 marca 2007 12:43:49.

Wynajduję koło od nowa

Niestety sprawa wygląda tak:


@interface BXSASL : NSObject
{
    uint8_t mechanism;
    NSString *realm;
    NSString *username;
    NSString *password;
    NSString *nonce;
    NSString *cnonce;
    NSString *rspauth;
}

- (id)initWithUsername:(NSString *)usr password:(NSString *)pwd andMethod:(NSString *)meth;
- (void)dealloc;

- (BOOL)getAnswer:(NSString *)answer toChallenge:(NSString *)challenge whichIsBase64Encrypted:(BOOL)enc;
- (BOOL)saveRspAuth:(NSString *)theRspAuth whichIsBase64Encrypted:(BOOL)enc;

@end


Tak tak. Poczytałem trochę i wynajduję koło od nowa i implementuję własną obsługę SASL. Na razie zaimplementuję tylko metodę DIGEST-MD5 i (być może) PLAIN. Mam GSASL za wzór. Musi wystarczyć.

Zły jestem. Zawsze pod górkę. No ale takie życie :(

2 komentarze. Poziom: 0; Kategorie: Cocoa Mac OS X Obj-C Programowanie Technikalia ; 20 marca 2007 16:02:37.

TLS

Mojej radości nie ma granic. Apple się postarał i strumienie (klasa NSStream) ma wsparcie do TLS!!!

Ależ roboty mi to ujmie! Pozostaje jedynie ten nieszczęsny SASL. Niestety GSASL robi mi hopsasy i nie chce się ładnie skompilić. Czeka mnie albo wciąganie całej biblioteki do projektu (Expat już się tego doczekał. A było z tym jazdy co nie miara! Jak można include'ować inne pliki .c wewnątrz innego pliku .c??? I to po 3-4 razy!).

Muszę jeszcze pogrzebać na stronach Apple'a. Może coś się tam dla mnie znajdzie. Mój najlepszy przyjaciel, jakoś ostatnio mało pomocny jest :(

Trzymać dalej kciuki!

Dodaj komentarz. Poziom: 0; Kategorie: Cocoa Mac OS X Obj-C Programowanie Technikalia ; 20 marca 2007 14:08:16.

NSURL robi psikusy a NSStream nie wie o co chodzi

Ponieważ rzeczy podstawowe nadal zapełniają po kolei karty mojego mini-podręcznika, podzielę się drobną uwagą na temat programowania w Cocoa.

Uważajcie z NSURL. Jeśli podacie mu adres bez podanego protokołu (np bazyl.net zamiast http://bazyl.net na ten przykład) to nie wyrzuci błęda. Ale wywołanie metody pobrania czystego adresu zwróci NULL. Bardzo nieprzyjemne.

Problem jest tym większy, że jak Wam wpadnie do glacy korzystać z tej klasy do sprawdzania poprawności adresu przed otworzeniem strumieni (NSStream) to możecie się nieźle zdziwić.

Konstruktor NSStream przejdzie gładziutko. Dostaniecie notyfikacje, że strumienie został otwarte i są gotowe na przyjęcie danych. Dopiero próba napisania czegoś po strumieniu wyjściowym (NSOutputStream) zwróci -1 jako błąd, po czym rzuci zdarzeniem błędu do metody nasłuchującej.

Moim skromnym zdaniem - to trochę głupie.

Dodaj komentarz. Poziom: 0; Kategorie: Cocoa Mac OS X Obj-C Programowanie Technikalia ; 19 marca 2007 13:47:40.

O tym jak ruszyć obiekta

Ostatnim razem prawie udało nam się skończyć naszą pierwszą klasę. Pozostało nam jedynie pozmieniać kilka znaczków wewnątrz ciągu znaków. Problem jedynie leży w naturze klasy NSString.

Obiekty (nie-)modyfikowalne



Jest to bowiem tak zwany obiekt niemodyfikowalny (ang. immutable). Oznacza to, że raz zainicjowana wartość pozostaje w nim niezmienna. To czego my jednak byśmy chcieli to obiekt modyfikowalny (ang. mutable). Na całe szczęście istnieje taka wersja klasy NSString. I - jak łatwo się domyśleć - jest to właśnie NSMutableString. Owa klasa dziedziczy oczywiście po oryginalnym NSString co oznacza, że łatwo będzie "przerzucać" te obiekty jako NSString.

Stwórzmy więc nową metodę, która będzie zwracała nam ciąg znaków przerobionej wartości argumentu, którą trzyma.
Szybka deklaracja
-(NSString *)convertValue;

I definicja w pliku .m
-(NSString *)convertValue
{
  NSMutableString *convValue = [[NSMutableString alloc] init];

  [convValue setString:value];

  // Now change the chosen characters
  [convValue replaceOccurrencesOfString:@"&" withString:@"&"
    options:NSLiteralSearch range:
NSMakeRange(0, [convValue length])];
  [convValue replaceOccurrencesOfString:@"<" withString:@"&lt;"
    options:NSLiteralSearch range:
NSMakeRange(0, [convValue length])];
  [convValue replaceOccurrencesOfString:@">" withString:@"&gt;"
    options:NSLiteralSearch range:
NSMakeRange(0, [convValue length])];
  [convValue replaceOccurrencesOfString:@"'" withString:@"&apos;"
    options:NSLiteralSearch range:
NSMakeRange(0, [convValue length])];
  [convValue replaceOccurrencesOfString:@"\"" withString:@"&quot;"
    options:NSLiteralSearch range:
NSMakeRange(0, [convValue length])];

  [convValue autorelease];

  return convValue;
}

I wszystko jasne! Ale potłumaczmy conieco z tego, co tutaj się dzieje. Szybko tworzymy sobie obiekt typu NSMutableString. Metoda replaceOccurrencesOfString: WithString: options: range: podmienia nam wystąpienia ciągu znaków na inny ciąg znaków. Ostatnie dwa parametry metody to dodatkowe opcje (u nas wybieramy dosłowne szukanie) i zakres - my chcemy szukać w całym ciągu. Dlatego za każdym razem zliczamy jeszcze raz długość ciągu znaków, ponieważ po każdej podmianie może on ulec zmianie.
Na samym końcu oczywiście robimy autorelease, żeby nie pogubić pamięci i zwracamy!

Teraz wystarczy nam jedynie zmodyfikować jedną linijkę z metody toString z:
retValue = [retValue stringByAppendingString:value];

na:
retValue = [retValue stringByAppendingString:[self convertValue]];


I to w sumie wszystko! Teraz już wszystko nam się przerabia jak trzeba.
ale...

NSMutableString to coś znacznie więcej! Pozwala na wiele innych operacji. Między innymi rozwiązuje problem konkatenacji ciągów znaków za pomocą metody:
- (void)appendString:(NSString *)aString


I tak oto powstają dwa zadania domowe!

Zadanie domowe dla leniwych



Zmodyfikować metodę toString tak, żeby korzystała z NSMutableString i konkateonowała ciągi znaków w miejscu, zamiast rozwalać się po niesamowitej ilości obiektów typu NSString.

Zadanie domowe dla gorliwych



Ponieważ to koniec lekcji podstawowych i przechodzimy do Cocoa, potrzebujemy jeszcze jednej klasy. Mianowicie klasy XMLNode. Nasza klasa będzie wyglądała tak:
@interface BXXMLNode : NSObject {
NSString *name;
NSString *lang;
NSString *nspace;
NSString *value;
NSMutableArray *attributes;
NSMutableArray *subNodes;
NSAutoreleasePool *pool;
}

// Constructors and destructors
- (id)init;
- (id)initWithName:(NSString *)nodeName;
- (void)dealloc;

// BXXMLNode attributes accessors
- (NSString *)name;
- (void) setName:(NSString *)newName;
- (NSString *)lang;
- (void) setLang:(NSString *)newLang;
- (NSString *)nspace;
- (void) setNspace:(NSString *)newNspace;
- (NSString *)value;
- (void) setValue:(NSString *)newValue;

- (void)addAttribute:(BXXMLAttribute *)attr;
- (void)addAttributeByName:(NSString *)attrName andValue:(NSString *)attrValue;
- (NSString *)getValueOfAttribute:(NSString *)attrName;

- (void)addNode:(BXXMLNode *)node;
- (BXXMLNode *)getNode:(NSString *)nodeName;

- (NSString *)toString;
@end

Jak łatwo zauważyć, korzystamy z dodatkowych bajerów, jak NSMutableArray, czyli modyfikowalnej tablicy. W tym wypadku jeden obiektow typu XMLAttribute a drugi obiektów typu XMLNode. Dla tych, którzy chcą się pobawić, podaję kilka dodatkowych wskazówek, które mogą uprościć życie:
  • NSString posiada miłą metodę compare
  • Zamiast importować inne nagłówki w nagłówkach warto użyć formuły @class XMLAttribute. Takie zagnieżdżone importowanie wydłuża kompilację, a tak szybko informujemy w innym miejscu, że taka klasa jest i i na pewno się o niej dowiemy.

Powodzenia! Bo już niebawem wchodzimy maczkowi w kakao :D

Dodaj komentarz. Poziom: 0; Kategorie: Mac OS X Obj-C Programowanie ; 11 lutego 2007 23:36:26.

Obj-C: O zarządzaniu pamięcią słów kilka, czyli piszemy dalej XMLAttribute

Kiedy ostatnio zostawiliśmy naszą nowo powstałą klasę, wiele jej jeszcze brakowało. Na razie mamy jedynie coś, co przechowuje nam dane, ale przecież nam potrzeba czegoś więcej, prawda? Chcielibyśmy, żeby nasza klasa zwracała siebie w postaci ciągu znaków:
<nazwa_atrybutu>='<wartość_atrybutu>'
Było by również miło, gdyby całość była poprzedzona spacją. Dzięki temu inna klasa, którą zbudujemy - XMLNode, będzie mogła gładko iterować przez wszystkie atrybuty i po prostu je dodać do swojego ciągu, który będzie wypluwała.
Ważne jest również, żeby zwracana wartość byla typu wygodnego w użyciu. Czyli już nie ciąg Ceowych znaków, tylko coś wygodniejszego. Coś maczkowego. Czyli NSString.

Spróbujmy zatem. Bo nie powinno to być przecież trudne, prawda? Dodajemy więc w nagłówku deklarację nowej metody:
-(void)toString;

A w implementacji:
-(NSString *)toString
{
  NSString *retValue = [[NSString alloc] initWithString:@" "];


  retValue = [retValue stringByAppendingString:name];
  retValue = [retValue stringByAppendingString:@"='"];
  retValue = [retValue stringByAppendingString:value;
  retValue = [retValue stringByAppendingString:@"'"];

  return retValue;
}

Proste, prawda?
Jak pewnie zauważyliście, nie zainicjowałem nowego obiektu NSString przez metodę init, tylko użyłem innego konstruktora, który w parametrze pobrał początkową wartość nowego ciągu znaków. Łatwo znaleźć informację o tej metodzie w dokumentacji. Z drugiej strony nam też przydały by się takie metody. Żeby od razu inicjować nasz obiekt z wartościami. Więc się da. Zaraz się dowiemy jak.
Druga metoda, którą tu użyłem to stringByAppendingString, która konkatenuje (czyt. łączy) ciąg znaków podany w parametrze (typu NSString * oczywiście) z ciągiem znaku obiektu, na którym wykonujemy metodę i zwraca nowy obiekt, który jest połączeniem ich obu.

Teraz tylko dopiszemy szybki teścik, który to sprawdzi, skompilujemy, odpalimy i...
Momencik.
Czego od nas chce ten XCode? Że pamięć gubimy? Jaką, kurde, pamięć?
No przecież! Za każdym razem, kiedy wykonujemy jedną z powyżej opisanych metod, tworzymy nowy obiekt. Oznacza to, że później musimy je zniszczyć. I zapewne jeszcze z metodami konkatenacji jakoś byśmy sobie poradzili, to kiedy chodzi o zwrócenie samego obiektu, to przecież nie możemy go zniszczyć. Musi być widoczny piętro wyżej!

Poza tym XCode coś nam tutaj napomyka o czymś co się nazywa NSAutoreleasePool...

NSAutoreleasePool jest taką miłą klasą, która rozwiązuje wszystkie nasze problemy.
Zamiast użyć metody release na obiekcie, możemy użyć metody autorelease. Wtedy obiekt samoczynnie zostanie dodany do najbliższego możliwego obiektu klasy NSAutorelease. Oznacza to, że jeżeli nasz obiekt będzie posiadał takie pole, to razem ze zniszczeniem go, zwolni pamięć po wszystkich obiektach, na których wywołaliśmy sami metodę autorelease. Co więcej, jeżeli została ta metoda wywołana gdzieś indziej - np w tym przypadku w klasie NSString przy wywołaniu metody stringByAppendingString - to również NSAutoreleasePool się nim zajmie.

Wspaniale! Prawda? Zatem jedyny obiekt, który nam przeszkadza teraz, to pierwotny NSString, który samodzielnie inicjujemy.

Zmodyfikujmy więc nagłówek dodając nowe pole do klasy:
NSAutoreleasePool *pool;
W konstruktorze zadbajmy, żeby go zainicjować:
pool = [[NSAutoreleasePool alloc] init];
A w destruktorze zniszczyć:
[pool release];
Za to naszą metodę zmodyfikujmy, by robiła tak: -(NSString *)toString
{
  NSString *retValue = [[NSString alloc] initWithString:@" "];

  [retValue autorelease];
  retValue = [retValue stringByAppendingString:name];
  retValue = [retValue stringByAppendingString:@"='"];
  retValue = [retValue stringByAppendingString:value;
  retValue = [retValue stringByAppendingString:@"'"];

  return retValue;
}
Owszem, nie zwalniamy "śmieci" powstających po drodze przy konkatenacji ciągów, ale z drugiej strony metodę toString wywołamy tak na prawdę raz, a potem zwolnimy całość, więc przy naszym zastosowaniu nie będziemy nawet się bawić zaciemniając kod metody.

Tym razem po kompilacji wszystko przeszło bez problemów, a metoda działa szybko i skutecznie dając nam to, czego potrzebowaliśmy.

Dodatkowe konstruktory



Pozostało nam tak na prawdę jedynie zatroszczyć się o dodatkowe konstruktory, które mogły by pobierać w argumentach początkową wartość dla nazwy i wartości atrybutu, którą obiekt ma reprezentować. Tutaj również sprawa ma się bardzo prosto.

Metoda, ktora ma inicjować, musi wykonać jedynie inicjację klasy po której dziedziczy, swoje własne i zwrócić siebie. Zatem nazwa metody inicjującej jest kompletnie dowolna! Dodajmy więc jeszcze dwie. W nagłówku zadeklarujmy:
-(id)initWithName:(NSString *)newName; -(id)initWithName:(NSString *)newName andValue:(NSString *)newValue;
Sama definicja jest już teraz super prosta:
-(id)initWithName:(NSString *)newName
{
  [self init];
  [self setName:newName];
  return self;
}

-(id)initWithName:(NSString *)newName andValue:(NSString *)newValue
{
  [self initWithName:newName];
  [self setValue:newValue];
  return self;
}


Teraz tylko kompilować i używać!

Podsumowanie



Udało nam się praktycznie skończyć naszą pierwszą klasę. Możemy tworzyć się na różne sposoby, wypluwać się do ciągu znaków, na jakim nam zależało i radzimy już sobie z pamięcią.
Niestety jednej rzeczy jeszcze brakuje.
Kiedy chcemy się budować do ciągu znaków dołączanego do źródła XML musimy zadbać, aby pewne znaki się tam nie pojawiły. Chodzi tu dosłownie o znaki: &, <, >, " i '. Musimy te znaki zamienić na odpowiednio: &amp;, &lt;, &gt;, &quot; i &apos;.

I właśnie tym - czyli manipulacją ciągów znaków zajmiemy się w następnym odcinku.

5 komentarzy. Poziom: 0; Kategorie: Obj-C Programowanie ; 04 lutego 2007 22:10:35.

Z czym się je ten Objective-C(e)? - odcinek 1

Tak jak już dziś obiecałem, Postaram się teraz wytłumaczyć jak działa ten dziwaczny język Obj-C. Całość będę wspierał przykładami, które wbrew pozorom nie będą wyrwane z kosmosu. Wszystko, co tutaj przedstawiam (może oprócz jakiś linijkowych przykładów) to moje próby ugryzienia tematu podczas pisania mojej aplikacji. Innymi słowy postaram się Was poprowadzić tą samą drogą, którą poszedłem ja.

Zawsze uważałem, że przykłady z życia wzięte są bardziej użyteczne. A co ważniejsze, to właśnie jak ja się uczę (bo nadal się uczę!) programowania w tym środowisku. Łatwiej mi przez to i Wam wytłumaczyć ten świat.

Zatem zaczynajmy!

Cel zadania na dziś



Ponieważ piszemy komunikator Jabbera (razem z biblioteką), musimy zacząć od samego dołu. Czyli struktur XML. Pomimo faktu, że Apple dostarczył całą masę interesujących bibliotek, jakoś takowej wrappującej XML nie odnalazłem. Z drugiej strony to wspaniale! Bo napisanie kilku klas do obsługi go, to znakomita wprawka przed prawdziwym programowaniem!

Pierwszą rzeczą, jaka będzie nam potrzebna (oprócz podstawowej klasy XMLNode) będzie malusia klasa do trzymania informacji o pojedynczym atrybucie encji. To będzie bardzo prosta i mała klasa, której będziemy ustawiać dwie wartości (nazwa atrybutu oraz wartość atrybutu) w postaci ciągu znaków. Dodatkowo klasa powinna mieć metodę zwracającą już sformatowany ciąg znaków, który będziemy mogli włożyć bezpośrednio do kodu XML tj nazwa_atrybutu='wartosc_atrybutu'.

Proste prawda?

Zatem zacznijmy od początku.

Podstawowe słowa kluczowe



W Obj-C - jak to z innym językiem bywa - nazwy plików również są inne. I tak jak przy C mamy rozszerzenie .c, a w C++ .cc lub .cpp tak w Obj-C mamy rozszerzenie .m. Każdy by się domyślił, prawda? :) Całe szczęście, że nagłówki mają standardowe .h. Tych formatów będziemy się trzymać, żeby nasz kochany gcc nie zgłupiał. jest to dość istotne, ponieważ złe rozszerzenie może się skończyć błędami w kompilacji.

Jak w każdym logicznym języku nagłówek powinien zawierać deklarację klasy (może być więcej niż jednej na raz, choć ja będę tego unikał), a plik .m jej implementację.

Wspominałem już, że oryginalnie Obj-C był dodatkowym preprocesorem nad C, prawda? No właśnie. Dzięki temu mamy dużo słów kluczowych, które pomagały owemu preprocesorowi to szybko przebudować na C. Posłużmy się jednak przykładem. Kod, który w znanym szeroko języku Java wygląda tak:
import com.wielkakorporacja.Foo;
import com.wielkakorporacja.Bar;

public class Blah extends Foo implements Bar {
... metody i atrybuty...
}
W języku Obj-C będzie wyglądać tak:
#import <wielkakorporacja/Foo.h>
#import <wielkakorporacja/Bar.h>

@interface Blah : Foo <Bar> {
...atrybuty...
}
...metody...
@end
Jak już od razu rzuca się w oczy wszystkie słowa kluczowe języka Obj-C zaczynają się od "małpki". Dodatkowo widać już z miejsca, że pierwsza część w klamrach zostanie gdzieś przetłumaczona na strukturę ANSI C, a to co jest poza nimi na tablicę metod.
Tak samo jak w języku Java, tak i tutaj możemy dziedziczyć tylko po jednej klasie na raz.

Problem jednak w tym, że w Javie możemy powiedzieć, który atrybut ma być publiczny, który prywatny, a który chroniony. Tu aż tak dobrze nam nie będzie. Wszystkie atrybuty są prywatne. Oznacza to, że dla każdego z nich będziemy zmuszeni stworzyć zestaw settera i gettera. I to według ściśle oznaczonej reguły, którą w tym ćwiczeniu jedynie opiszę. Wytłumaczenie przedstawię później, kiedy zaczniemy już bawić się z Cocoa.

Skoro już wiemy mniej więcej jak powinien wyglądać nagłówek - napiszmy go!
#import <Foundation/Foundation.h>


@interface XMLAttribute : NSObject {
  NSString *name;
  NSString *value;
}

// Konstruktory i destruktor
-(id)init;
-(void)dealloc;

// Settery i gettery
-(NSString *)name;
-(void)setName:(NSString *)newName;
-(NSString *)value;
-(void)setValue:(NSString *)newValue;

@end
Już widzę te miny. A cóż to takiego jest? wykrzykują grymaśne twarze. Pędzę zatem z szybkim wyjaśnieniem.

Zacznijmy od tego nieszczęsnego #import. Różni się od ceowego #include tym, że zawsze wywoła się tylko raz. Oznacza to, że podczas budowy programu nie musimy oklejać każdego nagółwka dodatkowymi ifdefami. Jeden nagłówek zostanie załączony podczas kompilacji tylko raz.
Sam początek z definicją interfejsu razem z zadeklarowanymi atrybutami, chyba niewiele wyjaśnienia wymagają. Dziedziczymy po najbardziej ogólnej klasie w systemie - NSObject (wszystkie klasy napisane przez Apple--ehm... NeXT zaczynają się od NS). Do przetrzymywania informacji o nazwie i wartości naszego XMLowego atrybutu wykorzystamy też już zaimplementowaną za nas klasę NSString. Poniżej zaś mamy deklarację metod naszej nowo konstruowanej klasy. Nie widać tego w tym przykładzie wyraźnie, ale notacja w tym języku jest mixfixowa.
Zupełnie jak w Smalltalku. Co za zbieg okoliczności! ;)
Minusik przed deklaracją oznacza, że dana metoda jest metodą publiczną. Gdyby zamiast minusa, był tam plus, oznaczałoby to, że metoda jest metodą prywatną, czyli do użytku przez sam obiekt. Zdaję sobie sprawę, że wszyscy wiedzą co to oznacza, więc oszczędzę sobie wyjaśnień.

Po oznaczeniu publiczności metody następuje zadeklarowanie zwracanej wartości w nawiasach. Zaraz za nią nazwa metody. Czy raczej jej części.
Dlaczego?
Ponieważ w notacji mixfixowej nazwa może być rozbita na fragmenty. Wszystko zależy od ilości atrybutów pobieranych przez metodę. Jeśli nie pobiera nic, po prostu podajemy nazwę i kończymy średnikiem. Jeśli chcemy jeden atrybut - po nazwie podajemy typ i nazwę danej zmiennej wewnątrz ciała funkcji poprzedzony dwukropkiem. W przypadku chęci deklaracji większej ilości, musimy podać dalszą część nazwy, znów dwukropek, typ, nazwa... itd.
Zamotałem?
Dobrze, spróbujmy zatem na przykładzie.
Zamiast javowego (postfixowego):

public void kanapka(String nazwa, bool ze_szczypiorkiem, int ilosc);
mamy (mixfixowe):

- (void)kanapka:(NSString *)nazwa zeSczypiorkiem:(BOOL)szypior wLiczbie:(int)ilosc;

Teraz jaśniej?
Uff. To dobrze :)

Tworzenie obiektów i ich niszczenie


Przejdźmy zatem do poważniejszego tematu, jakim jest idea alokacji i dealokacji pamięci potrzebnej na obiekty. W przeciwieństwie do bardziej obiektowej Javy, czy C++, żeby przygotować obiekt do pracy musimy wykonać dwie operacje: zaalokować pamięć na niego i go zainicjować. W naszym przypadku wyglądałoby to mniej więcej tak:
XMLAttribute *attr = nil;

attr = [XMLAttribute alloc];
[attr init];
Ojej. Chyba znów wprowadziłem pewne zamieszanie. Wykonanie metod też nie jest takie znów wprost :) Używa się do tego celu nawiasów kwadratowych, ujmując w nie najpierw wskaźnik na obiekt, po czym wywłowanie metody. Tak więc napierw alokujemy pamięć używając nazwy klasy jako obiektu, po czym inicjujemy go.
Trochę uciążliwe. Na całe szczęście metoda alloc zwraca wskaźnik na obiekt, tak samo jak metoda init, zatem możemy z pełnym luzem zamienić powyższy przykład na:

XMLAttribute *attr = [[XMLAttribute alloc] init];

Dlaczego znów z takim luzem? Ponieważ Obj-C ma zalążek wyjątków... Ale o tym napiszę innym razem.
Słucham? A. Co robi ten cały nil? No cóż. Powinniśmy używać niby NULL'a (w końcu to nadal praktycznie C), ale nil ma to do siebie, że jest obiektem i jak spróbujemy coś na nim wywłować to po prostu nic się nie stanie, a nie wypadniemy z programu z wielkim segfaultem :)

Trzeba jednak zrozumieć co tu tak naprawdę się dzieje. Funkcję init możemy sobie olać, jeżeli nie inicjujemy niczego swojego. Zostanie ślicznie zdziedziczona. W naszym jednak przypadku potrzebujemy utworzyć obiekty dla naszych atrybutów. Dlatego ją przeciążamy. To samo tyczy się metody dealloc, czyli naszego destruktora - tam musimy te obiekty zniszczyć.
Wbrew jednak pozorom do zwolnienia obiektu nie używamy metody dealloc tylko release.

Dlaczego?

Ponieważ Obj-C implementuje pewne dość prymitywne metody zliczania referencji do obiektu. Oznacza to, że każde wywołanie metody release zmniejsza licznik. Gdy osiągnie on wartość 0, dopiero wtedy obiekt zostanie zniszczony przez wywołanie jego metody dealloc. Taki mechanizm daje nam pewną wygodę. Kiedy dostajemy od kogoś obiekt, który chcemy sobie "potrzymać" zwiększamy licznik referencji wywołując na nim metodę retain.
Problem jednak powstanie, kiedy ZWRACAMY obiekt przez nas zallokowany. Ale i na to są metody, o których napiszę w następnej notce.

Definicja metod



Skoro mamy już zadeklarowaną klasę, pozostaje tylko zaimplementować metody. Zatem:
#import "BXXMLAttribute.h"

@implementation BXXMLAttribute

-(id)init
{
   [super init];

   name = [[NSString alloc] init];
   value = [[NSString alloc] init];

   return self;
}

-(void)dealloc
{
   [name release];
   [value release];

   [super dealloc];
}

-(NSString *)name
{
   return name;
}

-(void)setName:(NSString *)newName
{
   [name release];
   name = [newName copy];
}

-(NSString *)value
{
   return value;
}

-(void)setValue:(NSString *)newValue
{
   [value release];
   value = [newValue copy];
}

@end

Jak widać konstrukcja jest dość prosta. Znów mamy słowa kluczowe wokół definicji, znów powtarza się konstrukcja z nagłówka plus w miarę proste ciała metod. Jedyne co może się ewentualnie rzucić w oczy to fakt, że przy setterach zamiast retain wykonuję rzeczywistą kopię obiektu poprzez metodę copy. Dzieje się to tak dlatego, że w parametrze do wywołania metody mogę otrzymać nie zmienną, a stałą (w przykładzie testu poniżej jest przykład). W takim przypadku nasz prościutki silniczek do zliczania referencji zwyczajnie zgłupieje. A tego chyba byśmy nie chcieli :)

Tak, tak. Słyszę już te pytania. Więc odpowiem po krótce na kilka z nich:
Tak - id jest tzw. typem "coś". To może być cokolwiek. Tak piszą w ksiąźkach. Ja śmiem w to wątpić i uważam, że to po prostu wskaźnik na dowolny obiekt. Metody inicjalizujące muszą go zwracać.
Tak - self to to samo co Javove this.
Słowo kluczowe super oznacza oczywiście klasę, po której dziedziczymy. Przy inicjacji i niszczeniu należy je wywołać również z rodzica, bo przeciążamy w końcu jego metody.

Zatem zanim zakończę ten strasznie chaotyczny artykuł, napiszemy jeszcze razem szybki test naszej nowej klasy. Tworzymy więc dodatkowy plik o rozszerzeniu .m, a w nim wrzucamy zwykłą funkcję ceową main() w której (dzięki rozszerzeniu pliku) piszemy po "obiektowo-ceowemu"
#import "XMLAttribute.h"

int main()
{
   XMLAttribute *attr = [[XMLAttribute alloc] init];

   [attr setName:@"JakasNazwa"];
   [attr setValue:@"Jakas wartosc"];

   [attr release];
   return 0;
}
Przykład oczywiście bardzo prosty. Utworzyliśmy obiekt i ustawiliśmy jego atrybuty. I tym razem nasza "małpka" informuje nas o ingerencji preprocesora Obj-C, który zamieni nasz ciąg znaków na stałą typu NSString, a nie zwykłego ceowego char*.

Podsumowanie



Pomimo tego, że artykuł ten jest straszliwie chaotyczny i napisany niechlujnie, wierzę, że zdołaliście coś z niego zrozumieć. W razie jakichkolwiek pytań, proszę o komentarze. Odpowiem na każde pytanie :)

A już w następnym odcinku poprowadzę Was przez dalszą implementację, bo przecież:
  • Przydałby nam się konstruktor co bierze nazwę atrybutu od razu. Albo nawet taki, co brałby i nazwę i wartość!
  • Nadal brakuje metody do zwracania sformatowanego ciągu, więc klasa jest praktycznie bezużyteczna.
  • Na pewno przy tym narobi się sporo problemów od strony zarządzania pamięcią i musimy sobie z nimi jakoś poradzić. Tylko jak?
I właśnie o tym będzie w następnym odcinku krótkiej historii z produkcji małej aplikacji OpenSource o nazwie roboczej Jabbah

7 komentarzy. Poziom: 0; Kategorie: Obj-C Programowanie ; 03 lutego 2007 01:21:35.

Wprowadzenie: Fajnie, ale czemu taki dziwny język?

Każdy podręcznik zaczyna się od podziękowań i masy innych informacji na temat bardziej ogólny.
Uczynię to samo, w bardziej jednak skróconej formie. Chciałbym mianowicie skoncentrować się na jedynym elemencie, który może zainteresować ewentualnego czytelnika, czyli dlaczego ten cały Mac OS X używa tak kretyńskiego języka programowania jakim jest Obj-C?
Żeby zrozumieć ten dziwaczny fakt, trzeba się nieco zagłębić w historię firmy Apple tudzież historię jednego z jego założycieli.

Steve'a Jobs'a.

Jeśli więc interesuje Cię skąd ten pomysł i dlaczego się sprawdza - polecam dalszą część lektury.

Nie będę oszukiwał, że wiedzę tę posiadłem z powietrza. Wspieram się tutaj na informacjach z sieci, jak i materiale jednej z bardziej interesujących książek na temat programowania pod Mac OS X. Zainteresowanych głębszym zbadaniem tematu polecam jej lekturę. Jest to "Cocoa programming for Mac OS X" autorstwa pana Aaron'a Hillegass'a.
Traktuję to raczej jako wiarygodne źródło, ponieważ jest to były pracownik zarówno drużyny NeXT jak i firmy Apple.
Przejdźmy może jednak do konkretów.

Kiedy firma Apple zaczęła rozwijać się w zastraszającym tempie, współwłaściciele w osobach dwóch Steve'ów nie byli już w stanie kontrolować sami tego co w niej się dzieje. Idąc zatem po rozum do głowy zatrudnili z zewnątrz dyrektora - John'a Sculley'a. Doświadczony poprzez poprzednie firmy, ów dyrektor robił wszystko, żeby właściciele nie wtryniali się zbytnio w to w jaki sposób firma funkcjonuje. Wiedział dobrze o tym, że wprowadzi to więcej zamieszania niż pożytku.

Problem polegał na tym, że Steve'owi Jobs'owi podobało się to raczej średnio. Frustracja jego sięgała już zenitu, kiedy postanowił założyć sobie jeszcze jedną firmę, tym razem mniejszą. Firmę znaną początkowo jako NeXT Computers.

Główne cele firmy były dość podobne do celów Apple. Ale ze względu na braki w kadrach (firma miała pozostać mała) jak i wielu innych czynników, o których pisać nie warto, skoncentrowano się na jednym zestawie narzędzi do tworzenia aplikacji GUI. Ów produkt nosił nazwę NeXTSTEP.

Były to zamierzchłe czasy. Obiektowość była czymś nowym. Na język C++ więcej narzekano, niż chwalono. Chyba głównie z tych względów ekipa NeXT nie chciała się zdecydować na ten język. Do tego dochodził problem kompilowania. Nie wszystkie kompilatory do różnych procesorów posiadały możliwość kompilacji kodu C++. To jeszcze bardziej odstraszyło naszych innowatorów. Byli jednak tak zafascynowali ideą obiektowości, że szukali złotego środka. Okazał się nim właśnie język Objective C.

Dlaczego on? Ponieważ stworzono go na podstawie dwóch języków - ANSI C, który miał kompilatory na wszystko i Smalltalk'a - niedoścignionego ideału obiektowości, który jest tak idealny, że nie nadaje się specjalnie do niczego. Twórca - Brad Cox - zwyczajnie wziął język C, dorobił do niego preprocesor radzący sobie jakoś z teorią obiektowości i całość ochrzcił właśnie imieniem Objective-C.

Oczywiście wielu rzeczy nie da się tam zrobić, a znów inne wymagają od programisty wyjątkowej ostrożności. Ja sam, w sumie nie zakodowawszy zbyt wiele, popełniłem już taką masę prostackich błędów, że będę się starał je mocno podkreślać w serii tych artykułów, próbując ustrzec ewentualnych czytelników.

Mimo to język ma w sobie to coś i mimo faktu, że z początku straszliwie go nie lubiałem, teraz z dumą mogę stwierdzić, że z przyjemnością siadam do kodu. Wynika to zapewne z faktu, że większość życia programowałem wyłącznie w C, a C++ zdziebko mnie denerwował. Dla mnie osobiście jest to złoty środek, ale wiem, że możecie mieć w tym względzie inne zdanie. Nie będę próbował się nawet kłócić. Powiem jedynie, że ekipa NeXT też polubiła to rozwiązanie. Co więcej - idąc zupełnie inną drogą niż inni, nie stworzyli API polegającego na implementowaniu setek interface'ów i setki razy dziedzicząc po różnych klasach. Wręcz przeciwnie. Cała biblioteka NeXTSTEP opiera się głównie na dwóch szablonach projektowych. Delegacie i MVC.

Co więcej - takie podejście mnóstwo rzeczy uprościło. Pisanie prostych aplikacji zaczęło zajmować coraz mniej czasu. Pokochali to głównie programiści bankowi, którzy wiecznie pędzili z terminami w absurdalny sposób narzucanymi przez management. Dzięki temu NeXT Computers, które już zdążyło się przechrzcić na NeXT Software, zaczęło zarabiać. I to całkiem nieźle.

W tak zwanym międzyczasie, Apple doszedł do granic swoich możliwości. Postanowił porzucić pomysł, który miał na system wcześniej i stworzyć coś zupełnie nowego, opartego o mikro kernel BSD Mach. Brakowało im jedynie pomysłu na systemy okienkowe. Każdy kolejny projekt okazywał się fiaskiem. Postanowiono więc kupić jakiś z zewnątrz. Firma Steve'a była oczywistym wyborem. A dzięki faktowi, że firma była mała, a Apple duży... po prostu przejęto całość pod skrzydła z jabłuszkiem.

I tak właśnie system NeXTSTEP przechrzcił się na Cocoa. Jak szybko zauważycie, wszystkie klasy zaczynają się od literek NS, co nasuwa szybkie skojarzenie, że nawet nie chciało im się zmienić ich nazw. Mimo to sprawdza się znakomicie, a Mac OS X okazał się wielkim sukcesem.

Problem polega jednak na tym, że mimo prostoty tego języka i łatwości w jego używaniu, mało kto w Europie ma na jego temat jakiekolwiek pojęcie. A sami przecież wiecie, że na maczki softu OpenSource jest jak na lekarstwo.

Dlatego to piszę.

Chcę uruchomić ciekawość polskiego światka OpenSource i namówić, byście razem ze mną pokazali, że Mac OS X też jest wstanie mieć dużo fajnych programów :)

Będziemy poruszali się etapami. Wiele tajników jest i dla mnie jeszcze czarną magią, ale ilekroć przejdę jakiś kolejny próg będę się nim z Wami dzielił. Już dziś - podstawy języka Obj-C a jutro pierwsze kroki w Cocoa.

6 komentarzy. Poziom: 0; Kategorie: Cocoa Mac OS X Obj-C Programowanie ; 02 lutego 2007 17:26:49.