Objective-C Kurz gesagt Blöcke

Blöcke sind eigentlich eine Erweiterung der Programmiersprache C, werden jedoch von Apples Objective-C-Frameworks stark genutzt. Sie sind den Lambdas von C # insofern ähnlich, als dass Sie einen Anweisungsblock inline definieren und an andere Funktionen übergeben können, als ob es ein Objekt wäre.

Verarbeitung von Daten mit Funktionen im Vergleich zu beliebigen Aktionen mit Blöcken

Für das Definieren von Rückmeldemethoden sind Blöcke äußerst praktisch, da Sie die gewünschte Funktionalität zum Zeitpunkt des Aufrufs definieren können, anstatt an einer anderen Stelle in Ihrem Programm. Zusätzlich werden Blöcke als implementiert Verschlüsse (wie bei Lambdas in C #), wodurch der lokale Status des Blocks ohne zusätzliche Arbeit erfasst werden kann.


Blöcke erstellen

Die Blocksyntax kann im Vergleich zu der Objective-C-Syntax, die wir in diesem Buch verwendet haben, etwas beunruhigend sein. Machen Sie sich also keine Sorgen, wenn Sie sich erst einmal damit auskennen. Wir beginnen mit einem einfachen Beispiel:

^ (int x) return x * 2; ;

Dies definiert einen Block, der einen ganzzahligen Parameter annimmt, x, und gibt diesen Wert multipliziert mit zwei zurück. Abgesehen vom Caret (^) ähnelt dies einer normalen Funktion: Es enthält eine Parameterliste in Klammern, einen in geschweiften Klammern eingeschlossenen Anweisungsblock und einen (optionalen) Rückgabewert. In C # wird dies geschrieben als:

x => x * 2;

Blöcke sind jedoch nicht auf einfache Ausdrücke beschränkt - sie können eine beliebige Anzahl von Anweisungen enthalten, genau wie eine Funktion. Zum Beispiel können Sie ein hinzufügen NSLog () Aufruf vor Rückgabe eines Wertes:

^ (int x) NSLog (@ "Um% i mit 2 zu multiplizieren.", x); Rückgabe x * 2; ;

Parameterlose Blöcke

Wenn Ihr Block keine Parameter benötigt, können Sie die Parameterliste vollständig weglassen:

^ NSLog (@ "Dies ist ein ziemlich erfundener Block."); NSLog (@ "Es werden nur diese beiden Meldungen ausgegeben."); ;

Blöcke als Rückrufe verwenden

Ein Block alleine ist nicht sehr nützlich. Normalerweise übergeben Sie sie als Callback-Funktion an eine andere Methode. Dies ist eine sehr leistungsfähige Sprachfunktion, da Sie damit umgehen können Funktionalität als Parameter, anstatt auf beschränkt zu sein Daten. Sie können einen Block an eine Methode übergeben, wie Sie es auch bei einem anderen Literalwert tun würden:

[anObject doSomethingWithBlock: ^ (int x) NSLog (@ "Multiplizieren von% i mit zwei"); Rückgabe x * 2; ];

Das doSomethingWithBlock: Die Implementierung kann den Block genauso ausführen wie eine Funktion, die die Tür zu vielen neuen Organisationsparadigmen öffnet.

Als ein praktischeres Beispiel betrachten wir die sortUsingComparator: Methode definiert durch NSMutableArray. Dies bietet genau die gleiche Funktionalität wie das sortierteArrayNutzungsfunktion: Methode, die wir in verwendet haben Datentypen Kapitel, außer Sie definieren den Sortieralgorithmus in einem Block anstelle einer vollwertigen Funktion:

Enthaltenes Codebeispiel: SortUsingBlock

#einführen  int main (int argc, const char * argv []) @autoreleasepool NSMutableArray * numbers = [NSMutableArray arrayWithObjects: [NSNumber numberWithFloat: 3.0f], [NSNumber numberWithFloat: 5.5f], [NSNumber numberWithFloat: 1.0f], NSNumber numberWithFloat: 12.2f], nil]; [numbers sortUsingComparator: ^ NSComparisonResult (id obj1, id obj2) float number1 = [obj1 floatValue]; float number2 = [obj2 floatValue]; if (number1 < number2)  return NSOrderedAscending;  else if (number1 > number2) return NSOrderedDescending;  else return NSOrderedSame; ]; für (int i = 0; i<[numbers count]; i++)  NSLog(@"%i: %0.1f", i, [[numbers objectAtIndex:i] floatValue]);   return 0; 

Auch hier handelt es sich um eine unkomplizierte aufsteigende Sortierung. Die Möglichkeit, den Sortieralgorithmus an derselben Stelle wie den Aufruf der Funktion zu definieren, ist intuitiver, als an anderer Stelle im Programm eine unabhängige Funktion definieren zu müssen. Beachten Sie auch, dass Sie lokale Variablen wie in einer Funktion in einem Block deklarieren können.

Die Standard-Objective-C-Frameworks verwenden dieses Entwurfsmuster für alles von der Sortierung über die Aufzählung bis hin zur Animation. Tatsächlich könnten Sie die For-Schleife im letzten Beispiel sogar mit ersetzen NSArray's enumerateObjectsUsingBlock: Methode, wie hier gezeigt:

[sortiertNumbers enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) NSLog (@ "% lu:% 0.1f", idx, [obj floatValue]); if (idx == 2) // Stoppen Sie die Aufzählung am Ende dieser Iteration. * stop = JA; ];

Das obj Parameter ist das aktuelle Objekt, idx ist der aktuelle Index und *halt ist eine Möglichkeit, die Aufzählung vorzeitig zu beenden. Einstellen der *halt Zeiger auf JA weist die Methode an, die Aufzählung nach der aktuellen Iteration zu beenden. All dieses Verhalten wird von der angegeben enumerateObjectsUsingBlock: Methode.

Obwohl die Animation für dieses Buch ein wenig außerhalb des Themas liegt, ist es dennoch eine kurze Erklärung, um die Nützlichkeit von Blöcken zu verstehen. UIView ist eine der am häufigsten verwendeten Klassen in der iOS-Programmierung. Es handelt sich um einen generischen grafischen Container, mit dem Sie den Inhalt über die animieren können animateWithDuration: Animationen: Methode. Der zweite Parameter ist ein Block, der den Endzustand der Animation definiert. Die Methode ermittelt automatisch, wie die Eigenschaften mit dem ersten Parameter animiert werden. Dies ist eine elegante, benutzerfreundliche Methode, um Übergänge und anderes zeitgesteuertes Verhalten zu definieren. Wir werden Animationen im nächsten Abschnitt ausführlicher besprechen iOS Prägnant Buch.


Blöcke speichern und ausführen

Blöcke können nicht nur an Methoden übergeben werden, sondern können auch in Variablen zur späteren Verwendung gespeichert werden. Dieser Anwendungsfall dient im Wesentlichen als Alternative zur Definition von Funktionen:

#einführen  int main (int argc, const char * argv []) @autoreleasepool int (^ addIntegers) (int, int); addIntegers = ^ (int x, int y) return x + y; ; int result = addIntegers (24, 18); NSLog (@ "% i", Ergebnis);  return 0; 

Lassen Sie uns zunächst die Syntax zur Deklaration von Blockvariablen untersuchen: int (^ addIntegers) (int, int). Der Name dieser Variablen ist einfach AddIntegers (ohne das Caret) Dies kann verwirrend sein, wenn Sie lange keine Blöcke verwendet haben. Es ist hilfreich, das Caret als Blockversion des Dereferenzierungsoperators zu betrachten (*). Zum Beispiel a Zeiger namens AddIntegers würde als deklariert werden * addIntegers-ebenfalls a Block mit demselben Namen wird als deklariert ^ AddIntegers. Denken Sie jedoch daran, dass dies nur eine oberflächliche Ähnlichkeit ist.

Neben dem Variablennamen müssen Sie auch alle Metadaten deklarieren, die dem Block zugeordnet sind: Anzahl der Parameter, deren Typen und Rückgabetyp. Dadurch kann der Compiler die Typsicherheit mit Blockvariablen erzwingen. Beachten Sie, dass das Caret ist nicht Teil des Variablennamens - wird nur in der Deklaration benötigt.

Als nächstes verwenden wir den Standardzuweisungsoperator (=), um einen Block in der Variablen zu speichern. Natürlich sind die Parameter des Blocks ((int x, int y)) muss mit den von der Variablen deklarierten Parametertypen übereinstimmen ((int, int)). Ein Semikolon ist ebenso wie eine normale Variablenzuweisung nach der Blockdefinition erforderlich. Nachdem die Variable mit einem Wert gefüllt wurde, kann die Variable wie eine Funktion aufgerufen werden: addIntegers (24, 18).

Parameterlose Blockvariablen

Wenn Ihr Block keine Parameter übernimmt, müssen Sie dies explizit in der Variablen angeben, indem Sie platzieren Leere in der Parameterliste:

void (^ konstruiert) (void) = ^ NSLog (@ "Dies ist ein ziemlich erfundener Block."); NSLog (@ "Es werden nur diese beiden Meldungen ausgegeben."); ; gekünstelt();

Mit Variablen arbeiten

Variablen innerhalb von Blöcken verhalten sich ähnlich wie bei normalen Funktionen. Sie können lokale Variablen innerhalb des Blocks erstellen, auf Parameter zugreifen, die an den Block übergeben werden, und globale Variablen und Funktionen verwenden (z., NSLog ()). Aber auch Blöcke haben Zugriff auf nichtlokale Variablen, Dies sind Variablen aus dem einschließenden lexikalischen Bereich.

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42

In diesem Fall, Ursprünglicher Wert wird als nicht lokale Variable innerhalb des Blocks betrachtet, da sie außerhalb des Blocks definiert ist (nicht lokal, relativ zum Block). Die Tatsache, dass nicht lokale Variablen schreibgeschützt sind, bedeutet natürlich, dass Sie sie nicht zuweisen können:

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) Anfangswert = 5; // Dadurch wird ein Compiler-Fehler ausgegeben. return initialValue + x; ;

