[av_promobox button=’yes‘ label=’zum Seminar‘ link=’https://www.lmisacademy.de/course/app-entwicklung-ios/‘ link_target=’_blank‘ color=’theme-color‘ custom_bg=’#444444′ custom_font=’#ffffff‘ size=’large‘ icon_select=’no‘ icon=’ue800′ font=’entypo-fontello‘ av_uid=’av-2dxfi2′]
In dem Seminar Mobile Apps – iOS lernen Sie Grundlagen zur Programmierung von Apps für das iPhone und bekommen einen Einblick in die wichtigsten Konzepte.
[/av_promobox]
Asynchrone Operationen sind fundamentaler Bestandteil einer jeden oberflächenbasierten Applikation.
Eine der größten Herausforderungen für Entwickler besteht darin, alle komplexen Berechnungen so auszuführen, dass die Benutzeroberfläche möglichst nicht blockiert und somit die Nutzererfahrung nicht beeinträchtigt wird.
Leider ist das Entwickeln nebenläufiger Applikationen nicht ganz trivial. Es muss z. B. darauf geachtet werden, dass Threads sich nicht gegenseitig blockieren (Deadlock), Ressourcen korrekt verarbeitet und besonders kritische Programmabschnitte synchronisiert abgearbeitet werden. Bereits bei den einfachsten Szenarien können so sehr komplexe Probleme auftreten. Nicht verwunderlich, dass viele Entwickler sich vor nebenläufigem Code scheuen und Multithreading eher mit schwarzer Kunst verbinden.
Apple’s Programmierschnittstelle (englisch: application programming interface, kurz: API) Grand Central Dispatch soll bei diesen Herausforderungen Abhilfe schaffen. Sie wurde erstmals im September 2009 mit Mac OS X 10.6 eingeführt und steht auch seit Juni 2010 und iOS 4 der mobilen Welt zur Verfügung. Sie erlaubt es auf sehr einfache Art, die zu verrichtende Arbeit in kleine Blöcke aufzuteilen und auf mehrere Threads und je nach Hardware auch auf mehrere CPUs zu verteilen.
Die grundlegenden Technologien und deren Anwendung werden in diesem Artikel diskutiert und anhand einiger Beispiel verständlich dargestellt.
Ein Großteil der neuen API Grand Central Dispatch (GCD) wird über die so genannten Blöcke (englisch blocks) angesprochen. Sie stellen eine weitere Apple-Innovation dar, die C, C++ und Objective-C um eine Art anonyme Inline-Funktion erweitert (Exkurs: Objective-C ist eine Obermenge der Programmiersprache C). In anderen Programmiersprachen ist der Begriff Block auch bekannt als Closure oder Lambda. Letztere Bezeichnung ist vor allem .NET-Entwicklern ein Begriff, aber mit Java 8 findet diese Technologie nun auch Einzug in die Java-Welt.
Die Idee hinter Blöcken besteht vor allem darin, bestimmte Codeabschnitte wie ein Objekt behandeln zu können. Da sie somit Variablen zugewiesen oder als Argument einer Funktion bzw. Methode übergeben werden können, stellen Blöcke eine gute Alternative zum Delegate-Pattern in Objective-C oder zu den aus C bekannten Callback-Funktionen dar. So muss nicht explizit eine Klasse oder Methode deklariert werden. Dadurch eignen sich Blöcke hervorragend für nebenläufige Programmierung.
Wer bereits Erfahrung mit der Deklaration von Funktionszeigern in C hat, wird feststellen, dass die Deklaration eines Blocks sich in Bezug darauf nur in einem Detail unterscheidet.
// Pointer zu einer Funktion int (*function)(int, int); //Pointer zu einem Block int (^block)(int, int);
Anstelle von * wird ^ als Zeiger zu einem Block verwendet. Der Rest ist äquivalent. Zunächst wird der Rückgabetyp, gefolgt von der Bezeichnung des Blocks inklusive ^-Symbol angegeben. Anschließend folgt eine Liste der Parametertypen.
Es wird empfohlen, diese Deklaration mit einem typedef zu versehen, um den Block als eigenen Datentyp im Programmcode wieder verwenden zu können.
// typedef verwenden typedef int(^block_t)(int, int); block_t meinBlock = argument,
So kann sehr leicht und übersichtlich eine Block-Variable deklariert und ein entsprechender Programmcode zugewiesen werden.
// direkt zuweisen block_t meinBlock = ^ (int a, int b) { return a - b; }; //ausführen meinBlock(2, 1);
Die Ausführung des Blocks geschieht hier wie gewohnt über den Variablennamen, gefolgt von den konkreten Parametern.
Einige iOS-APIs stellen bereits blockbasierte Aufrufe bereit. Ein anschauliches Beispiel sei hier anhand eines NSDictionary aufgeführt.
NSDictionary *dict = @{ @"key" : @"value", @"key2" : @"value2" }; [dict enumerateKeysAndObjectsUsingBlock : ^ (id key, id value, 800L * stop) { NSLog(@"%@ => %@", key, value) }];
Eine Iteration über alle Werte, die sonst über herkömmliche Schleifen oder ähnliches realisiert worden wäre, kann mithilfe von Blöcken deutlich schlanker und eleganter umgesetzt werden. Für jedes Key-Value-Paar ruft das NSDictionary die Methode enumerateKeysAndObjectsUsingBlock: auf und übergibt dem Block key und value als Parameter. Ein vorzeitiger Abbruch wird durch Setzen des boolschen Zeigers stop erzielt.
Ein weiterer großer Vorteil liegt darin, dass ein Block auf alle in seinem Sichtbarkeitsbereich (Scope) deklarierten Variablen lesend und mittels __block auch schreibend zugreifen kann.
BOOL rev = arg; __block NSUInteger count = 0; [dict enumerateKeysAndObjectsUsingBlock : ^ (NSString *key, id value, BOOL *stop) { if (rev) count++; }];
Mit GCD können derartige Code-Blöcke in so genannten Dispatch Queues eingereiht und vom System entsprechend je auf einem anderen Thread ausgeführt werden. Dadurch kann der Main-Thread seine Aufgaben wieder aufnehmen und wird nicht blockiert. An dieser Stelle ist es wichtig zu erwähnen, dass der Main-Thread dafür zuständig ist, sämtliche Ereignisse (z. B. Touch- und Zeichenevents) zu behandeln. Der Main-Thread sollte daher durch länger andauernde Operationen nicht blockiert werden.
Dispatch Queues führen Aufgaben effizient und auf sehr einfache Art entweder synchron oder asynchron aus. Wie es von „Warteschlangen“ zu erwarten ist, arbeiten die Queues ihre Aufgaben nach dem First In – First Out-Prinzip ab. So werden alle Aufgaben in der Reihenfolge, in der sie hinzugefügt wurden, gestartet.
Serielle Queues (private dispatch queues) führen je eine Aufgabe zur selben Zeit, aber auf unterschiedlichen Threads aus. Sie werden oft verwendet, um den Zugriff auf eine bestimmte Ressource zu synchronisieren. Es können so viele serielle Queues erstellt werden wie benötigt. Dabei arbeitet dennoch jede Queue nebenläufig in Bezug auf alle anderen Queues.
dispatch_queue_t serial_q = dispatch_queue_create("de.lmis.serial", DISPATCH_QUEUE_SERIAL);
Dieses Beispiel zeigt, wie eine derartige Queue erstellt wird. Die Funktion dispatch_queue_create erwartet zwei Parameter: Den Namen der Queue und einen Typen. Der Name dient lediglich zur Identifizierung der jeweiligen Queue im Debugger oder in den Performance Tools. Derzeit gibt es zwei Typen: DISPATCH_QUEUE_SERIAL (oder NULL) für serielle und DISPATCH_QUEUE_CONCURRENT für nebenläufige Queues.
Nebenläufige Queues (global dispatch queues) führen mehrere Aufgaben gleichzeitig aus. Auch hier werden alle Tasks auf unterschiedlichen Threads ausgeführt. Die genaue Anzahl an parallel ausgeführten Aufgaben zu einer bestimmten Zeit hängt vom System ab.
Seit iOS 5 können nebenläufige Dispatch Queues ebenfalls mit der Funktion dispatch_queue_create erstellt werden. Hierzu wird lediglich als Typ DISPATCH_QUEUE_CONCURRENT übergeben.
dispatch_queue_t global_q1 = dispatch_queue_create("de.lmis.global", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t global_q2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_FAULT, 0);
Zusätzlich existieren vier vordefinierte globale Queues, die über dispatch_get_global_queue zu erreichen sind. Diese Queues unterscheiden sich dabei in ihren Prioritätslevels (Background, Low, Default, High). Queues mit hohem Prioritätslevel werden erwartungsgemäß vor Queues mit niedrigem Prioritätslevel ausgeführt.
Die Main Dispatch Queue ist eine globale, serielle Queue, die ihre eingereihten Tasks auf dem bereits erwähnten Main-Thread ausführt. Sie kann über die Funktion dispatch_get_main_queue() erreicht werden.
dispatch_queue_t main_q = dispatch_get_main_queue();
Es gibt zwei Möglichkeiten, Aufgaben in eine Queue einzureihen: synchron oder asynchron. Dabei kann nicht vorhergesagt werden, wann genau dieser Code ausgeführt wird. Daher ist die asynchrone Variante der synchronen vorzuziehen.
Wird eine Aufgabe synchron mit dispatch_sync in eine Queue eingereiht, so blockiert diese Aufgabe die Queue solange, bis der Block abgearbeitet wurde.
dispatch_sync(queue, ^{ NSLog(@"Do some synchronized work here!"); }); NSLog(@"Block is completed.");
Daher sollte dispatch_sync niemals von einem Block mit derselben Queue aufgerufen werden, da so gerade bei seriellen Queues ein Deadlock entsteht.
dispatch_async(queue, ^{ NSLog(@"Do some heavy asynchron work here!"); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Make some UI updates"); }); }); NSLog(@"Block may or may not have run.");
Asynchrones Einreihen von Aufgaben mit dispatch_async ist die empfohlene Variante, da die Queue von dem Code nicht blockiert wird. Sie sorgt dafür, dass zeitintensive Berechnungen nicht auf dem Main-Thread abgearbeitet und die UI somit auch nicht einfriert. Soll das Ergebnis einer solchen Hintergrundoperation im UI angezeigt werden, ist darauf zu achten, dass das nur auf dem Main-Thread geschehen darf. Dazu muss der entsprechende Code asynchron in die Main-Queue eingereiht werden.
dispatch_sync(queue, ^{ dispatch_sync(queue, ^{ //DEADLOCK }); });
Mit GCD vereinfacht sich auch die Initialisierung von Singleton-Objekten ungemein. Hierzu empfiehlt sich die Ausführung eines Blockes mit der Funktion dispatch_once. Diese garantiert, dass der Code auch bei parallelen Aufrufen aus mehreren Threads nur einmal ausgeführt wird.
+(instancetype)sharedInstance { static id instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; }
Apple hat mit der Veröffentlichung von Grand Central Dispatch einen wahren Meilenstein für die Entwicklung nebenläufiger Applikationen gelegt. Es ist nun möglich, komplexe Berechnungen effizient und einfach auf mehreren Threads bzw. CPUs aufzuteilen, ohne direkt mit ihnen arbeiten zu müssen. Es gibt sogar weitere APIs, wie NSOperation bzw. NSOperationQueue, die auf GCD aufbauen, sodass auf einem noch höheren Level entwickelt werden kann. Aber auch durch die Einführung der Blöcke sind viele neue Möglichkeiten entstanden, effizienter und dennoch transparent zu entwickeln.
Im Großen und Ganzen ist Apple mit GCD der Entwicklung leistungsstarker Applikationen einen entscheidenden Schritt entgegen gekommen.