Swift, die neue Programmiersprache von Apple ist da und steht mit Xcode 6 als Beta zum Ausprobieren bereit.
Wir haben einige typische Lösungen aus der Objective-C Welt in Swift umgeschrieben, um zu sehen was passiert.

In diesem Artikel zeige ich beispielhaft den umgeschriebenden Objective-C Code eines Menüsystems in Form einer TableView:

Die TableView im Simulator

Es gibt drei Menüpunkte mit jeweils unterschiedlichem Titel, unterschiedlicher Höhe und unterschiedlichem Accessory Type. In diesem Beispiel legt der Accessory Type fest, ob der Menüpunkt auf der rechten Seite einen Pfeil hat.

Objective-C Code

#import <UIKit/UIKit.h>

@interface TableViewControllerObjc : UITableViewController

@end
#import "TableViewControllerObjc.h"

@implementation TableViewControllerObjc

typedef NS_ENUM(NSUInteger, NavigationTarget) {
  NavigationTargetHome,
  NavigationTargetShop,
  NavigationTargetFeedback,
  
  NavigationTargetsCount
};

- (void)       getTitle:(NSString**)title
          accessoryType:(UITableViewCellAccessoryType*)accessoryType
                 height:(CGFloat*)height
    forNavigationTarget:(NavigationTarget)navigationTarget {
  
  switch (navigationTarget) {
    case NavigationTargetHome:
      *title = @"Startseite";
      *accessoryType = UITableViewCellAccessoryDisclosureIndicator;
      *height = 50;
      break;
    case NavigationTargetShop:
      *title = @"Shop betreten";
      *accessoryType = UITableViewCellAccessoryDisclosureIndicator;
      *height = 60;
      break;
    case NavigationTargetFeedback:
      *title = @"Feedback senden";
      *accessoryType = UITableViewCellAccessoryNone;
      *height = 70;
      break;
    default:
      break;
  }
}

- (NSInteger)   tableView:(UITableView*)tableView
    numberOfRowsInSection:(NSInteger)section{
  
  return NavigationTargetsCount;
}

- (CGFloat)       tableView:(UITableView*)tableView
    heightForRowAtIndexPath:(NSIndexPath*)indexPath{
  
  NSString* title;
  UITableViewCellAccessoryType accessoryType;
  CGFloat height;
  [self        getTitle:&title
          accessoryType:&accessoryType
                 height:&height
    forNavigationTarget:(NavigationTarget)indexPath.row];
  
  return height;
}

- (UITableViewCell*)tableView:(UITableView*)tableView
        cellForRowAtIndexPath:(NSIndexPath*)indexPath{
  
  UITableViewCell* cell = [[UITableViewCell alloc]
      initWithStyle:UITableViewCellStyleDefault
    reuseIdentifier:@"Cell"];
  
  NSString* title;
  UITableViewCellAccessoryType accessoryType;
  CGFloat height;
  [self        getTitle:&title
          accessoryType:&accessoryType
                 height:&height
    forNavigationTarget:(NavigationTarget)indexPath.row];
  
  cell.textLabel.text = title;
  cell.accessoryType = accessoryType;
  
  return cell;
}

@end

Als erstes definiere ich ein Enum mit dem Namen NavigationTarget. Die einzelnen Werte stehen für je einen Menüeintrag. Die implizit vergebenen Integerwerte 0, 1 und 2 verwende ich später als Index der Tabelle, sodass das Enum auch gleich die Reihenfolge der Menüeinträge festlegt.
Der letzte Enum-Wert NavigationTargetsCount bekommt implizit einen Wert, der der Anzahl der vorherigen Werte entspricht. In diesem Fall also 3. Den Wert NavigationTargetsCount gebe ich später in der Delegate-Methode tableView:numberOfRowsInSection: zurück.

Als nächstes bekommt die Klasse eine Mapping-Methode getTitle:accessoryType:height:forNavigationTarget:, die für jeden Menüeintrag den Titel, den Accessory Type und die Zellenhöhe festlegt. Ich übergebe also einen Menüeintrag und bekomme einen Titel, ein Accessory Type und eine Höhe zurück. Methoden, die mehr als einen Wert zurückgeben, bekommen nach Apple-Konvention je einen Zeiger auf die Variablen, die zurückgegeben werden sollen.
Diese Mapping-Methode benutze ich später in den Delegate-Methoden tableView:heightForRowAtIndexPath: und tableView:cellForRowAtIndexPath: um an die Höhe, bzw. den Titel und den Accessory Type für die jeweilige Zelle zu kommen.
(Für den Fall, dass man an bestimmten Ausgabeparametern nicht interessiert ist, hätte ich die Mapping-Methode auch so schreiben können, dass nil als Ausgabeparameter übergeben werden darf. Aber die dafür nötigen nil-Checks würden diesen Beispielcode nur zusätzlich aufblasen.)