Der Zugriff auf die umgebenden (nicht lokalen) Variablen ist eine große Sache, wenn Inline-Blöcke als Methodenparameter verwendet werden. Es bietet eine bequeme Möglichkeit, jeden Status innerhalb des Blocks darzustellen.

Wenn Sie beispielsweise die Farbe einer UI-Komponente animieren und die Zielfarbe vor der Blockdefinition in einer lokalen Variablen berechnet und gespeichert wurde, können Sie einfach die lokale Variable innerhalb des Blocks verwenden. Es ist keine zusätzliche Arbeit erforderlich. Wenn Sie keinen Zugriff auf nicht lokale Variablen hätten, hätten Sie den Farbwert als zusätzlichen Blockparameter übergeben. Wenn Ihre Rückruffunktion von einem großen Teil des Umgebungszustands abhängt, kann dies sehr umständlich sein.

Blöcke sind Verschlüsse

Blöcke haben jedoch nicht nur Zugriff für nicht lokale Variablen - sie sorgen auch dafür, dass diese Variablen verwendet werden noch nie ändern, egal wann oder wo der Block ausgeführt wird. In den meisten Programmiersprachen wird dies als a bezeichnet Schließung.

Closures funktionieren, indem sie eine konstante, schreibgeschützte Kopie nicht-lokaler Variablen erstellen und diese in a speichern Referenztabelle mit den Anweisungen, die den Block selbst bilden. Diese Nur-Lese-Werte werden bei jeder Ausführung des Blocks verwendet. Das bedeutet, dass selbst wenn sich die ursprüngliche nicht lokale Variable ändert, der von dem Block verwendete Wert garantiert derselbe ist wie bei der Definition des Blocks.

