Multitasking in iOSAls das erste iPhone 2007 erschien wurde es von vielen belächelt. Die Gründe dafür waren mannigfaltig, ein Argument wurde aber oft genannt: Das fehlende Multitasking. Tatsächlich bot das iPhone zu Beginn keinerlei Möglichkeiten, um Aktionen im Hintergrund auszuführen; nur die im Vordergrund befindliche App konnte benutzt werden und Aktionen durchführen, alle anderen wurden beendet.

Seit der Einführung von iOS 4 hat sich die Situation nach und nach ein wenig gelockert. Damals führte Apple das als „eingeschränkt“ bezeichnete Multitasking ein. Damit erlaubte Apple erstmals, bestimmte Aktionen auch im Hintergrund auszuführen und damit den eigenen Apps mehr Möglichkeiten zu geben. Das grundlegende Modell dahinter ist bis heute gleich geblieben, jedoch wurde es von Apple nach und nach immer mehr ausgebaut und die Möglichkeiten erweitert. Im Folgenden gebe ich Ihnen einen Einblick in die Möglichkeiten, die iOS im Bereich Multitasking bietet, wie Sie diese in eigenen Apps nutzen und welche Einschränkungen es gibt.

 

Background Execution

Wenn es um Multitasking in iOS geht, spricht Apple von der sogenannten Background Execution (sprich alle Aktionen, die eine App im Hintergrund ausführt während sie nicht aktiv ist). Dazu stellt uns die Architektur von iOS insgesamt drei Möglichkeiten zur Verfügung, um Aktionen im Hintergrund auszuführen:

  1. Durchführen kleiner und kurzer Aufgaben wenn eine App in den Hintergrund wechselt.
  2. Durchführen von angestoßenen Downloads im Hintergrund.
  3. Komplettes Weiterlaufen der App im Hintergrund für spezifische Background Tasks

Mehr Möglichkeiten gibt es in iOS nicht, um Aktionen und Aufgaben im Hintergrund auszuführen. Im Folgenden möchte ich diese drei Möglichkeiten einmal im Detail vorstellen. Beginnen wir dabei mit dem Ausführen kleiner und kurzer Aufgaben einer App beim Wechsel in den Hintergrund.

 

Ausführen zeitlich begrenzter Tasks

Unter dem Durchführen kleiner und kurzer Aufgaben im Hintergrund versteht Apple technisch gesehen einen Block, den wir als so genannten Background Task vom System im Hintergrund ausführen lassen, sobald unsere App in den Hintergrund wechselt. In der Regel erfolgt dieser Wechsel in den Hintergrund, wenn der Nutzer unsere App auf iPhone, iPad oder iPad touch beendet, entweder durch aktives Drücken der Home-Taste oder durch Aktionen wie einen eintreffenden Telefonanruf. Die App verliert dadurch ihren Vordergrund-Fokus und wandert in den Hintergrund. Darüber informiert eine entsprechende Methode des UIApplicationDelegate-Protokolls, die wir innerhalb des App Delegate unserer App implementieren können. Listing 1 zeigt die Definition dieser Methode in Swift und Objective-C.

Listing 1: Deklaration der App Delegate-Methode zum Wechsel in den Hintergrund

// Swift
optional func applicationDidEnterBackground(_ application: UIApplication)

// Objective-C
- (void)applicationDidEnterBackground:(UIApplication *)application

 

Die Methode applicationDidEnterBackground: wird gefeuert, sobald unsere App in den Hintergrund wechselt. Innerhalb dieser Methode haben wir dann die Möglichkeit, zeitlich begrenzte und kurze Aufgaben auszuführen, auch wenn unsere App schon gar nicht mehr im Vordergrund ist. Apple betont aber explizit, dass es sich bei diesen Aufgaben tatsächlich nur um abschließende Befehle handeln sollte, um eine womöglich laufende Aktion noch vernünftig und ohne Fehler zu verursachen zu beenden. Für ein beständiges Weiterlaufen der App im Hintergrund ist diese Technik also weder gedacht noch geeignet.

