von Java zu Objective-C

veröffentlicht am : Do, 24. 10. 2013 geändert am: Do, 24. 10. 2013

Kategorie: Apple --> Computer

Schlagworte:


Ich arbeite ja nun schon seit einigen Jahren mit Java und habe da so manches größeres und auch kleineres Projekt verwirklicht (einige OpenSource-Projekte wie Morphium (link2) und auch kommerzielles wie CalugaMed für Arztpraxen oder Physiotherapeuten oder natürlich holidayinsider.com). Ich hab auch schon einige Dinge für Palm gebaut - aber das war ein anderes Zeitalter.

Dennoch hat mich die Entwicklung auf iOS interessiert, seit das iPhone seinen Siegeszug durch die Mobiltelefonindustrie gestartet hat. Ich hatte nur nie wirklich die Zeit mich ausführlich mit Objective-C auseinander zu setzen.

Seit wir auch in der Firma anfangen, eigene Apps zu entwickeln, musste ich mir doch mal die Zeit nehmen und mir das ganze etwas genauer zu Gemüte führen.

Ich hab ja gerade angefangen, den RSA-Algorithmus auf Objective-C zu implementieren, und meine Erfahrungen möchte ich hier gerne mal zusammenstellen. Das hier soll aber nur ein paar Hinweise geben und vielleicht ein wenig Angst bei Neulingen abbauen ;-)

Außerdem vergleiche ich hier Java auf dem Desktop bzw. Java auf dem Server mit Objective-C auf dem Desktop (OSX) bzw. dem Handy. Eigentlich müsste man noch einen Vergleich zw. Java/Android und Java/iOS ziehen - der kommt vielleicht später ;-)

Es ist eindeutig nicht so, dass das hier ein Einführungskurs in Objective-C sein soll, noch ist das hier vollständig. Aber vielleicht zeigt es dem ein oder anderen einen Weg, wie man in das Coding für iOS einsteigen kann.

Ähnlichkeiten und Unterschiede Objective-C und Java

Beides ist objektorientiert. Man kann in Objective-C also genauso objektorientiert arbeiten, wie in Java. Es gibt einige Strukturen, die sind syntaktisch ziemlich ähnlich und helfen es einem, sich schnell "zuhause" zu fühlen. Also ein IF ist in Objective-C eigentlich gleich wie in Java... Schleifenkonstrukte sehen sich auch sehr ähnlich. Auch die Primitiven Datentypen sind gleich. Also zumindest mit einfachen Aufgaben sollte man da keine Probleme haben.

Strings werden in Objective-C allerdings schon anders geschrieben. Da muss man wissen, dass in C ein String in der form "ein String" eigentlich ein an dieser stelle eingefüges array von Bytes ist, das zufällig diesen String ergibt. ist dann in c normalerweise auch so was wie:

[codesyntax lang="c"]

char* string="Halo Welt";

[/codesyntax]

Das ist also KEIN Objekt oder eine Struktur oder sonst was - diese Zeichenketten sind C-Zeichenketten und müssen auch mit C-Funktionen bearbeitet werden (wie strlen, strcopy etc).

In Objective-C benötigt man aber Instanzen von Objekten, man möchte also eine  Instanz eines NSString.

Das geht durch anstellen eines @ vor das erste Anführungszeichen:

[codesyntax lang="objc"]

NSString *str=@"Hallo Welt";

int len=[str length];

[/codesyntax]

Nur wenn man ein Objekt bzw. die Instanz einer Klasse hat, kann man diesem auch Nachrichten senden (in Java Jargon "Methoden aufrufen"), in diesem Fall die Nachricht "length" - also gib mir die Länge von dem String.

Ein sehr praktisches Feature sind die Categories in Objective-C. Damit kann man quasi bestehende Klassen um Funktionalität erweitern, ohne sie Abzuleiten. Das ist wirklich praktisch und sehr mächtig. Macht den Code etwas übersichtlicher und man kann damit zusätzlich zur Vererbung arbeiten. Das ist auch ein Feature, dass in in Java gerne hätte....

Null-Pointer

Da unterscheiden sich die beiden Sprachen wirklich erheblich (ich beziehe mich hier auf Objective-C, nicht C/C++).