Speichern nichtlokaler Variablen in einer Referenztabelle

Wir können dies in Aktion sehen, indem wir dem neuen einen neuen Wert zuweisen Ursprünglicher Wert Variable aus dem vorherigen Beispiel:

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42 initialValue = 100; NSLog (@ "% i", addToInitialValue (10)); // Noch 42.

Egal wo du anrufst addToInitialValue (), das Ursprünglicher Wert vom Block verwendet wird immer Sein 32, denn so war es, als es geschaffen wurde. In jeder Hinsicht ist es so, als ob die Ursprünglicher Wert Die Variable wurde innerhalb des Blocks in einen Literalwert umgewandelt.

Der Nutzen von Blöcken ist also zweifach:

  1. Sie ermöglichen es Ihnen, Funktionalität als Objekt darzustellen.
  2. Damit können Sie neben dieser Funktionalität Statusinformationen darstellen.

Die ganze Idee hinter dem Einkapseln der Funktionalität in einem Block besteht darin, sie verwenden zu können später im Programm. Verschlüsse ermöglichen vorhersagbares Verhalten wann immer Ein Block wird ausgeführt, indem der Umgebungszustand eingefroren wird. Dies macht sie zu einem wesentlichen Bestandteil der Blockprogrammierung.

Veränderliche Blockvariablen