Das Enum und die Mapping-Methode bilden die beiden Stellen, an denen ich Menüeinträge hinzufügen, entfernen und ändern kann, ohne den restlichen Code anfassen zu müssen.

Swift Code

Der in Swift umgeschriebene Code sieht wie folgt aus:

import UIKit

class TableViewControllerSwift : UITableViewController {
  
  enum NavigationTarget : Int {
    case Home
    case Shop
    case Feedback
    
    static var numberOfCases: Int {
      var i = 0
      while true {
        if !fromRaw(i) { break }
        else { ++i }
      }
      return i
    }
    
    var attributes: (title: String,
                     accessoryType: UITableViewCellAccessoryType,
                     height: CGFloat) {
      switch self {
        case .Home: return ("Startseite", .DisclosureIndicator, 50)
        case .Shop: return ("Shop betreten", .DisclosureIndicator, 60)
        case .Feedback: return ("Feedback senden", .None, 70)
      }
    }
  }
  
  override func tableView(tableView: UITableView!,
      numberOfRowsInSection section: Int) -> Int {
        
    return NavigationTarget.numberOfCases
  }
  
  override func tableView(tableView: UITableView!,
      heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
          
    return NavigationTarget.fromRaw(indexPath.row)!.attributes.height
  }
  
  override func tableView(tableView: UITableView!,
      cellForRowAtIndexPath indexPath: NSIndexPath!)->UITableViewCell!{
          
    let cell = UITableViewCell(style: .Default, reuseIdentifier: "Cell")
    
    let navigationTarget = NavigationTarget.fromRaw(indexPath.row)!
    
    cell.textLabel.text = navigationTarget.attributes.title
    cell.accessoryType = navigationTarget.attributes.accessoryType
    
    return cell
  }
}

Im Gegensatz zu Objective-C ist das Swift Enum innerhalb der Klasse definiert (eingebettet). Es ist Eigentum der Klasse und kann nur in Verbindung mit dieser Klasse benutzt werden. Außerdem entfällt der Enum-Name Prefix für jeden Enum-Wert, da die Werte im Namensbereich des Enums liegen und mit NavigationTarget.Home usw. zugegriffen werden können.

Swift Enums können – ähnlich wie Klassen – Methoden und Properties besitzen. Ich habe in dem Enum die statische Property numberOfCases definiert, um die Anzahl der Enum-Werte zu berechnen und zurückzugeben.
Im Vergleich zur Objective-C Variante, in der ein extra Wert Count für die Anzahl eingeführt wurde, hat die Property Variante in Swift einige Vorteile: Die Auto-Completion Funktion von Xcode wird Count nicht fälschlicherweise als gültigen Enum-Wert vorschlagen. Und wenn das Enum in einem switch-Block eingesetzt wird, wird Xcode keine Warnung zu dem nicht behandelten Count case bringen.
Die numberOfCases Property habe ich so geschrieben, dass sie in andere Enums hineinkopiert werden kann. Ich vermute und hoffe, dass Swift in Zukunft noch erweitert wird, sodass alle Enums von sich aus eine ähnliche Property oder Methode besitzen, die die Anzahl der Werte zurückgibt.

Die Enum Property mit dem Namen attributes entspricht der Mapping Methode getTitle:accessoryType:height:forNavigationTarget: aus der Objective-C Variante.
Der Unterschied ist jedoch, dass Swift mir erlaubt die Property innerhalb des Enums zu definieren. Damit habe Ich nur noch eine Stelle – nämlich das Enum – die ich anpassen muss, wenn ich Menüeinträge hinzufügen, löschen oder ändern möchte.

Weiterhin habe ich in Swift die Möglichkeit Tupel zu benutzen um mehrere Werte auf einmal zurückgeben zu lassen. Die attributes Property liefert für jeden Menüeintrag ein Tupel mit drei Werten: Titel, Accessory Type und Zellenhöhe.
In den beiden TableView Delegate Methoden tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) und tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) kann man sehen, dass ich nur noch auf diejenigen Attribute zugreifen muss, die ich in der jeweiligen Methode brauche: die Zellenhöhe in der ersten Methode und den Titel und Accessory Type in der zweiten.

Fazit

Im Vergleich zu den 80 Zeilen Objective-C Code ist die 54-zeilige Swift-Variante nicht nur deutlich kürzer, sondern auch übersichtlicher, klarer strukturiert und dank multipler Rückgabewerte eleganter umgesetzt.

Im nächsten Artikel werde ich den Objective-C Code eines einfachen JSON Importers mit einer umgeschriebenen Variante in Swift vergleichen.

0 Kommentare

Dein Kommentar

An Diskussion beteiligen?
Hinterlasse uns Deinen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.