In Objective-C heißt der Null-Pointer (der in java ja bekanntlich null heitß): nil

Es gibt aber auch noch den guten alten Nullpointer von C namens NULL! Das sind aber verschiedene Dinge, bitte nicht verwechseln. Daumenregel: benutze ich OBjective-C API dann immer mit nil arbeiten, bei C-Aufrufen mit dem entsprechenden Pendant.

Ok, das wäre ja nicht so dermaßen seltsam, ABER: Alle Nachrichten, die an nil gesendet werden, liefern nil zurück (und tun natürlich nix)! Es gibt keinen Fehler zur Laufzeit! Nur einfach kein Ergebnis! Zum Vergleich: in Java gibt es da eine Nullpointer-Exception - einer der beliebtesten und weit verbreitetsten Fehler schlichthin ;-)

Das kann sehr nervig zu debuggen sein, und man sollte des öfteren mal ein "if (irgendwas==nil)" einfügen, um sicher zu sein.

Aber: man kann sich leider nicht immer drauf verlassen, dass nil einfach funktioniert und nix tut, einige Zugriffe klemmen dann doch und werfen Fehler (meist BAD ACCESS oder so): z.B. wenn man versucht, auf einem nil-Pointer mit Array-Index zuzugreifen -> Boing!

Das macht die Sache etwas komplizierter. Ich fand den Effekt am Anfang eigentlich ganz nett, Juhuu, nie Wieder Nullpointer-Exceptions. Zumindest erweckt das Konzept den Anschein. Das ist zwar voerdergründig richtig, verursacht aber beim Debuggen des Öfteren mal Kopfzerbrechen - "warum ist denn das NIL???? hä?"

Kleiner Tipp: falls nil Eingaben nicht erlaubt sind, prüft das entsprechend und schmeißt eine Exception.

Syntaxvergleich

mir persönlich liegt die Syntax von Java mehr, aber das mag auch daran liegen, dass ich mehr mit Java gemacht habe und vor allem kein amerikanisches Tastaturlayout verwende. Denn Objective-C ist gespickt mit eckigen Klammern.

hier mal ein Beispiel in Objective-C:

[codesyntax lang="objc"]

BigInteger *i = [[BigInteger alloc] init];
BigInteger *result= [i add:[BigInteger valueOf:@"AFFE" usingRadix:16]];
NSLog(@"this is the result %@",result);

[/codesyntax]

und jetzt der identische Code in Java

[codesyntax lang="java"]

BigInteger i=new BigInteger(); BitInteger result=i.add(BigInteger.valueOf("AFFE",16)); System.out.println("this is the result "+result);

[/codesyntax]

an den * kann man die nahe Verwandtschaft zu C/C++ erkennen. Für alle Nicht-Eingeweihten: * bedeutet so viel wie "Zeiger auf".

man erkennt, wie "geschwätzig" Objective-C sein kann. Das kann wirklich etwas nervig werden. Was dabei vor allem nervt, ist, dass die Parametertypen nicht mit in die Signatur aufgenommen werden. Definiert man in java zwei Methoden mit verschiedenen Parametern, werden die auch getrennt behandelt. In Objective-C wird nur der Name als Methodensignatur verwendet. (also im obigen Beispiel valueOf:usingRadix:). Da tippt man sich evtl. die Finger wund.

Auch meckert Objective-C nicht unbedingt laut genug an (meistens wird es nur als Warning angezeigt), wenn man den falschen Parameter übergibt - anstelle eines Zeigers z.B. ein "long". Das bedeutet dann in diesem Fall, er interpretiert meinen "long"-Value als Zeiger irgendwo in den Speicher, wo er natürlich nix zu suchen hat => Crash!

Dafür gibt es in Objective-C einige Features, die ich mir in Java so einige Male wünschen würde… z.B. Blocks:

[codesyntax lang="objc"]