Innerhalb dieser Methode können wir nun den gewünschten Code implementieren, der im Hintergrund ausgeführt werden soll. Typischerweise wird dieser Code in einen asynchronen Ausführungsblock mittels Grand Central Dispatch gebracht. Doch bevor wir unseren im Hintergrund auszuführenden Code in der Methode applicationDidEnterBackground: implementieren, gilt es, ein zusätzliches Objekt zu erstellen, welches unseren gewünschten Background Task eindeutig identifiziert. Dabei handelt es sich um ein Objekt vom Typ UIBackgroundTaskIdentifier. Um ein solches Objekt zu erstellen, liefert die Klasse UIApplication zwei Methoden, die uns ein entsprechend konfiguriertes Objekt vom Typ UIBackgroundTaskIdentifier zurückliefert. Diese Methoden können Sie somit direkt über das von der Methode applicationDidEnterBackground: übergebene UIApplication-Objekt aufrufen. Die Deklaration der Methoden finden Sie in Listing 2.

Listing 2: Deklaration der Methoden zum Registrierten eines Hintergrund-Blocks

// Swift
func beginBackgroundTaskWithName(_ taskName: String?,
 
expirationHandler handler: (() -> Void)?) -> 
UIBackgroundTaskIdentifier
func beginBackgroundTaskWithExpirationHandler(_ handler: () -> 
Void) -> UIBackgroundTaskIdentifier

// Objective-C
-(UIBackgroundTaskIdentifier)beginBackgroundTaskWithName:
(NSString *)taskName
 expirationHandler:(void (^)(void))handler
-(UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:
(void (^)(void))handler

 

Beiden Methoden gemein ist, dass Sie als Parameter handler einen Block ohne Parameter und ohne Rückgabewert erwarten. Innerhalb dieses Blocks sollten Sie allen notwendigen Code zum Aufräumen und Abschließen Ihrer Hintergrundaktion hinterlegen, denn er wird aufgerufen, sobald die zur Ausführung der Hintergrundaktion zur Verfügung stehende Zeit so gut wie am Ende ist. Nutzen Sie daher diesen Block, um Ihre App in einem sauber konfigurierten und nicht instabilen Zustand zurückzulassen.

Der zusätzliche Parameter taskName der Methode beginBackgroundTaskWithName:expirationHandler: ist für Debug-Zwecke gedacht, da Sie über diesen String im Debugger sehen können, welcher Background Task gerade ausgeführt wird. Weitere Aktionen sind damit nicht vorgesehen. Gerade wenn Sie aber sowieso nur einen einzigen Background Task im Hintergrund ausführen möchten, ist die Verwendung der Methode beginBackgroundTaskWithExpirationHandler: vollkommen ausreichend.

Wie Sie anhand der Deklaration in Listing 2 sehen, liefern beide Methoden ein Objekt vom Typ UIBackgroundTaskIdentifier zurück. Einen solchen brauchen wir, um am Ende der Aktionen, die wir innerhalb der Methode applicationDidEnterBackground: im Hintergrund ausführen, dem System Bescheid zu geben, dass unsere Arbeit nun getan ist und die App nun nicht weiter im Hintergrund laufen muss. Zu diesem Zweck rufen wir am Ende unserers Codes die Methode endBackgroundTask: der Klasse UIApplication auf und übergeben ihr das zuvor erstellte UIBackgroundTaskIdentifier-Objekt. Zu guter Letzt weisen wir eben jenem Objekt noch den Wert UIBackgroundTaskInvalid zu. Damit weiß iOS, dass die Hintergrundarbeit dieser App erledigt ist und muss sie nicht länger aktiv halten.

Dasselbe gilt übrigens für den Block, den wir dem Parameter handler beim Erstellen des UIBackgroundTaskIdentifier-Objekts übergeben. Auch dieser sollte am Ende die Methode endBackgroundTask: aufrufen und sich darüber selbst beenden, auch das anschließende Setzen auf den wert UIBackgroundTaskInvalid sollte darin durchgeführt werden.

Listing 3 gibt einmal einen Überblick über den typischen Aufbau des Codes, der innerhalb der Methode applicationDidEnterBackground: gesetzt werden sollte. Da, wo Sie Ihre eigene Logik zum Ausführen im Hintergrund setzen, weise ich in Form eines Kommentars entsprechend hin.

Listing 3: Beispielhafte Implementierung der Methode applicationDidEnterBackground:

// Swift
func applicationDidEnterBackground(application: UIApplication) {
   var backgroundTask = UIBackgroundTaskInvalid
   backgroundTask = application.beginBackgroundTaskWithExpirationHandler { () -> Void in
       // Aufräumarbeiten, verfügbare Hintergundzeit ist fast vorüber
       application.endBackgroundTask(backgroundTask)
       backgroundTask = UIBackgroundTaskInvalid
   }
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
       // Code zum Ausführen im Hintergrund
       application.endBackgroundTask(backgroundTask)
       backgroundTask = UIBackgroundTaskInvalid
   })
}

 

