A short Croatian language opinion post on use of Objective-C in games
Dobio sam nedavno pitanje o tome da li se isplati učiti Objective-C (u kontekstu igara).
Moje je mišljenje da Objective-C ima deset puta logičniju internu strukturu nego C++, te da je svojom kombinacijom karakteristika dinamičnih i statičnih jezika izuzetno pogodan za pisanje igara. Primjerice, evo stvaranje kapitalnog svemirskog broda koristeći string, i spremanje istog u SvemirskiBrod
:
NSString* nekaj = @"KapitalniSvemirskiBrod"; SvemirskiBrod *nekiBrod = [[NSClassFromString(nekaj) alloc] initWithPosition:CGPointMake(10, 10)];
Ovo je jako dobro jer se ovakva XML specifikacija mape:
može lako učitati s primjerice (ako imamo teoretsku klasu XMLNode
i varijablu trenutniNode
):
NSString *tipBroda = [trenutniNode atribut:tip]; CGPoint pozicija = CGPointMake([trenutniNode atribut:x], [trenutniNode atribut:y]); Class klasaBroda = NSClassFromString(tipBroda); SvemirskiBrod *brod = [[klasaBroda alloc] initWithPosition:pozicija];
Tu su i druge mogućnosti, tipa lakše pozivanje funkcije nad objektom u nekom mini-skriptnom jeziku koji smisliš (bez razmišljanja o statičkim funkcijama, callbackovima, member-function-pointerima i slično, kao u C i C++).
Pozivanje nepostojeće funkcije (npr [svemirskaStanica udjiUHyperspace];
) je samo warning, što znači da se tebi vjeruje da će ta funkcija postojati u toj varijabli. Ako ne postoji, onda će se program skršiti. Postoji provjera da li neka varijabla sadrži neku funkciju.
Ekvivalent std::vector
a je NSArray
koji unutra može spremati bilo kakve Objective-C objekte. Znači ne moraš templateove slagati i forsirati da se unutra nalaze samo, primjerice, NSString
ovi, nego se unutra mogu nalaziti i SvemirskaStanica
i SvemirskiBrod
i KapitalniSvemirskiBrod
.
Uglavnom, iz svega navedenog vidi se da smatram da je Objective-C odličan jezik koji spaja najbolje stvari iz Pythona i C/C++. S obzirom da se u Objective-C može pisati i za Windowse i za Linux koristeći Cocotron i GNUstep, te konačno za Mac i iPhone koristeći Appleove Cocoa i Cocoa Touch, mislim da je to dovoljno portabilan jezik za razvoj igara.
Što se tiče aplikacija, odlično je i za izradu toga, ali relativno manje portabilno ako gledamo naprednije mogućnosti frameworka.
Dakle, da, isplati se 🙂
–
via blog.vucica.net
Ovaj prvi primjer s instanciranjem preko imena razreda u stringu, ajd ok, u C++u se mora ručno pisat preslikavanje iz stringa u razred. Al ak ti "klase" u XML-u nisu istog naziva kao u kodu, da li i onda moš proć bez vlasoručnog pisanja factorya?
Primjer s nepostojećom funkcijom je li-la jer osobno smatram da je loše pozivat funkciju koja ne postoji. Neki editori omogućavaju da si sam stavljaš takve "warninge", npr. u Eclipse za Javu ističe linije s //TODO komentarom na isti način kao i errore i warninge. Tako da ako ne želiš da ti kompajliranje puca zbog nepostojeće funkcije a ne želiš odmah implementirati tu funkcionalnost, onda samo u prazno tijelo funkcije staviš //TODO.
Primjer s NSArrayem, da li moraš castat ako elementu niza želiš pristupiti kao objektu nekog razreda? U C++u možeš imat std::vector spremat nutra bilo šta. S druge strane, održivi kod treba biti dizajniran tako da je čim manje specijalnih slučajeva. To znači da bi vector u koji idu različiti tipovi, trebao biti templetiran po nekom baznom tipu (apstraktnom razredu, ne void *) te da se maksimalno koristi dinamički polimorfizam.
1. Naravno da, ako ti se imena klasa ne slažu s onime u data fajlovima, moraš pisati translator iz onoga što je u fajli i obrnuto. No to zaobiđeš tako da — vidvraga — složiš klase i XML na način da imaju ista imena u kodu i fajlama 🙂
Zar je za čuditi se što ako u HTMLu nekom elementu dam id
<div id="pero">
poslije trebam u Javascriptu pristupiti tom elementu sdocument.getElementById('pero')
? 🙂2. Naravno da je loše pozivati funkciju koja nije deklarirana (koja ne postoji). Zato su tu warninzi! Zato ćeš na odgovarajućem mjestu definirati odgovarajući tip podataka i zbog warninga pripaziti da ta funkcija bude deklarirana u
@interface
u. Zatim će te opet warninzi upozoriti da funkcija nije definirana u@implementation
u.Dok god se držiš best practices, nemaš problema.
Pa gdje je onda korist? Korist je u tome da imaš generički tip podataka
id
, efektivno ekvivalentan tipu podatakaNSObject
koji je bazna klasa za sve objekte u Objective-C. Taj tip podataka namijenjen je upravo tome da se koristi kada ti "garantiraš" da znaš što radiš. Primjerice:Tu naravno slijedi napomena da ako računam da su unutra
NSString
ovi, ljepše je to i navesti u deklaraciji varijableobj
u for petlji. Tada ću imati sve one lijepe provjere i sl. Zanimljivo je da će stvar raditi i ako su unutra spremljeni objekti koji nisuNSString
— nema provjere.Sad bih se mogao vrtiti u krug. No, cijela priča oko ove dinamike je slična kao kod Pythona… samo što actually imam nekakvu compile-time provjeru, pa da barem warninge dobijem. Dakle, relaksiranost i dinamičnost Pythona uz neobavezne, ali uobičajene i poželjne compile-time provjere nekog klasičnog kompajliranog jezika.
I naravno, možda najbitnije u kontekstu tvojeg komentara, a što nisam naveo u tekstu: ako funkcija ne postoji na instanci objekta, bacit će se exception koji, s obzirom da exceptione ne uobičavam handleati u Objective-C (i malo je nespretno bilo do Objective-C 2.0) znači i kršenje i kritičnu pogrešku. Slično kao kod Pythona. Što znači da čovjek bude oprezniji. Za razliku od Pythona, gdje je uobičajeno staviti
try .. except
blok i prešutiti grešku.3. Ne moraš castati.
NSArray
u sebi držiid
eve, ili bolje rečenoNSObject
e. Usudio bih se reći da on čuva samo pointere (jer efektivno to i radi), no s obzirom da prilikom svakog poziva na-[NSArray addObject:]
automatski se radi poziv[prviArgument retain];
, to znači da bi se na pointeru koji nije Objective-C objekt program skršio. I to ružno, jer nema lijepog načina da se detektira da li je pokazivač Objective-C objekt.std::vector
ne može držati bilo što — on može držati samo one objekte koji su mu definirani u predlošku. Što znači da moramo definirati baznu klasu i držati unutra pokazivače na derivirane objekte… što je, kao što vidiš, vrlo sličan princip. Nažalost, problem nastaje kada unutra moraš držati third-party objekte koji ne slušaju tvoje principe. Problem nastaje i u tome da sestd::vector
neće automatski brinuti za tvoj reference counting (retain/release cycle)… što znači da ako želiš da se bar za tvoje objekte brine, sad već moraš raditi novi container.A da ne pričamo o tome da u tom slučaju, cijela poanta templateiranja postaje apsurdna i bespotrebna — jer si iovako ionako htio napraviti generički spremnik, što znači da su svi objekti izvedeni iz jedne bazne klase, što znači… da. To znači da se u C++ pokušava imitirati Objective-C, a bez sve njegove dinamike i koristeći nespretni RTTI koji nosi svoje probleme.
Ti pak kažeš kako je za održivi kod nužno specificirati tip podataka i držati ga se vrlo usko. U Objective-C nisam se susreo sa situacijom gdje nisam mogao nazvati varijablu, primjerice,
NSArray* personArray;
i u njoj čuvati instance klasePerson
. Tu se vidi da je očito što se unutra drži. Stoga održivost po mom mišljenju ne dolazi u pitanje.