dispatch_block_t block = (dispatch_block_t) ^{ //Do Something here… };

[/codesyntax]

Solche Blocks können dann als Funktionsparameter z.B. übergeben werden oder auch benutzt werden, um mit "Grand Central Dispatch" Dinge zu parallelisieren (Ein super praktisches Feature auf OSX und iOS).

In Java muss das Gleiche über Interfaces und Anonyme Klassen geregelt werden, was aber nicht ganz das selbe ist - funktioniert aber auch.

Leider ist der Aufruf der Blöcke nicht Objective-C konform, sondern eher wie C... also in dem Oberen Beispiel würde man das aufrufen mit block(); Das kann schon verwirrend sein.

Alles in Allem muss man sagen, dass die Syntax von Objective-C doch ganz schön gewöhnungsbedürftig ist und die "Geschwätzigkeit" ist nicht immer angenehm.

Speichermanagment

Java hat da eindeutig die Nase vorn. Das Prinzip des Garbage Collectors ist schon super und funktioniert nahezu problemlos auch bei speicherintensiven Aufgaben in Java. In Objective-C gibt es so was ähnliches, es wird ein "AutoReleasePool" verwendet. Im Endeffekt ist das das gleiche, wie der Garbage Collector in Java, funktioniert intern nur etwas anders. Diesen Autorelease-Pool gibt es seit iOS 6 auch endlich auf iPhones, vorher war das nur Mac-Programmen vorbehalten. Damit muss man sich nicht mehr um das Speichermanagement kümmern.

Da war es so, dass jede Instanz eines Objektes, die man erstellt, retained wird, d.h. der UsageCounter wird um eins erhöht. Braucht man die Instanz nicht mehr, reduziert man den Counter mit release. Jeder kann sich denken, dass das binnen kürzester Zeit zu echt Komplexen Problemen und Speicherlöchern führt… eine Methode liefert ein Objekt zurück, welches vorher von irgendwo gekommen ist. Wer retained, wer released? Weis man das immer?

Ein Problem ganz anderer Art ist das Speichermanagement von Nicht-Objective-C strukturen. Dieser "Autoreleasepool" oder auch das Retain/Release Usage-Counting funktioniert natürlich nur für Objecitve-C eigene Objekte. Das bedeutet, sobald man z.B. auf eine C/C++ Funktion zurückgreift, wird Objective-C's Autoreleasepool davon nix wissen (können). Da ist man wieder selbst gefragt, den Rambedarf zu reduzieren und einmal allokierten Speicher wieder freizugeben.

Coding Speed

wie schnell ist man denn so im Vergleich beim Coden…. Das hängt sehr stark davon ab, was man eigentlich tut. XCode ist eine echt super Entwicklungsumgebung und ist wirklich einfach zu benutzen. Auch wenn der Code Editor  so seine Schwächen hat (da empfehle ich mal einen Blick auf AppCode zu werfen). Für die "normalen" Apps, die ja sehr GUI-Intensiv sind, ist das super. Und um einiges Besser als die meisten GUI-Designer, die für Java verfügbar sind.

Ich bin natürlich auch in Java auch deshalb schneller, weil ich viel mehr Erfahrung dort habe. Ich kenne die nötigen Frameworks, Konzepte und Ideen um meine Probleme umzusetzen oder die Herangehensweisen einschätzen zu können. In Objective-C tu ich mich da noch etwas schwer, mit gerade mal ner Handvoll Apps Erfahrung. Dennoch viel mir die Einarbeitung bisher noch recht leicht, mit der Java-Erfahrung im Kreuz ist eigentlich nur die Syntax ein wenig ungewohnt...

Wenn ich lange Objective-C gecoded habe und dann wieder Java programmiere, kommt mir das komisch vor ;-)

Alles in Allem würde ich sagen ist man bei einigen Aufgaben mit Objecitve-C bzw. XCode deutlich schneller, bei anderen even in Java... das hält sich in Etwa die Waage.

ABER: sobald man anfängt Objective-C mit C/C++ zu mischen und sonstige "Schweinereien" zu machen, kann insbesondere das debugging ein Graus sein!

Anmerkung: Speed

da wir gerade beim Thema sind... ich war SEHR erstaunt, was ich über die Ausführungsgeschwindigkeit herausgefunden habe. Es geht hier dabei um das erstellen von 2048Bit RSA-Schlüsseln, sowohl in Objective-C als auch in Java. Weiter infos dazu findet ihr hier.

