ObjectiveCeeds › iOS 7: Background Fetch

iOS 7: Background Fetch

Von Manfred Kreß
Vordergründiges
Seite 1 von 3



Mit iOS 7 wurde das Multitasking überarbeitet. Nun ja, genau genommen ist es noch das alte Mutitasking: Apps werden nach dem Verlassen über den Homebutton im Hintergrund eingefroren, sprich bleiben im Speicher, bekommen aber die Rechenzeit entzogen. Neu ist allerdings die Anzeige aller laufenden Apps im Taskmanager mit Vorschau und es gibt neue "Background Modes" - unter anderem den "Fetch", um den es in diesem Artikel geht.

Diese Beispielapp wird bei einem Webservice periodisch im Hintergrund eine neue Farbe abfragen und sich dann entsprechend anpassen. Ausgangsbasis für das Beispiel ist ein das Xcode Template "Empty Application". Bevor die App Background Fetches verwenden kann, müssen die Rechte dafür vergeben werden. Das geschieht über einen Key in der info.plist der App:
UIBackgroundModes >> fetch
Mit Xcode 5 können die Backgroundmodes allerdings in einen Tab in der Projektübersicht ausgewählt werden und müssen nicht mehr von Hand in der info.plist hinzugefügt werden. Capabilitys Das Beispielprojekt besteht eigentlich nur aus einer einzigen Klasse, dem AppDelegate. Die Implementation des AppDelegates (bei mir OCAppDelegate) wird zuerst wie folgt verändert:

#import "OCAppDelegate.h"


@interface OCAppDelegate ()

@property (readonly) UIViewController *viewController;

@end