// Objective-C
- (void)applicationDidEnterBackground:(UIApplication *)application
{
   __block UIBackgroundTaskIdentifier backgroundTask = UIBackgroundTaskInvalid;
   backgroundTask = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{
       // Aufräumarbeiten, verfügbare Hintergundzeit ist fast vorüber
       [application endBackgroundTask:backgroundTask];
       backgroundTask = UIBackgroundTaskInvalid;
   }
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // Code zum Ausführen im Hintergrund
       [application endBackgroundTask:backgroundTask];
       backgroundTask = UIBackgroundTaskInvalid;
   }
}

 

Die Ausführung vom Code im Hintergrund unterteilt sich hierbei also in zwei Schritte:

  1. Erstellen eines Background Tasks inklusive Block, wenn die verfügbare Zeit zur Ausführung im Hintergrund gering wird.
  2. Asynchrones Ausführen der gewünschten Hintergrundaktion.

Wundern Sie sich im Übrigen nicht über die initiale Zuweisung von UIBackgroundTaskInvalid zur Variable backgroundTask in Listing 3, das dient lediglich dazu, erfolgreich ein Objekt vom Typ UIBackgroundTaskIdentifier zu initialisieren, damit dieser innerhalb des Blocks des handler-Parameters genutzt werden kann.

 

Downloads im Hintergrund ausführen

Mit iOS 7 hat Apple neue Klassen eingeführt, die es erlauben, Downloads einer App im Hintergrund weiter laufen zu lassen, und das selbst dann, wenn die entsprechende App zwischenzeitlich komplett beendet wird. Grundlage für diese Technik sind die Klassen NSURLSession und NSURLSessionConfiguration. Zunächst geht es dabei an die Konfiguration eines passenden NSURLSessionConfiguration-Objekts. Listing 4 zeigt diese grundlegende Konfiguration mit allen essenziellen Einstellungen.

 

Listing 4: Konfiguration eines NSURLSessionConfiguration-Objekts für die Ausführung von Downloads im Hintergrund

// Swift
let backgroundSessionConfiguration =
NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(“BackgroundDownload“)
backgroundSessionConfiguration.sessionSendsLaunchEvents = true
backgroundSessionConfiguration.discretionary = true

 

// Objective-C
NSURLSessionConfiguration *backgroundSessionConfiguration =
[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@“BackgroundDownload“];
backgroundSessionConfiguration.sessionSendsLaunchEvents = YES;
backgroundSessionConfiguration.discretionary = YES;

 

Wie Sie sehen sind drei Schritte wichtig: Zunächst muss die Instanz der Klasse NSURLSessionConfiguration mittels der Klassenmethode backgroundSessionConfigurationWithIdentifier: erstellt werden. Dabei muss ihr ein eindeutiger Identifier als String übergeben werden, der später dazu dient, den entsprechenden Download zu identifizieren. Darüber hinaus müssen noch die beiden Properties sessionSendsLaunchEvents und descretionary auf true (Swift) beziehungsweise YES (Objective-C) gesetzt werden. Damit ist unser NSURLSessionConfiguration-Objekt so konfiguriert, dass es auch Downloads im Hintergrund durchführen kann.

