Im ersten Teil dieser Artikelserie über Swift möchte ich Ihnen einen schnellen Einblick in die Programmiersprache Swift geben und die 10 interessantesten syntaktischen Besonderheiten und innovativsten Features vorstellen, die Swift von vielen anderen Hochsprachen unterscheidet.
In folgenden Artikeln tauchen wir gemeinsam in einen praktischen Workshop mit dem Apple SpriteKit ein, schauen uns die Neuerungen der kommenden Major-Version an und behandeln einige der häufigsten Problemlösungen, die uns im täglichen Einsatz mit Swift begegnen.
Was ist Swift?
Swift ist eine Programmiersprache aus dem Hause Apple, die für die Entwicklung von Software für die wichtigsten Apple-Produkte, darunter iPhone, iPad, iMac und MacBook, verwendet wird.
Als Apple die Sprache Swift der Öffentlichkeit 2014 vorstellte, überraschte sie Experten mit ihren innovativen Features und ihren revolutionären Konzepten. In ihrer aktuellen Version 4.2 ist die Sprache technisch so ausgereift, dass das Programmieren damit nicht nur riesigen Spaß macht, sondern auch das Schreiben von High-Quality-Code mit einem hohen Maß an Pragmatismus und Lesbarkeit fördert.
Xcode und der Playground
Um Swift in der Praxis auszuprobieren, benötigen wir lediglich Apples primäre Entwicklungsumgebung Xcode. Diese IDE kann kostenfrei über Apples App Store heruntergeladen und installiert werden. Nach dem Start von Xcode kann mit „Get started with a playground“ ein neues Projekt erstellt werden, in dem wir die Syntax und die coolsten Features von Swift 4.2 direkt ausprobieren können.
1. Verzicht auf Semikolons
Swift kommt ohne ein abschließendes Semikolon am Zeilenende aus. Der Einsatz von Semikolons ist lediglich dann erforderlich, wenn mehrere Anweisungen innerhalb einer Zeile formuliert werden, was die meisten Coding Styles aber ohnehin als Bad Practice ansehen:
// logs one line of output to the debug console print( "Welcome to Swift" ) // logs three lines of output to the debug console print( "Welcome" ); print( "to" ); print( "Swift" )
2. Parameter Labeling
Standardmäßig verlangt Swift beim Aufruf einer Funktion, dass jedes Argument mit dem Namen des zugehörigen Funktionsparameters angeführt wird. Obwohl diese Anforderung zum Schreiben von mehr Code zwingt, wirkt sie sich positiv auf die Lesbarkeit des Programmcodes aus:
func interpolate( start:Double, end:Double, ratio:Double ) -> Double { return ( start + ( ( end - start ) * ratio ) ); } // parameter names must be specified let result:Double = interpolate( start: 10, end: 20, ratio: 0.75 ) // logs '17.5' to the debug console print( result )
Optional kann für jeden internen Parameternamen ein zusätzlicher öffentlicher Parametername definiert werden, dessen Name sich von dem des internen unterscheidet:
func interpolate( x start:Double, y end:Double, ratio:Double ) -> Double { // internal parameter names are used here return ( start + ( ( end - start ) * ratio ) ) } // public parameter names must be specified let result:Double = interpolate( x: 10, y: 20, ratio: 0.75 )
Die obligatorische Angabe des Parameternamens beim Aufruf einer Funktion kann für bestimmte Parameter explizit deaktiviert werden, indem ein Unterstrich als öffentlicher Parametername vergeben wird. Diese Technik bietet sich für selbsterklärende Funktionen an, bei denen die Angabe des Parameternamens beim Funktionsaufruf keinen Mehrwert für die Lesbarkeit darstellt:
func toInt( _ value:Double ) -> Int { return Int( value ) } // parameter name must be omitted let result:Int = toInt( 7.89 ) // logs '7' to the debug console print( result )
3. Type Inference
Das strikte und statische Typensystem von Swift stellt sicher, dass eine typisierte Variable nie mit dem Wert eines anderen Datentyps belegt werden darf und schützt den Entwickler damit vor Laufzeitfehlern.
Trotzdem macht Swift die explizite Definition von Typdeklarationen überflüssig, indem der Swift-Compiler anhand des zugewiesenen Wertes Rückschlüsse auf den erforderlichen Datentyp einer Variable zieht und diesen inferiert:
var meaningOfLife = 42 // infere type 'Int' meaningOfLife = 23 // compiles smooth meaningOfLife = "Hello" // won't compile!
Darüber hinaus ist die explizite Spezifikation eines Datentyps optional bei der Deklaration einer Variable. Die folgende Zuweisung ist daher äquivalent und ebenso gültig:
var meaningOfLife:Int = 42
4. Kein Fallthrough in Switch-Cases
Das aus anderen Sprachen gewohnte und fehleranfällige fallthrough-Verhalten in case-
Statements existiert in Swift per Design nicht. Somit kann auf das Hinzufügen von break-
Statements gänzlich verzichtet und Code wie der folgende formuliert werden:
enum Direction { case Left case Right } var playerX = 10.0 let stepSize = 2.5 func updatePosition( _ direction:Direction ) -> Void { switch ( direction ) { case .Left: playerX -= stepSize case .Right: playerX += stepSize } } updatePosition( Direction.Right ) updatePosition( Direction.Right ) updatePosition( Direction.Right ) updatePosition( Direction.Right ) updatePosition( Direction.Left ) // logs '17.5' to the output console print( playerX )
5. Umfassende Switch-Statements
switch-
Statements müssen alle Zustände einer Variable abdecken, andernfalls reagiert der Compiler mit einem Fehler. So muss bei einem switch
nach einer Variable vom Typ Int
oder String
immer ein default-
case angegeben werden und bei einem switch
nach einer Variablen vom Typ Enumeration
alle Enumerationskonstanten abgedeckt oder alternativ ein default-
case definiert werden:
enum Direction { case Left case Right case Up case Down } func updatePosition( _ direction:Direction ) -> Void { switch ( direction ) { case .Left: playerX -= stepSize case .Right: playerX += stepSize default: print( "TODO player Y movements are not implemented yet" ) } }
6. Optionals
Meiner Meinung nach hat Swift den Umgang mit nil
-Werten, in anderen Sprachen besser bekannt als null
-Werte, sehr pragmatisch gelöst: Jede Variable muss bei deren Deklaration oder im Konstruktor der zugehörigen Kompilationseinheit einen Wert zugewiesen bekommen, der nicht nil
ist – andernfalls reagiert der Compiler darauf mit einem Fehler.
Per Design kann es natürlich vorkommen, dass man den Wert einer Variablen bei deren Deklaration oder im Konstruktor noch nicht belegen kann oder will, da deren Wert hier noch nicht bekannt oder verfügbar ist. Für den Fall, dass eine Variable mit dem Wert nil
vorbelegt werden muss, verlangt Swift, dass diese Variable explizit mit einem optionalen Datentyp deklariert wird. Hierfür muss der für die Variable angegebene Datentyp von einem Fragezeichen begleitet werden:
var firstName:String = "Christopher" // firstName is a non-optional String firstName = nil // won't compile! var lastName:String? = "Stock" // lastName is an optional String lastName = nil // compiles smooth
Optionals werden in Swift als ein eigener Datentyp gehandhabt. Daher kann der Swift-Compiler beim Zuweisen von Werten an Variablen Verletzungen des Typensystems erkennen:
// won't compile: // Value of optional type 'String?' must be unwrapped to a value of type 'String' firstName = lastName
7. Asserted Variable States
Um auf Variablen eines optionalen Datentyps zugreifen zu können, müssen diese explizit ausgepackt werden. Dieser Vorgang wird als unwrapping oder auch unboxing bezeichnet und stellt sicher, dass eine Variable bei deren Zugriff nicht nil
ist. Swift bietet hierfür zwei eigene Sprachkonstrukte an:
Unwrapping mit if let
Das if let-
Statement überführt ein Optional in eine lokale Variable, die anschließend innerhalb des if let-
Körpers verwendet werden kann. Diese neu aufgestellte lokale Variable darf dabei sogar den selben Namen haben wie die Variable, die ausgepackt werden soll. Der Körper des if let
-Statements wird nur dann ausgeführt, wenn der Wert des Optionals nicht nil
ist:
func printUppercased( msg:String? ) -> Void { if let msg = msg { print( msg.uppercased() ) } } // logs 'HELLO SWIFT' to the output console printUppercased( msg: "Hello Swift" ) // doesn't log to the output console printUppercased( msg: nil )
Unwrapping mit guard else
Im Gegensatz zum if let-
Statement kann mit dem guard else
-Konstrukt ein Optional in eine lokale Variable überführt und dabei ein Körper ausgeführt werden, wenn der Wert des Optionals nil
ist:
func printUppercased( msg:String? ) -> Void { guard let msg = msg else { return } print( msg.uppercased() ) } // logs 'HELLO SWIFT' to the output console printUppercased( msg: "Hello Swift" ) // doesn't log to the output console printUppercased( msg: nil )
Obwohl diese Anweisungen erstmal ziemlich gewöhnungsbedürftig erscheinen, ist deren Vorteil klar ersichtlich: Bereits der Compiler erkennt und überprüft das Zuweisen von nil
-Werten, sodass der Entwickler keine eigene Prüfung auf nil
einbauen oder eine NullPointerException
abfangen muss.
8. Open for Extension
Alle Funktionen und Typen können in Swift erweitert werden, ohne dass hierfür eine eigene Kompilationseinheit abgeleitet werden muss. Mithilfe des Schlüsselworts extension
kann jedes bestehende Element mit zusätzlicher Funktionalität angereichert werden, egal ob es sich dabei um eigene oder um grundlegende Einheiten handelt. Hier ein triviales Beispiel für die Erweiterung der nativen Klasse String
:
extension String { func simon() { print( "Simon says '" + self + "'" ) } } // logs "Simon says 'Hello Swift'" to the output console "Hello Swift".simon()
Da in Swift auch globale Funktionen überschrieben werden können, ermöglicht uns dies Konstruktionen wie beispielsweise das Überschreiben des print
-Statements und dessen bedingter Außerkraftsetzung:
var enableLogs = true func print( _ msg:Any ) { if ( enableLogs ) { Swift.print( msg ) } } // logs 'Hello Swift' to the output console print( "Hello Swift" ) // logs nothing to the output console enableLogs = false print( "Hello Swift" )
9. Konstante Funktionsparameter
Viele Coding Styles sehen das Neuzuweisen von Funktionsparametern als Bad Practice an, da diese Praxis bei der Übergabe von Referenzen zu unerwünschtem Verhalten führen kann. Swift löst dieses Problem konsequent, indem es alle Parameter innerhalb einer Funktion als Konstanten definiert, deren Neuzuweisung mit einem Compilerfehler quittiert wird:
func square( x:Int ) -> Int { // won't compile! x *= x return x }
Dieser Fehler kann aufgelöst werden, indem eine sprechende lokale Variable eingeführt wird, die zudem auf die Lesbarkeit dieser Funktion einzahlt:
func square( x:Int ) -> Int { let squareValue = ( x * x ) return squareValue }
10. UTF-8 Namings und eigene Operatoren
In Swift können alle Character aus dem Unicode-Zeichensatz zur Benennung von Variablen, Funktionen, Operatoren und Kompilationseinheiten verwendet werden. Darüber hinaus erlaubt Swift die Definition eigener Operatoren, die als Präfix-, Postfix- oder Infix-Operatoren fungieren können. Im folgenden Beispiel wird ein Infix-Operator erstellt, der zwei Strings konkateniert und mit einem einzelnen Unicode-Character benannt wird:
// specify operator name and position ( prefix, infix or postfix ) infix operator ♥ func ♥( left:String, right:String ) -> String { return ( left + " loves " + right ) } // logs 'Christopher loves Swift' to the output console print( "Christopher" ♥ "Swift" )
Da der Einsatz eigener Operatoren die Interoperabilität unseres Programmcodes erschweren und auch dessen Lesbarkeit eher verschlechtern kann, rate ich von deren Einsatz in quelloffenem Code ab. Trotzdem kann es praktisch und vereinfachend sein, sie in abgeschlossenen und nicht öffentlichen Komponenten unseres Programms einzusetzen.
Mehr Swift!
Das waren einige von Swifts Vorteilen und spannendsten syntaktischen Neuerungen, die diese Programmiersprache von vielen anderen Sprachen abhebt. Meiner Meinung nach handelt es sich bei Swift um eine sehr gut konzipierte und durchdachte Programmiersprache, die die Stärken ihres Vorgängers Objective-C ausgebaut und sich in ihrer aktuellen Version 4.2 sehr gut entwickelt hat.
Der Release der Major-Version 5.0 von Swift wurde für 2019 angekündigt. Sobald dieser erfolgt, stelle ich Ihnen die wichtigsten Neuerungen dieser Version hier im Mayflower-Blog vor. In weiteren Artikeln dieser Serie werfen wir einen Blick auf die Swift-IDE AppCode von JetBrains und behandeln Lösungen für die häufigsten Problemstellungen mit Swift.
Swift auf dem Developer Camp 2019
Vielleicht konnte ich Sie mit meinem Artikel sogar dazu begeistern, selbst einmal Swift in der Praxis auszuprobieren? Auf dem Developer Camp 2019 in Würzburg biete ich zusammen mit meinem Kollegen Sebastian Sellmeier einen praktischen Einstieg in die Programmierung mit Swift und dem Apple SpriteKit an. Einsteiger und Fortgeschrittene können hier die Grundlagen der Anwendungsentwicklung mit Xcode und Swift erlernen. Das praktische Vorgehen des Workshops werde ich darüberhinaus in einem Folgeartikel dieser Blogserie festhalten
Ich würde mich sehr freuen wenn ich Ihnen einen kurzen Einblick in diese interessante Programmiersprache aus dem Hause Apple geben konnte. Über Feedback oder Rückfragen zu Swift oder dieser Blogserie können Sie mich sehr gerne über christopher.stock@mayflower.de erreichen.
Apple, App Store, iPad, iPhone, iMac, MacBook, Swift, Xcode und ihre Logos sind eingetragene Marken von Apple Inc in den USA und anderen Ländern.
Foto von Flo Maderebner von Pexels
Schreibe einen Kommentar