@implementation OCAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    _viewController = [[UIViewController alloc] init];
    [[_viewController view] setBackgroundColor: [UIColor yellowColor]];
    [_viewController setTitle: @"ObjectiveCeeds"];
    
    UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController: _viewController];
    [[self window] setRootViewController: nc];
    
    self.window.backgroundColor = [UIColor blackColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Fertig ist die App! Ein NavigationController, ein ViewController. Der View des Controllers wird eine Farbe zugewiesen. In periodischen Abständen soll über einen Webservice die neue Farbe der View ermittelt und entsprechend geändert werden. Um nachher leichten Zugriff auf den ViewController bzw. dessen View zu haben, wird der Controller als Property im AppDelegate gespeichert. Im Prinzip müssen nun drei Dinge für den Background Fetch implementiert werden. 1. Über UIApplication muss dem System ein Fetch Intervall übergeben werden. Damit werden die Fetches ein- bzw. ausgeschaltet. 2. Im AppDelegate muss eine weitere Methode implementiert werden. Diese wird vom System bei jedem Fetch aufgerufen und wir bekommen hier jedesmal einen Block "completitionHandler" übergeben. 3. Wenn der Fetch abgeschlossen ist muss dieser CompletitionHandler ausgeführt werden. Er bekommt als Argument einen UIBackgroundFetchResult übergeben. Also los. Die Beispielapp reistriert sich in der applicationDidFinishLaunchingWithOptions: für Hintergrundaktivitäten. Am Anfang der Delegate Methode einfach diese Zeile hinzufügen:
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
und damit sieht die Methode jetzt so aus:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    
    /*
        Enable Background Fetch
     */
    [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    _viewController = [[UIViewController alloc] init];
    [[_viewController view] setBackgroundColor: [UIColor yellowColor]];
    [_viewController setTitle: @"ObjectiveCeeds"];
    
    UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController: _viewController];
    [[self window] setRootViewController: nc];
    
    self.window.backgroundColor = [UIColor blackColor];
    [self.window makeKeyAndVisible];
    return YES;
}
setMinimumBackgroundFetchInterval: bekommt ein NSTimeIntervall übergeben, mit dem festgelegt wird, in welchen Abständen (in Sekunden) ein Background Fetch erfolgen soll. Allerdings dürfen wir uns hier nur etwas "wünschen", letztendlich entscheidet das System darüber, wann und wie oft ein Fetch ausgeführt wird. Dazu später etwas mehr. iOS stellt uns hier zwei Konstanten für Standardintervalle zur Verfügung: UIApplicationBackgroundFetchIntervalMinimum -> die Abstände so kurz wie möglich UIApplicationBackgroundFetchIntervalNone -> keine Fetches Für Hintergrund Fetches muss man sich nicht zwingend in der applicationDidFinishLaunching… registrieren. Wird z.B. ein Login bei einem Webservice für Updates gebraucht, kann man entsprechend dem Login Status die Fetches ein oder abschalten. Als nächstes wird im AppDelegate die Methode
- (void) application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
implementiert. Diese Methode wird vom Sytem periodisch aufgerufen. Hier bekommt die App dann ein wenig Rechenzeit, führt die Hintergrundaktivitäten aus und schließlich rufen wir den completitionHandler auf und übergeben ein UIBackgroundFetchResult . Hier nun die Implementation der Methode:
- (void) application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    NSLog(@"Background fetch");
    
    NSError *e = nil;
    
    NSURL *url = [NSURL URLWithString: @"http://objectiveceeds.com/app/color.php"];
    
    NSData *d = [[NSData alloc] initWithContentsOfURL: url
                                              options: NSDataReadingUncached
                                                error: &e];
    
    if (e != nil) // Primitive Error Handling
    {
        NSLog(@"Fetch error %@", e);
        completionHandler (UIBackgroundFetchResultFailed);
        return;
    }
    
    e = nil;
    NSDictionary *colorDict = [NSJSONSerialization JSONObjectWithData: d
                                                              options: 0
                                                                error: &e];
    
    if (e != nil)
    {
        NSLog(@"JSON error %@", e);
        completionHandler (UIBackgroundFetchResultFailed);
        return;
    }
    
    CGFloat red = [[colorDict objectForKey: @"red"] floatValue];
    CGFloat green = [[colorDict objectForKey: @"green"] floatValue];
    CGFloat blue = [[colorDict objectForKey: @"blue"] floatValue];
    
    
    // update UI
    UIColor *newColor = [UIColor colorWithRed: red
                                        green: green
                                         blue: blue
                                        alpha: 1.0];
    
    [[_viewController view] setBackgroundColor: newColor];
    
    // fire Notification
    UILocalNotification *note = [[UILocalNotification alloc] init];
    [note setAlertBody: @"Color did change"];
    [[UIApplication sharedApplication] scheduleLocalNotification: note];

    // call Completition Handler
    completionHandler (UIBackgroundFetchResultNewData);
}
Hier wird nun einfach bei meinem Webservice eine neue Farbe angefordert. Der Service gibt in diesem Beispiel einfach ein JSON File mit Zufälligen RGB Werten zurück. Es folgt die übliche Fehlerbehandlung, dann wird aus den Daten ein UIColor Objekt erzeugt und der View als Hintergrundfarbe zugewiesen. Obendrein wird noch ein UILocalNotification ausgeliefert um den Anwender über die dramatischen Änderungen zu informieren. Wichtig ist, immer den completitionHandler aufzurufen! Dem können drei Werte übergeben werden:
typedef NS_ENUM(NSUInteger, UIBackgroundFetchResult) {
    UIBackgroundFetchResultNewData,
    UIBackgroundFetchResultNoData,
    UIBackgroundFetchResultFailed
}
Ich denke die Bezeichner sprechen für sich. Aber das schönste ist: Rufen wir den completitionHandler mit UIBackgroundFetchResultNewData auf, wird automatisch das Userinterface der App offline in einen Screenshot gerendert und die Vorschau der App im "Taskmanager" wird aktualisiert. Zeit die App zu starten. Am besten auf dem Gerät. In Xcode 5 kann dann ein BackgroundFetch von Hand getriggert werden. Wenn die App läuft, Menu > Debug > Simulate Background Fetch. Die Hintergrundfarbe wird abgefragt, aktualisiert und im Taskmanager wird immer die App mit der aktuellen UI (in dem Fall die Farbe) zu sehen sein. Wer natürlich Zeit hat, kann auch einfach warten, bis das System den Fetch ausführt. Das kann allerdings ein Wenig dauern. Dazu gleich mehr...

nächste Seite »
Seite: 1 von 3


iOS 7: Background Fetch

Vordergründiges
Hintergründiges
Code