In den meisten Fällen ist das Erfassen des Zustands mit Schließungen intuitiv, was Sie von einem Block erwarten würden. Es gibt jedoch Zeiten, in denen das entgegengesetzte Verhalten erforderlich ist. Veränderliche Blockvariablen sind nicht lokale Variablen, die als schreibgeschützt und nicht als schreibgeschützt bezeichnet werden. Um eine nicht lokale Variable in eine Variable umzuwandeln, müssen Sie diese mit der __Block Modifikator, der eine direkte Verbindung zwischen der außerhalb des Blocks verwendeten Variablen und der innerhalb des Blocks verwendeten Variablen herstellt. Dies öffnet die Tür für die Verwendung von Blöcken als Iteratoren, Generatoren und andere Objekte, die den Status verarbeiten.

Erstellen einer direkten Verknüpfung mit einer veränderlichen Blockvariablen

Das folgende Beispiel zeigt, wie Sie eine nicht lokale Variable ändern können:

#einführen  #import "Person.h" int main (int argc, const char * argv []) @autoreleasepool __block NSString * name = @ "Dave"; void (^ generateRandomName) (void) = ^ NSLog (@ "Ändern von% @ in Frank", name); name = @ "Frank"; ; NSLog (@ "% @", Name); // Dave // ​​Ändere es innerhalb des Blocks. generateRandomName (); // Dave zu Frank wechseln. NSLog (@ "% @", Name); // Frank // Ändere es außerhalb des Blocks. name = @ "Heywood"; generateRandomName (); // Heywood zu Frank wechseln.  return 0; 

Dies sieht fast genauso aus wie im vorherigen Beispiel, mit zwei sehr signifikanten Unterschieden. Erstens das Nichtlokale Name Variable können aus dem Block zugewiesen werden. Zweitens, die Variable außerhalb des Blocks ändern tut Aktualisieren Sie den im Block verwendeten Wert. Es ist sogar möglich, mehrere Blöcke zu erstellen, die alle dieselbe nichtlokale Variable bearbeiten.

Der einzige Nachteil bei der Verwendung des __Block Modifikator ist das kann nicht auf Arrays mit variabler Länge verwendet werden.

Definieren von Methoden, die Blöcke akzeptieren

Das Erstellen von Methoden, die Blöcke akzeptieren, ist sinnvoller als das Speichern in lokalen Variablen. Es gibt Ihnen die Möglichkeit, Ihre eigenen hinzuzufügen enumerateObjectsUsingBlock:-Stilmethoden für benutzerdefinierte Klassen.

Betrachten Sie die folgende Schnittstelle für die Person Klasse:

// Person.h @Interface Person: NSObject @property int age; - (Void) CelebrityBirthdayWithBlock: (Void (^) (int)) Aktivität; @Ende

Das void (^) (int) Code ist der Datentyp für den Block, den Sie akzeptieren möchten. In diesem Fall akzeptieren wir einen Block ohne Rückgabewert und einen einzelnen ganzzahligen Parameter. Beachten Sie, dass im Gegensatz zu Blockvariablen kein Name für den Block erforderlich ist - nur ein Schmuckstück ^ Charakter.