Damit ist der größte Teil der Arbeit von unserer Seite erledigt. Was nun noch bleibt, ist die Erstellung einer NSURLSession, die sich um den Download kümmert. Wichtig ist dabei, diese Instanz mithilfe der Klassenmethode sessionWithConfiguration: zu erstellen (alternativ steht noch die Klassenmethode sessionWithConfiguration:delegate:delegateQueue: zur Verfügung, die bereits eine weitreichendere Konfiguration der NSURLSession-Instanz erlaubt. Alle Downloads, die Sie nun mit diesem Objekt anstoßen, werden automatisch vom System im Hintergrund weitergeführt.

Wird nun ein solcher Download im Hintergrund abgeschlossen, wird die Methode application:handleEventsForBackgroundURLSession:completionHandler: des App Delegate aufgerufen (die genaue Deklaration dieser Methode finden Sie in Listing 5).

 

Listing 5: Deklaration der Methode nach Abschluss 
eines Downloads im Hintergrund
// Swift
optional func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,

completionHandler completionHandler: () -> Void)

// Objective-C
- (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier

completionHandler:(void (^)(void))completionHandler

 

Der Parameter identifier entspricht dabei dem String, der zuvor bei der Initialisierung der NSURLSessionConfiguration vergeben wurde. Er dient dazu, den angestoßenen Download zu identifizieren und entsprechende Aktualisierungen an der App innerhalb der Methode application:handleEventsForBackgroundURLSession:completionHandler: vorzunehmen. Am Ende der Methode soll noch der übergebene Block completionHandler ausgeführt werden, um darüber iOS mitzuteilen, dass der Download erfolgreich innerhalb der App abgeschlossen wurde.

 

Ausführen großer und lang andauernder Tasks

Neben den bisher gezeigten Möglichkeiten erlaubt iOS auch tatsächlich das längerfristige Ausführen von Funktionen im Hintergrund, allerdings nur für spezielle Einsatzgebiete; diese sind im Folgenden einmal vollständig aufgelistet:

  • Audio und AirPlay: Damit können Apps im Hintergrund Sound wiedergeben oder Sprache aufzeichnen.
  • Location Updates: Erlaubt das Auslesen des aktuellen Standorts des Nutzers im Hintergrund.
  • Voice over IP: Erlaubt das Tätigen von Telefonaten über das Internet, auch wenn die App nicht im Vordergrund ist.
  • Newsstand Downloads: Neue Artikel und Ausgaben einer Newsstand-App können darüber im Hintergrund aktualisiert und geladen werden.
  • External Accessory Communication: Erlaubt die Verbindung und Kommunikation mit externen Geräten.
  • Uses Bluetooth LE Accessories: Erlaubt die Verbindung und Kommunikation mit bestimmten Bluetooth-Geräten.
  • Acts as Bluetooth LE Accessory: Erlaubt der App, sich wie ein externes Bluetooth-Gerät zu verhalten, auch im Hintergrund.
  • Background Fetch: Die App hält die Verbindung zu einem Netzwerk aufrecht und kann regelmäßig Aktualisierungen durchführen.
  • Remote Notifications: Erlaubt den Download von Inhalten beim Eingehen einer Push Notification.

Diese einzelnen Einsatzgebiete erlauben umfangreiche Hintergrundaktivitäten. Wie diese im Detail aussehen, ist dabei von Einsatzgebiet zu Einsatzgebiet unterschiedlich. Die technischen Möglichkeiten werden letzten Endes durch die Frameworks geregelt, die den einzelnen Einsatzgebieten zugrunde liegen, weshalb eine nähere Betrachtung für jedes einzelne Thema den Rahmen sprengen würde. So wissen Sie aber um die Möglichkeiten, die iOS Ihnen bietet und können im Einzelfall prüfen, wie Sie diese verschiedenen Einsatzgebiete für sich und Ihre Apps nutzen können.