2.6 Blöcke
![]()
Erst 2010 mit iOS 4 hat Apple Blöcke eingeführt, und seit iOS 5 nimmt der Anteil der API-Funktionen in iOS, die Blöcke verwenden oder unterstützen, rapide zu. Auch wenn Blöcke erst in jüngster Zeit Einzug in Objective-C gehalten haben, ist die Idee dahinter schon alt, älter als Objective-C. Ein Vater von Objective-C, Smalltalk, kannte bereits das Konzept von Blöcken als elementaren Bestandteil. Blöcke sind in Smalltalk selber als Objekte konzipiert, können also Nachrichten empfangen. Genauso verhält es sich in Objektive-C. Auch dort ist ein Block ein Objekt und kann dementsprechend wie ein Objekt in die Kommunikation einbezogen werden.
Das theoretische Konzept für Blöcke, die Closures, stammt aus der funktionalen Programmierung und wurde erstmalig mit LISP in einer Programmiersprache umgesetzt. Es erfreut sich in den letzten Jahren zunehmender Beliebtheit, was sich auch an der Integration dieses Konzepts in andere Programmiersprachen – wie jüngst Java und C++ – zeigt.
Ein Beispiel für Blöcke: Ein Fischhändler verkauft tagein, tagaus auf dem Markt Ma-krelen. Die Kunden kommen zu ihm, um eine bestimmte Menge an Fisch zu kaufen. Dafür geben sie ihm einen entsprechenden Geldbetrag. Der Fischhändler braucht also eine Funktion, die einer Menge Fisch einen Geldbetrag zuordnet. Seine Makrelen bekommt der Fischhändler auf dem Großmarkt, wo sich die Kilopreise für Makrelen jedoch jeden Tag ändern.
Der Fischhändler muss sich also jeden Morgen auf dem Markt neu überlegen, wie viel er für das Kilogramm Makrele von seinen Kunden haben will. Sie kennen ja das alte Marktwirtschaftsspielchen: Verlangt er zu viel, kauft keiner den Fisch. Ist der Fisch zu billig, dann geht er pleite, und kann seiner Frau Ilsebill nicht den ihr zustehenden Lebensstandard erfüllen. Da der gute Mann hingegen nicht jeden Tag ausführliche Marktforschungsanalysen betreiben möchte, hat er die einfache Faustregel aufgestellt, den Fisch für den doppelten Einkaufspreis zu verkaufen. In die Verkaufsfunktion kommen also mit dem Einkaufspreis und dem Faktor noch zwei zusätzliche Werte ins Spiel.
Ohne Closures müssten Sie diese beiden Werte entweder als zusätzliche Funktionsparameter oder über globale Variablen umsetzen. Zusätzliche Funktionsparameter haben den Nachteil, dass Sie sie immer beim Funktionsaufruf verfügbar haben müssen. Globale Variablen schränken Ihre Funktion hingegen auf eine Variante ein. Wenn Sie beispielsweise mit einem Programm den Makrelenmarkt simulieren wollen, haben alle Fischverkäufer den gleichen Großmarktpreis bezahlt und berechnen den Verkaufspreis mit dem gleichen Faktor.
Mit Closures können Sie hingegen für jeden Fischverkäufer eine eigene Verkaufsfunktion erstellen. Das könnte dann so aussehen:
typedef double Money;
typedef double Weight;
Money thePurchasePrice = 4.20; // Großmarktpreis
double thePurchaseFactor = 2.0; // Aufschlagsfaktor
Money (^thePurchaseFunction)(Weight) = ^(Weight inWeight) {
return inWeight * thePurchasePrice * thePurchaseFactor;
};
Money thePrice = thePurchaseFunction(2.5); // = 21 Euro
Listing 2.123 Erzeugung einer Blockfunktion
Listing 2.123 erzeugt eine Blockfunktion, speichert sie in der Variablen thePurchaseFunction und ruft sie einmal mit dem Wert 2,5 auf. Für die Berechnung verwendet sie die Variablen thePurchasePrice und thePurchaseFactor, die außerhalb des Blocks definiert wurden. Dabei speichert das Laufzeitsystem diese Werte allerdings als Kontext der Blockfunktion ab. Wenn Sie die Werte der Variablen ändern, verändern Sie diese Werte in der Blockfunktion nicht. Wenn Sie also die Zeilen aus Listing 2.124 an Listing 2.123 anhängen, erhalten Sie den gleichen Verkaufspreis wie oben und nicht 18,90 Euro. Die Blockfunktion merkt sich also die Variablenwerte bis zu ihrer Ausführung.
thePurchaseFactor = 1.8;
thePrice = thePurchaseFunction(2.5); // = 21 Euro
Listing 2.124 Eine Änderung der Variablen ändert nicht die Blockfunktion.
Das ist bei vielen Anwendungsfällen wichtig, da Sie häufig über einen Block beschreiben, was nach einer bestimmten Operation passieren soll. Wann Cocoa Touch diesen Block ausführt, kann Ihnen dabei egal sein. Er hat ja genau die Werte, die Sie bei seiner Erzeugung festgelegt haben.
Rückgabetyp von Blockfunktionen
Blockfunktionen sind sehr streng bezüglich ihres Rückgabetyps. Im Gegensatz zu vielen C-Anweisungen konvertieren sie den Wert nicht automatisch in den richtigen Typ. Das liegt daran, dass die Definition die Deklaration nicht kennt. Die Definition muss also den Rückgabetyp aus der Return-Anweisung extrahieren:
^(void) { return 1; } // Rückgabetyp int
^(void) { return 1.0; } // Rückgabetyp double
^(void) { return 1.0f; } // Rückgabetyp float
Sie müssen also sicherstellen, dass die Rückgabetypen der Blockdeklaration und ?definition übereinstimmen.
Das gilt übrigens auch, wenn der Block ein Objekt zurückgeben soll. Auch hier wandelt der Compiler den Rückgabetyp nicht automatisch in die Superklasse um:
id ^(theBlock)(void) = ^(void) { return @"Hello"; }; // Fehler
NSObject *^(theBlock)(void) { return @"Hello"; }; // Fehler
Sie müssen hier explizit den Rückgabetyp in den richtigen Rückgabetyp umwandeln:
id ^(theBlock)(void) = ^(void) { return (id)@"Hello"; }; // OK
Wenn Sie Blockfunktionen in Ihren Programmen verwenden möchten, sollten Sie wie in Listing 2.126 dafür über typedef eigene Typen definieren. Das vereinfacht Ihnen die Deklaration von Variablen.
Wie bereits erwähnt wurde, können Sie diese Funktion nicht nur aufrufen, sondern auch wie ein Objekt behandeln. Beispielsweise können Sie die Funktion als Parameterwert an eine andere Funktion oder eine Methode übergeben. Dabei ist es wichtig, dass Sie die Blockfunktion nur innerhalb des Bereichs verwenden dürfen, in dem Sie sie deklariert haben. Folgendes dürfen Sie beispielsweise nicht machen:
if(YES) { /* Anfang */
thePurchaseFunction = ^(Weight inWeight) {
return inWeight * thePurchasePrice * 2.1;
};
/* Ende */ }
thePrice = thePurchaseFuntion(3.1); // Fehler
Listing 2.125 Bereichsverletzungen mit Blöcken
Die letzte Anweisung in Listing 2.125 ist eine Bereichsverletzung, weil sie die Blockfunktion außerhalb des deklarierenden Bereichs verwendet (der durch /* Anfang */ und /* Ende */ gekennzeichnet ist). Sie dürfen eine Blockfunktion auch nicht einfach als Funktions- oder Methodenergebnis zurückgeben, da Sie auch hier ja den deklarierenden Block verlassen. Bei Bereichsverletzungen von Blöcken gibt Ihnen der Compiler in vielen Fällen keine Fehlermeldungen aus. Es kann sogar vorkommen, dass das Programm trotz einer Bereichsverletzung funktioniert.
Sie können Bereichsverletzungen vermeiden, indem Sie den Block kopieren. Hier treten die Objekteigenschaften von Blöcken zutage. Sie können nämlich auch an Blöcke die Methoden retain, release, autorelease und copy senden, wobei bei automatischem Referenzenzählen natürlich nur die Methode copy erlaubt ist. Wenn Sie also einen Block aus einer Methode zurückgeben möchten, schicken Sie ihm ein copy und gegebenenfalls noch ein autorelease, wenn Sie manuelles Referenzenzählen verwenden: [Erinnern Sie sich noch an die erste Speicherverwaltungsregel?]
typedef double (^FunctionType)(double inValue);
- (FunctionType)createFunction {
return [[^(double inValue) {...} copy] autorelease];
}
- (double)evaluateFunctionWithValue:(double)inValue {
FunctionType theFunction = [self createFunction];
return theFunction(inValue);
}
Listing 2.126 Das Kopieren von Blöcken
Automatisches Referenzenzählen und Blockfunktionen
Automatisches Referenzenzählen vereinfacht die Handhabung von Blockfunktionen als Rückgabewerte. Sie können hier beispielsweise einfach
return ^(void){ ... };
schreiben. Der ARC-Compiler fügt automatisch die notwendigen Aufrufe von copy und autorelease ein:
return [[^(void){ ... } copy] autorelease];
Wenn Sie Blockfunktionen in Objekten speichern möchten, ist es am einfachsten, dafür Propertys vom Typ copy zu verwenden. Sie können dann die Property-Werte wie bei anderen Propertys auch zuweisen. Durch den Speicherverwaltungstyp kopiert die Laufzeitumgebung automatisch die Blockfunktion bei der Zuweisung:
// Typdeklaration
typedef double (^FunctionType)(double inValue);
// Property-Deklaration
@property(nonatomic, copy) FunctionType function;
// Zuweisung
self.function = ^(double inValue) { ... };
// Ausführung
self.function(2.5);
Listing 2.127 Blockfunktion als Property-Wert
2.6.1 Rückruffunktionen
Durch Blockfunktionen lassen sich in vielen Fällen Fallunterscheidungen vermeiden, die den Programmcode häufig schwer verständlich machen. Ein typisches Beispiel dafür sind mehrere delegierende Objekte, die ein gemeinsames Delegate besitzen. Wenn ein Viewcontroller beispielsweise mehrere unterschiedliche Alertviews anzeigen kann, dann können die Alertviews den Controller als gemeinsames Delegate verwenden. Natürlich müssen die Delegate-Methoden die Alertviews wieder auseinanderhalten können, wozu Sie Tags verwenden können. Diese Lösung sieht dann ungefähr so aus:
- (IBAction)showFirstAlertView {
UIAlertView *theAlert = ...;
theAlert.tag = 10;
[theAlert show];
}
- (IBAction)showSecondAlertView {
UIAlertView *theAlert = ...;
theAlert.tag = 20;
[theAlert show];
}
- (void)alertView:(UIAlertView *)inAlertView
clickedButtonAtIndex:(NSUInteger)inButtonIndex {
if(inAlertView.tag == 10) {
// Anweisungen für ersten Alertview
}
else {
// Anweisungen für zweiten Alertview
}
}
Listing 2.128 Unterscheidung von Alertviews durch Tags
Die Anweisungen für die Unterscheidung der Alertviews sind in Listing 2.128 in Fettdruck hervorgehoben. Zu dieser Fallunterscheidung kommen dann noch die Fallunterscheidungen für die Buttons der Alertviews, und so entstehen schnell sehr lange und schwer verständliche Methoden. Durch Blockfunktionen lässt sich der Code für die Ergebnisauswertung der Alertviews jeweils zu Ihrem Konstruktionsort verschieben und die Fallunterscheidung für die Trennung der Alertviews vermeiden.
Projektinformation
Den Quellcode des nachfolgenden Beispiels finden Sie auf der DVD unter Code/Apps/Storyboard/MultipleAlertViews oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_2/Apps/Storyboard/MultipleAlertViews.
Dazu deklarieren Sie am besten zuerst einen eigenen Typ und eine Property für die Blockfunktionen. Die Blockfunktionen bekommen dabei die gleichen Parameter wie die Delegate-Methode des Alertviews. Die Property enthält jeweils die Blockfunktion, die der Controller nach dem Drücken eines Buttons des aktuell angezeigten Alertviews aufrufen soll. Solche (Block-)Funktionen nennt man auch Rückruffunktionen oder Callbacks.
typedef void (^AlertViewCallback)(
UIAlertView *inAlertView, NSUInteger inButtonIndex);
@interface ViewController()<UIAlertViewDelegate>
@property (nonatomic, copy) AlertViewCallback
alertViewCallback;
@end
Listing 2.129 Callback-Deklaration für die Alertviews
Das Beispielprojekt besitzt zwei Buttons, die beide einen Alertview öffnen. Im ersten Alertview können Sie ein Bild und im zweiten einen Titel für die Anzeige auswählen. Bei der Anzeige der Alerts müssen Sie nun der Callback-Property eine passende Rückruffunktion zuweisen.
- (IBAction)chooseImage:(id)inSender {
UIAlertView *theAlert .= [[UIAlertView alloc] ...];
self.alertViewCallback = 
^(UIAlertView *inAlertView, NSUInteger inButton) {
if(inButton > 0) {
NSString *theName = inButton == 1 ? 
@"dragonfly.jpg" : @"flower.jpg";
self.imageView.image = 
[UIImage imageNamed:theName];
}
};
[theAlert show];
}
- (IBAction)chooseTitle:(id)inSender {
UIAlertView *theAlert = [[UIAlertView alloc] ...];
self.alertViewCallback = 
^(UIAlertView *inAlertView, NSUInteger inButton) {
if(inButton > 0) {
self.titleLabel.text = inButton == 1 ? 
NSLocalizedString(@"Dragonfly", @"") : 
NSLocalizedString(@"Flower", @"");
}
};
[theAlert show];
}
Listing 2.130 Initialisierung des Callbacks bei Anzeige des Alerts
Die Delegate-Methode braucht jetzt nur noch die Rückruffunktion aufzurufen. Allerdings sollte sie die Referenz auf die Funktion zwischenspeichern, damit Sie die Property vor dem Aufruf auf nil setzen können. Dann kann eine Rückruffunktion den Wert der Property ändern, ohne dass es zu Komplikationen kommen kann. Da das Beispielprojekt automatisches Referenzenzählen verwendet, reicht die Zuweisung des Callbacks an eine lokale Variable aus, da diese die Referenz hält.
- (void)alertView:(UIAlertView *)inAlertView
clickedButtonAtIndex:(NSInteger)inButton {
AlertViewCallback theCallback = self.alertViewCallback;
self.alertViewCallback = nil;
theCallback(inAlertView, inButton);
}
Listing 2.131 Aufruf des Callbacks
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.




Jetzt bestellen