Ich habe bei der Implementierung den Code von Java nach Objective-C portiert (auch deswegen, weil ich die ganzen RSA-Implementierungen in c/c++ nicht kapiert hab ;-))

Der Algorithmus ist also identisch. Ich habe zunächst einen sehr objektorientierten Ansatz zur repräsentation der BigInteger-Objekte genutzt. Die Zahlenwerte werden in der Java-Implementierung als int-Array abgelegt. Meine erste Objective-C Implementierung nutzt ein NSMutableArray und NSNumber.

Das war nicht so clever. Der Code war verständlich, aber die Ausführungsgeschwindigkeit desolat! Objective C war in diesem Fall ca. 100x langsamer als Java. Beim identischen Algorithmus!

Das hat micht schon erstaunt... Ich habe dann die Implementierung noch mal korrigiert und anstelle von NSNumbers und NSMutableArrays auch int-Arrays verwendet. Dummerweise muss man sich dann komplett selbst um das Speichermanagement kümmern.

Damit konnte ich den Speed immerhin auf Faktor 3 erhöhen - Also die Objective-C Implementierung war "nur" 3x so langsam, wie die Java Implementierung!!

Dank dem Einbinden von GCD ist es jetzt "nur" noch 1,5 bis 2x so langsam wie Java - Aber java bleibt DEUTLICH flotter.

Dabei gilt noch was anzumerken: der Erste Durchlauf in der Schleife zum Erstellen einer Primzahl in Java ist um ca. Faktor 60 Langsamer als in Objective-C... das ändert sich aber rasch, wenn der Just-In-time-compiler eingreift und dann auch noch den Code optimiert. Danach landet man auf ca. 2x schneller.

Ich bin nicht der Meinung, das Objective-C langsamer ist als Java... allerdings sollte man sich beim Coden doch seine Gedanken über Performance machen. Offensichtlich gibt es beim Coden mit Objective-C so einige "Fallstricke" und "Bremsen" auf die man achten muss. Und die Algorithmen müssen vermutlich noch mal genauer auf Performance untersucht werden. Ich weiß nicht, in wie weit es Optimierungspotential in den Einstellungen gibt, aber so gravierend werden die sich vermutlich nicht auswirken.

Nachtrag dazu: Was beim Arbeiten mit Objective-C ganz besonders positiv auffällt ist, die Startup-Time! Also, nachdem man etwas kompiliert hat, bis es dann wirklich läuft (mal abgesehen vom iOS-Simulator). Aber da ist Objective-C bestimmt 10x schneller als Java!

Anmerkung: Platformunabhängikeit

Das war auch so ein Ding... Ich hab die RSA und BigInteger implementeriung erst für OSX gemacht - sollte ja kompatibel (zumindest weitgehend) zu iOS sein... als der code unter OSX endlich lief, hab ich das ganze versucht auf iOS zu starten... CRASH!

Warum: in iOS ist ein int 32 Bit Breit, in OSX 64! Deswegen musste ich den ganzen code noch mal anpassen unter verwendung von int32_t und int64_t... das hatte auch noch mal ein paar Fehler zu Tage gebracht, aber jetzt ist's auch in iOS lauffähig.

Also: achtet auf eure Datentypen! Die sind nicht immer und überall gleich (alte C-Weisheit, die auch auf Objective-C zutrifft).

 

Fazit:

Kann man überhaupt ein Fazit ziehen? Hier vergleicht man nun wirklich Äpfel mit Birnen. Und beides ist irgendwo gut, beides hat so seine Macken. Spass macht beides, bei beidem muss man lernen. Insbesondere die zugrunde liegende Laufzeitumgebung macht noch eine Menge aus und da finde ich Java im Momemnt etwas "aufgeräumter" - auch was Dokumentation und so betrifft.

Aber ich denke, jemand der zumindest schon mal mit objektorientierten Programmiersprachen gearbeitet hat und am besten schon mal C-Code gesehen hat, der tut sich mit Objective-C anfangs recht leicht - bei komplexeren Dingen wird die Sache in jeder Programmiersprache haarig.

 

 

erstellt Stephan Bösebeck (stephan)