Sie verfügen nun über alle Fähigkeiten, um Methoden zu erstellen, die Blöcke als Parameter akzeptieren. Eine einfache Implementierung für die Person Die im vorherigen Beispiel gezeigte Benutzeroberfläche könnte wie folgt aussehen:

// Person.m #import "Person.h" @implementation Person @synthesize age = _age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) Aktivität NSLog (@ "Es ist eine Party !!!"); Tätigkeit (self.age);  @Ende

Sie können dann eine anpassbare Aktivität übergeben, die Sie auf einem ausführen möchten PersonGeburtstag wie so:

// main.m int main (int argc, const char * argv []) @autoreleasepool Person * dave = [[Personallokation] init]; dave.age = 37; [Dave FeiernBirthdayWithBlock: ^ (int age) NSLog (@ "Woot! Ich werde% i", Alter + 1); ];  return 0; 

Es ist offensichtlich, dass die Verwendung von Blöcken als Parameter unendlich viel flexibler ist als die Standarddatentypen, die wir bis zu diesem Kapitel verwendet haben. Sie können tatsächlich eine Instanz anweisen tun etwas, anstatt nur Daten zu verarbeiten.


Zusammenfassung

Mit Hilfe von Blöcken können Sie Anweisungen als Objective-C-Objekte darstellen, sodass Sie beliebige übergeben können Aktionen auf eine Funktion, anstatt auf beschränkt zu sein Daten. Dies ist für alles nützlich, von der Iteration über eine Folge von Objekten bis hin zur Animation von UI-Komponenten. Blöcke sind eine vielseitige Erweiterung der Programmiersprache C und ein notwendiges Werkzeug, wenn Sie vorhaben, mit den Standard-iOS-Frameworks viel zu tun. In diesem Kapitel haben wir gelernt, wie man Blöcke erstellt, speichert und ausführt. Außerdem haben wir die Feinheiten von Schließungen und des __Block Speichermodifizierer. Wir haben auch einige gängige Nutzungsparadigmen für Blöcke diskutiert.

Damit ist unsere Reise durch Objective-C abgeschlossen. Wir haben alles abgedeckt, von der grundlegenden Syntax bis zu Kerndatentypen, Klassen, Protokollen, Eigenschaften, Methoden, Speicherverwaltung, Fehlerbehandlung und sogar der fortgeschrittenen Verwendung von Blöcken. Wir haben uns mehr auf Sprachfunktionen konzentriert als auf die Erstellung grafischer Anwendungen. Dies war jedoch eine solide Grundlage für die Entwicklung von iOS-Apps. Ich hoffe, dass Sie sich mit der Objective-C-Sprache sehr wohl fühlen.

Denken Sie daran, dass sich Objective-C auf viele der gleichen objektorientierten Konzepte wie andere OOP-Sprachen stützt. Während wir in diesem Buch nur einige objektorientierte Entwurfsmuster angesprochen haben, sind praktisch alle für andere Sprachen verfügbaren Organisationsparadigmen auch in Objective-C möglich. Dies bedeutet, dass Sie Ihre vorhandene objektorientierte Wissensdatenbank mit den in den vorhergehenden Kapiteln vorgestellten Tools problemlos nutzen können.


iOS Prägnant

Wenn Sie bereit sind, funktionsfähige iPhone- und iPad-Anwendungen zu erstellen, lesen Sie den zweiten Teil dieser Serie, iOS Prägnant. In diesem praktischen Handbuch zur App-Entwicklung werden alle aus diesem Buch erworbenen Objective-C-Fertigkeiten auf reale Entwicklungssituationen angewendet. Wir werden durch alle wichtigen Objective-C-Frameworks gehen und lernen, wie Sie Aufgaben erledigen, einschließlich: Konfigurieren von Benutzeroberflächen, Erfassen von Eingaben, Zeichnen von Grafiken, Speichern und Laden von Dateien und vieles mehr.

Diese Lektion stellt ein Kapitel von Objective-C Succinctly dar, ein kostenloses eBook des Teams von Syncfusion.