Apple Swift – Eine moderne Programmiersprache
Mit der Programmiersprache Swift hat Apple ein modernes Werkzeug für die Anwendungsentwicklung für Mac und iOS bereit gestellt. Diese Sprache löst das von vielen Entwicklern als etwas gewöhnungsbedürftig und sperrig betrachtete Objective C ab. Mit der aktuellen Version 3 steht eine erste vollständige und unabhängige Implementierung zur Verfügung. Die anstehende Version 4 wird weitere Features einführen, jedoch keine Änderungen an der Core-Sprache mehr vornehmen.
Swift wurde von Apple als Open Source-Initiative freigegeben und steht damit auch für andere Plattformen zur Verfügung, z.B. für Ubuntu. Eine durchaus beachtenswerte Abkehr von der bisherigen Abschottung der Apple-Produkte. Allerdings gilt dies natürlich nur für den Kern der Sprache, die App-Programmierung muss aus Lizenz-Gründen weiterhin unter Apple Hardware und Betriebssystem erfolgen. Diese Einschränkung ist für diesen Artikel allerdings nicht relevant, da ich mich hier bewusst auf den Vergleich der Programmiersprachen konzentrieren möchte; für einen (durchaus interessanten!) Vergleich von Google Android und Apple Swift gibt es andere Artikel.
Swift vereinigt etablierte Konzepte aus Sprachen wie Java, C#, JavaScript und natürlich auch Objective C und ergänzt diese durch eigene Ideen. Damit finden Programmierer einesteils recht viel Bekanntes, aber eben auch unerwartete und damit überraschende Konstrukte. Hier möchte ich auf die Unterschiede zu Java eingehen, die aus meiner Erfahrung heraus relevant sind.
Swift und Java – Welche Konzepte sind interessant?
Für Java-Entwickler ist Swift auch deshalb interessant, weil einige Konzepte durchaus auch in einer zukünftigen Java-Version Einzug halten könnten. Weiterhin gibt es doch eine Reihe von Besonderheiten, die den Einstieg in Swift erschweren könnten.
Konsequente Type Inference
Swift ist wie Java streng und statisch typisiert. Damit prüft der Swift-Compiler, dass einer einmal deklarierten Variable kein Wert eines anderen Datentyps zugewiesen werden kann.
Allerdings ist eine Typ-Angabe bei der Deklaration optional; der Typ kann meistens aus dem Typen der Zuweisung bestimmt werden:
var message = "Hello" //message is a String message = "Hello!" //OK //message = 42 //Compiler Error var message2:String //No type inference
Optionals
Sehr gut gelöst ist meiner Meinung nach der Umgang mit Null-Werten, in Swift nil
genannt. Einer Variablen muss ein Nicht-Null-Wert zugewiesen werden, es sei denn, sie wird explizit als Optional-Typ deklariert:
var message = "Hello" //message non optional String //message = nil //Compiler error var message2:String? = "Hello" //message2 an optional String message2 = nil //OK
Optionals sind in Swift eigene Typen, so dass der Compiler bei Zuweisungen die normale Typ-Prüfung durchführt und Fehler erkennt:
//message = message2 //Compiler error: String? not of type String
Optionale Typen müssen vor ihrer weiteren Verwendung “ausgepackt”=unwrapped werden. Dafür gibt es in Swift zwei Möglichkeiten:
- Explizites Unwrap mit dem
!
-Operator - Sicheres Unwrap über die
if -let
-Konstruktion:
message = message2! if let x = message2 { print (x) }
Obwohl diese Sequenzen für einen Java-Entwickler erst einmal merkwürdig aussehen liegt der Vorteil auf der Hand: Bereits der Compiler erkennt Zuweisungen von Null-Werten, so dass der Entwickler auf null
-Prüfungen oder das Fangen einer NullPointerException
verzichten kann.
Und dann haben wir noch das “Optional Chaining” beim Zugriff auf Eigenschaften eines Objekts:
if (message2!.hasPrefix("H"){ //Unsafe //... } if (message2?.hasPrefix("H"){ //Safe //... }
Falls message2 nil
sein sollte wird die hasPrefix
-Methode nicht ausgeführt.
Klassen und Strukturen in Swift
Swift kennt zwei unterschiedliche Definitionen eines benutzerdefinierten Datentyps: Klassen und Strukturen.
struct Address{ var city: String, street: String }
class Person{ private var name:String init(name: String){ self.name = name } var Name: String{ get { return name } set { print ("setting lastname...") name = newValue } } func info() -> String { var greeting = "Hello, my name is \(name)" return greeting } }
Während Instanzen von Klassen wie in Java stets über Referenzen angesprochen werden, werden Strukturen immer als Werte angesehen:
let address1 = Address(city: "München", street: "Marienplatz") let address2 = address1 let person1 = Person(name: "Hugo") let person2 = person1
Ganz besonders verwirrend für Java-Entwickler ist, dass alle Collections-Implementierungen in Swift als Strukturen realisiert wurden!
Weitere Besonderheiten sind eher unter “syntactic sugar” zu sehen:
- Einfache Definition von Properties durch
get
– undset
-Blöcke - Bei Strukturen werden die Konstruktoren automatisch angelegt
Extensions
Alle Klassen und Strukturen sind in Swift “Open for Extension”. Das bedeutet, dass auch ohne Vererbung vorhandene Klassen jederzeit erweitert werden können. Nur zur Klarstellung: Dies gilt selbstverständlich auch für die Klassen der Standard-Bibliothek!
Zur Definition wird das Schlüsselwort extension
benutzt. Im Folgenden eine (zugegebenermaßen triviale) Erweiterung der Person
-Klasse sowie ein neuer Konstruktor für String
:
extension Person{ func sayHello{ return "Hello", } } extension String{ init?(_ address: Address){ self.init("Die Adresse \(address.city)") } }
Extensions können eine Klasse nicht mit neuen Attributen ausstatten. get
– und set
-Blöcke können jedoch verwendet werden.
Verschiedenes
- Neben Klassen und Strukturen unterstützt Swift auch Enums und Tuples
- Als Zeichen für Namen von Variablen oder Funktionen können beliebige Unicode-Zeichen benutzt werden.
- Funktionsparameter haben eine internen und gegebenenfalls einen öffentlichen Namen, der beim Aufruf mitgegeben werden muss:
func func1(message: String){ print(message) } func1(message: "Hello") //func1("Hello) //compiler error func func2(message m: String){ print(m) //print (message) } func2(message: "Hello") //func2("Hello) //compiler error //func2(m: "Hello) //compiler error
Sollen die Parameter nicht benannt sein wird der Unterstrich als öffentlicher Name benutzt:
func func3(_ message: String){ print(message) } func3("Hello") //func3(message: "Hello") //compiler error
- Die Swift-Installation enthält eine REPL, so dass Sequenzen und einfache Anwendungen auch ohne Installation einer Entwicklungsumgebung ausgeführt werden können.
- Schnittstellen heißen in Swift
protocol
, nichtinterface
. - Mit der String Interpolation werden Variablen bzw. Ausdrücke direkt in Zeichenketten ausgewertet:
let multiplier = 3.0 let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)" // message is "3 times 2.5 is 7.5"
- Operatoren können jederzeit geändert oder für eigene Datentypen definiert werden
func + (left: Int, right: Int) -> Int{ //just for fun return left - right } func == (left: Person, right: Person) -> Bool{ //all people are equal return true } func + (left: Person, right: Person){ //adding to people = marriage, suggestions for '-'? left.marry(right) }
- Auch neue Operatoren sind einfach zu deklarieren. So wird im folgenden Beispiel das Unicode-Zeichen für das Herz-Symbol als Operator zwischen zwei Personen definiert:
infix operator ❤ {associativity left precedence 200} func ❤ (left: Person, right: Person) -> Person{ left.marry(right) return left } //In action: rainer ❤ carola
Die Beispiele
Die im Artikel beschriebenen Code-Fragmente stehen über meinen GitHub-Account zur freien Verfügung und können gerne für eigene Experimente benutzt werden. Die Sourcen gibt es hier.
Die Beispiele benötigen eine Swift-Installation, ich habe hierzu die Ubuntu-Installation benutzt.