Advertisement
  1. Code
  2. Core Data

iOS 8: Kerndaten und asynchrones Abrufen

Scroll to top
Read Time: 15 min
This post is part of a series called Core Data from Scratch.
iOS 8: Core Data and Batch Updates

() translation by (you can also view the original English article)

Im vorherigen Artikel über iOS 8 und Core Data haben wir Batch-Updates besprochen.  Batch-Updates sind nicht die einzige neue API in der Stadt.  Ab iOS 8 und OS X Yosemite ist es möglich, Daten asynchron abzurufen.  In diesem Lernprogramm werden wir uns genauer ansehen, wie asynchrones Holen implementiert wird und in welchen Situationen Ihre Anwendung von dieser neuen API profitieren kann

1. Das Problem

Wie Batch-Updates steht das asynchrone Holen schon seit geraumer Zeit auf der Wunschliste vieler Entwickler.  Fetch-Anfragen können komplex sein und erfordern eine nicht unerhebliche Zeitspanne.  Während dieser Zeit blockiert die Abrufanforderung den Thread, auf dem sie ausgeführt wird, und blockiert als Ergebnis den Zugriff auf den Kontext des verwalteten Objekts, der die Abrufanforderung ausführt.  Das Problem ist einfach zu verstehen, aber wie sieht die Lösung von Apple aus?

2. Die Lösung 

Apples Antwort auf dieses Problem ist asynchrones Abrufen.  Eine asynchrone Abrufanforderung wird im Hintergrund ausgeführt.  Dies bedeutet, dass andere Aufgaben während der Ausführung nicht blockiert werden, z. B. das Aktualisieren der Benutzeroberfläche im Hauptthread. 

Asynchrones Abrufen bietet auch zwei weitere praktische Funktionen, Fortschrittsberichte und Stornierungen.  Eine asynchrone Abrufanforderung kann jederzeit abgebrochen werden, z. B. wenn der Benutzer entscheidet, dass die Abrufanforderung zu lange dauert.  Fortschrittsberichte sind eine nützliche Ergänzung, um dem Benutzer den aktuellen Status der Abrufanforderung anzuzeigen. 

Asynchrones Holen ist eine flexible API.  Es ist nicht nur möglich, eine asynchrone Abrufanforderung abzubrechen, es ist auch möglich, Änderungen an dem verwalteten Objektkontext vorzunehmen, während die asynchrone Abrufanforderung ausgeführt wird.  Mit anderen Worten, der Benutzer kann weiterhin Ihre Anwendung verwenden, während die Anwendung eine asynchrone Abrufanforderung im Hintergrund ausführt. 

3. Wie funktioniert es?

Wie Batch-Updates werden asynchrone Abrufanforderungen an den verwalteten Objektkontext als ein NSPersistentStoreRequest-Objekt weitergegeben, um genau zu sein, eine Instanz der NSAsynchronicFetchRequest-Klasse. 

Eine NSAsynchronousFetchRequest-Instanz wird mit einem NSFetchRequest-Objekt und einem Abschlussblock initialisiert.  Der Abschlussblock wird ausgeführt, wenn die asynchrone Abrufanforderung seine Abrufanforderung abgeschlossen hat. 

Lassen Sie uns die To-Do-Anwendung, die wir zuvor in dieser Serie erstellt haben, erneut aufrufen und die aktuelle Implementierung der NSFetchedResultsController-Klasse durch eine asynchrone Abrufanforderung ersetzen. 

Schritt 1: Projekteinrichtung

Laden oder klonen Sie das Projekt von GitHub und öffnen Sie es in Xcode 6.  Bevor wir mit derNSAsynchronousFetchRequest-Klasse arbeiten können, müssen wir einige Änderungen vornehmen.  Wir können die NSFetchedResultsController-Klasse nicht zum Verwalten der Daten der Tabellenansicht verwenden, da die NSFetchedResultsController-Klasse für die Ausführung im Hauptthread vorgesehen ist. 

Schritt 2: Ersetzen des abgerufenen Ergebniscontrollers 

Beginnen Sie mit der Aktualisierung der privaten Klassenerweiterung der TSPViewController-Klasse, wie unten gezeigt.  Wir entfernen die Eigenschaft "hetchedResultsController" und erstellen eine neue Eigenschaft namens "NSArray" zum Speichern der Aufgaben.  Dies bedeutet auch, dass die TSPViewController-Klasse nicht mehr dem NSFetchedResultsControllerDelegate-Protokoll entsprechen muss.

1
@interface TSPViewController ()
2
3
@property (strong, nonatomic) NSArray *items;
4
5
@property (strong, nonatomic) NSIndexPath *selection;
6
7
@end

Bevor wir die Methode viewDidLoad umgestalten, möchte ich zuerst die Implementierung des UITableViewDataSource-Protokolls aktualisieren.  Sehen Sie sich die Änderungen an, die ich in den folgenden Codeblöcken vorgenommen habe. 

1
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
2
    return self.items ? 1 : 0;
3
}
1
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
2
    return self.items ? self.items.count : 0;
3
}
1
- (void)configureCell:(TSPToDoCell *)cell atIndexPath:(NSIndexPath *)indexPath {
2
    // Fetch Record

3
    NSManagedObject *record = [self.items objectAtIndex:indexPath.row];
4
    
5
    // Update Cell

6
    [cell.nameLabel setText:[record valueForKey:@"name"]];
7
    [cell.doneButton setSelected:[[record valueForKey:@"done"] boolValue]];
8
    
9
    [cell setDidTapButtonBlock:^{
10
        BOOL isDone = [[record valueForKey:@"done"] boolValue];
11
        
12
        // Update Record

13
        [record setValue:@(!isDone) forKey:@"done"];
14
    }];
15
}
1
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
2
    if (editingStyle == UITableViewCellEditingStyleDelete) {
3
        NSManagedObject *record = [self.items objectAtIndex:indexPath.row];
4
        
5
        if (record) {
6
            [self.managedObjectContext deleteObject:record];
7
        }
8
    }
9
}

Wir müssen auch eine Codezeile in der prepareForSegue: sender: -Methode wie unten gezeigt ändern. 

1
// Fetch Record

2
NSManagedObject *record = [self.items objectAtIndex:self.selection.row];

Löschen Sie nicht zuletzt die Implementierung des NSFetchedResultsControllerDelegate-Protokolls, da wir es nicht mehr benötigen. 

Schritt 3: Erstellen der asynchronen Abrufanforderung 

Wie Sie unten sehen können, erstellen wir die asynchrone Abrufanforderung in der viewDidLoad-Methode des View-Controllers.  Lass uns einen Moment nehmen, um zu sehen, was vor sich geht. 

1
- (void)viewDidLoad {
2
    [super viewDidLoad];
3
    
4
    // Helpers

5
    __weak TSPViewController *weakSelf = self;
6
    
7
    // Initialize Fetch Request

8
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"TSPItem"];
9
    
10
    // Add Sort Descriptors

11
    [fetchRequest setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:YES]]];
12
    
13
    // Initialize Asynchronous Fetch Request

14
    NSAsynchronousFetchRequest *asynchronousFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult *result) {
15
        dispatch_async(dispatch_get_main_queue(), ^{
16
            // Process Asynchronous Fetch Result

17
            [weakSelf processAsynchronousFetchResult:result];
18
        });
19
    }];
20
    
21
    // Execute Asynchronous Fetch Request

22
    [self.managedObjectContext performBlock:^{
23
        // Execute Asynchronous Fetch Request

24
        NSError *asynchronousFetchRequestError = nil;
25
        NSAsynchronousFetchResult *asynchronousFetchResult = (NSAsynchronousFetchResult *)[weakSelf.managedObjectContext executeRequest:asynchronousFetchRequest error:&asynchronousFetchRequestError];
26
        
27
        if (asynchronousFetchRequestError) {
28
            NSLog(@"Unable to execute asynchronous fetch result.");
29
            NSLog(@"%@, %@", asynchronousFetchRequestError, asynchronousFetchRequestError.localizedDescription);
30
        }
31
    }];
32
}

Zunächst erstellen und konfigurieren wir eine NSFetchRequest-Instanz, um die asynchrone Abrufanforderung zu initialisieren.  Es ist diese Abrufanforderung, bei der die asynchrone Abrufanforderung im Hintergrund ausgeführt wird.

1
// Initialize Fetch Request

2
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"TSPItem"];
3
4
// Add Sort Descriptors

5
[fetchRequest setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:YES]]];

Um eine NSAsynchronousFetchRequest-Instanz zu initialisieren, rufen wir initWithFetchRequest auf: completionBlock :, wobei fetchRequest und ein Completion-Block übergeben werden. 

1
// Initialize Asynchronous Fetch Request

2
NSAsynchronousFetchRequest *asynchronousFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult *result) {
3
    dispatch_async(dispatch_get_main_queue(), ^{
4
        // Process Asynchronous Fetch Result

5
        [weakSelf processAsynchronousFetchResult:result];
6
    });
7
}];

Der Abschlussblock wird aufgerufen, wenn die asynchrone Abrufanforderung die Ausführung seiner Abrufanforderung abgeschlossen hat.  Der Completion-Block verwendet ein Argument vom Typ NSAsynchronousFetchResult, das das Ergebnis der Abfrage sowie einen Verweis auf die ursprüngliche asynchrone Abrufanforderung enthält. 

Im Completion-Block rufen wir processAsynchronousFetchResult: auf und übergeben das NSAsynchronousFetchResult-Objekt.  Wir sehen uns diese Hilfsmethode in einigen Augenblicken an. 

Die Ausführung der asynchronen Abrufanforderung ist fast identisch mit der Ausführung eines NSBatchUpdateRequest.  Wir rufen executeRequest auf: error: im Kontext des verwalteten Objekts, wobei die asynchrone Abrufanforderung und ein Zeiger auf ein NSError-Objekt übergeben werden.

1
[self.managedObjectContext performBlock:^{
2
    // Execute Asynchronous Fetch Request

3
    NSError *asynchronousFetchRequestError = nil;
4
    NSAsynchronousFetchResult *asynchronousFetchResult = (NSAsynchronousFetchResult *)[weakSelf.managedObjectContext executeRequest:asynchronousFetchRequest error:&asynchronousFetchRequestError];
5
    
6
    if (asynchronousFetchRequestError) {
7
        NSLog(@"Unable to execute asynchronous fetch result.");
8
        NSLog(@"%@, %@", asynchronousFetchRequestError, asynchronousFetchRequestError.localizedDescription);
9
    }
10
}];

Beachten Sie, dass wir die asynchrone Abrufanforderung durch Aufrufen von performBlock: im Kontext des verwalteten Objekts ausführen.  Obwohl dies nicht unbedingt notwendig ist, da die Methode viewDidLoad, in der wir die asynchrone Abrufanforderung erstellen und ausführen, im Hauptthread aufgerufen wird, ist es eine gute Angewohnheit und eine gute Vorgehensweise, dies zu tun. 

Obwohl die asynchrone Abrufanforderung im Hintergrund ausgeführt wird, beachten Sie, dass die executeRequest: error: -Methode sofort zurückkehrt und uns ein NSAsynchronousFetchResult-Objekt übergibt.  Sobald die asynchrone Abrufanforderung abgeschlossen ist, wird das gleiche NSAsynchronousFetchResult-Objekt mit dem Ergebnis der Abrufanforderung gefüllt. 

Schließlich prüfen wir, ob die asynchrone Abrufanforderung ohne Probleme ausgeführt wurde, indem überprüft wird, ob das NSError-Objekt gleich null ist.

Schritt 4: Verarbeitung des asynchronen Abrufergebnisses 

Die processAsynchronousFetchResult: -Methode ist nichts anderes als eine Hilfsmethode, in der wir das Ergebnis der asynchronen Abrufanforderung verarbeiten.  Wir setzen die items -Eigenschaft des View-Controllers mit dem Inhalt der finalResult -Eigenschaft des Ergebnisses und laden die Tabellenansicht neu. 

1
- (void)processAsynchronousFetchResult:(NSAsynchronousFetchResult *)asynchronousFetchResult {
2
    if (asynchronousFetchResult.finalResult) {
3
        // Update Items

4
        [self setItems:asynchronousFetchResult.finalResult];
5
        
6
        // Reload Table View

7
        [self.tableView reloadData];
8
    }
9
}

Schritt 5: Erstellen und Ausführen 

Erstellen Sie das Projekt und führen Sie die Anwendung im iOS-Simulator aus.  Sie werden möglicherweise überrascht sein, dass Ihre Anwendung abstürzt, wenn sie versucht, die asynchrone Abrufanforderung auszuführen.  Zum Glück sagt uns die Ausgabe in der Konsole, was schief gelaufen ist.

1
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSConfinementConcurrencyType context <NSManagedObjectContext: 0x7fce3a731e60> cannot support asynchronous fetch request <NSAsynchronousFetchRequest: 0x7fce3a414300> with fetch request <NSFetchRequest: 0x7fce3a460860> (entity: TSPItem; predicate: ((null)); sortDescriptors: ((

2
    "(createdAt, ascending, compare:)"

3
)); type: NSManagedObjectResultType; ).'

Wenn Sie den Artikel über Kerndaten und Nebenläufigkeit nicht gelesen haben, können Sie verwirrt sein, was Sie gerade lesen.  Denken Sie daran, dass Core Data drei Typen für den gemeinsamen Zugriff deklariert: NSConfinementConcurrencyType, NSPrivateQueueConcurrencyType und NSMainQueueConcurrencyType.  Wenn Sie einen verwalteten Objektkontext durch Aufrufen der Initmethode der Klasse erstellen, entspricht der Parallelitätstyp des resultierenden verwalteten ObjektkontextsNSConfinementConcurrencyType.  Dies ist der standardmäßige Nebenläufigkeitstyp.

Das Problem besteht jedoch darin, dass das asynchrone Abrufen nicht mit dem Typ NSConfinementConcurrencyType kompatibel ist.  Ohne zu sehr ins Detail zu gehen, ist es wichtig zu wissen, dass die asynchrone Abrufanforderung die Ergebnisse ihrer Abrufanforderung mit dem verwalteten Objektkontext zusammenführen muss, der die asynchrone Abrufanforderung ausgeführt hat.  Es muss wissen, in welcher Dispatch-Warteschlange dies möglich ist, und deshalb unterstützen nur NSPrivateQueueConcurrencyType und NSMainQueueConcurrencyType asynchrones Abrufen.  Die Lösung ist jedoch sehr einfach.

Schritt 6: Konfigurieren des verwalteten Objektkontexts 

Öffnen Sie TSPAppDelegate.m und aktualisieren Sie die Methode managedObjectContext wie unten gezeigt.

1
- (NSManagedObjectContext *)managedObjectContext {
2
    if (_managedObjectContext) {
3
        return _managedObjectContext;
4
    }
5
    
6
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
7
    
8
    if (coordinator) {
9
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
10
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
11
    }
12
    
13
    return _managedObjectContext;
14
}

Die einzige Änderung, die wir vorgenommen haben, ist das Ersetzen der init-Methode durch initWithConcurrencyType :, wobei NSMainQueueConcurrencyType als Argument übergeben wird.  Dies bedeutet, dass auf den verwalteten Objektkontext nur vom Hauptthread aus zugegriffen werden darf.  Dies funktioniert gut, solange wir die performBlock: oder performBlockAndWait: -Methoden verwenden, um auf den Kontext des verwalteten Objekts zuzugreifen.

Führen Sie das Projekt noch einmal durch, um sicherzustellen, dass unsere Änderung das Problem tatsächlich behoben hat.

4. Zeige Fortschritt

Die NSAsynchronousFetchRequest-Klasse fügt Unterstützung hinzu, um den Fortschritt der Abrufanforderung zu überwachen, und es ist sogar möglich, eine asynchrone Abrufanforderung abzubrechen, z. B. wenn der Benutzer entscheidet, dass die Verarbeitung zu lange dauert.

Die NSAsynchronousFetchRequest-Klasse nutzt die NSProgress-Klasse zur Fortschrittsberichterstellung sowie zum Abbrechen einer asynchronen Abrufanforderung.  Die NSProgress-Klasse, die seit iOS 7 und OS X 10.9 verfügbar ist, ist eine clevere Möglichkeit, den Fortschritt einer Aufgabe zu überwachen, ohne die Aufgabe fest an die Benutzerschnittstelle koppeln zu müssen. 

Die NSProgress-Klasse unterstützt auch die Stornierung, wodurch eine asynchrone Abrufanforderung abgebrochen werden kann.  Lassen Sie uns herausfinden, was wir tun müssen, um die Fortschrittsberichterstattung für die asynchrone Abrufanforderung zu implementieren.

Schritt 1: Hinzufügen von SVProgressHUD 

Wir zeigen dem Benutzer den Fortschritt der asynchronen Abrufanforderung mithilfe der SVProgressHUD-Bibliothek von Sam Vermette an. Laden Sie die Bibliothek von GitHub herunter und fügen Sie den Ordner SVProgressHUD Ihrem Xcode-Projekt hinzu.

Schritt 2: Einrichten von NSProgress 

In diesem Artikel werden wir die NSProgress-Klasse nicht detailliert untersuchen, aber lesen Sie mehr dazu in der Dokumentation.  Wir erstellen eine NSProgress-Instanz in dem Block, den wir der performBlock: -Methode in der viewDidLoad-Methode des View-Controllers übergeben. 

1
// Create Progress

2
NSProgress *progress = [NSProgress progressWithTotalUnitCount:1];
3
4
// Become Current

5
[progress becomeCurrentWithPendingUnitCount:1];

Sie werden überrascht sein, dass wir die Gesamtzahl der Einheiten auf 1 setzen.  Der Grund ist einfach.  Wenn Core Data die asynchrone Abrufanforderung ausführt, weiß es nicht, wie viele Datensätze im persistenten Speicher gefunden werden.  Das bedeutet auch, dass wir dem Benutzer den relativen Fortschritt nicht anzeigen können - ein Prozentsatz.  Stattdessen zeigen wir dem Benutzer den absoluten Fortschritt - die Anzahl der gefundenen Datensätze. 

Sie können dieses Problem beheben, indem Sie eine Abrufanforderung ausführen, um die Anzahl der Datensätze abzurufen, bevor Sie die asynchrone Abrufanforderung ausführen.  Ich möchte dies jedoch nicht tun, da dies auch bedeutet, dass das Abrufen der Datensätze aus dem persistenten Speicher aufgrund der zusätzlichen Abrufanforderung zu Beginn länger dauert.

 Schritt 3: Hinzufügen eines Beobachters

Wenn wir die asynchrone Abrufanforderung ausführen, wird uns sofort ein NSAsynchronousFetchResult-Objekt übergeben.  Dieses Objekt hat eine Fortschrittseigenschaft vom Typ NSProgress.  Es ist diese Fortschrittseigenschaft, die wir beobachten müssen, wenn wir Fortschrittsupdates erhalten möchten.

1
// Execute Asynchronous Fetch Request

2
[self.managedObjectContext performBlock:^{
3
    // Create Progress

4
    NSProgress *progress = [NSProgress progressWithTotalUnitCount:1];
5
    
6
    // Become Current

7
    [progress becomeCurrentWithPendingUnitCount:1];
8
    
9
    // Execute Asynchronous Fetch Request

10
    NSError *asynchronousFetchRequestError = nil;
11
    NSAsynchronousFetchResult *asynchronousFetchResult = (NSAsynchronousFetchResult *)[self.managedObjectContext executeRequest:asynchronousFetchRequest error:&asynchronousFetchRequestError];
12
    
13
    if (asynchronousFetchRequestError) {
14
        NSLog(@"Unable to execute asynchronous fetch result.");
15
        NSLog(@"%@, %@", asynchronousFetchRequestError, asynchronousFetchRequestError.localizedDescription);
16
    }
17
    
18
    // Add Observer

19
    [asynchronousFetchResult.progress addObserver:self forKeyPath:@"completedUnitCount" options:NSKeyValueObservingOptionNew context:ProgressContext];
20
    
21
    // Resign Current

22
    [progress resignCurrent];
23
}];

Beachten Sie, dass wir resitCurrent für das Fortschrittsobjekt aufrufen, um den früheren Aufruf von becomeCurrentWithPendingUnitCount: auszugleichen.  Beachten Sie, dass diese beiden Methoden für denselben Thread aufgerufen werden müssen. 

Schritt 4: Entfernen des Beobachters

Im Abschlussblock der asynchronen Abrufanforderung entfernen wir den Beobachter und schließen das Fortschritts-HUD ab.

1
// Initialize Asynchronous Fetch Request

2
NSAsynchronousFetchRequest *asynchronousFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult *result) {
3
    dispatch_async(dispatch_get_main_queue(), ^{
4
        // Dismiss Progress HUD

5
        [SVProgressHUD dismiss];
6
        
7
        // Process Asynchronous Fetch Result

8
        [weakSelf processAsynchronousFetchResult:result];
9
        
10
        // Remove Observer

11
        [result.progress removeObserver:weakSelf forKeyPath:@"completedUnitCount" context:ProgressContext];
12
    });
13
}];

Bevor wir es umsetzen observeValueForKeyPath: ofObject: change: context :, wir müssen eine import-Anweisung für die SVProgressHUD-Bibliothek hinzufügen, die statische Variable ProgressContext deklarieren, die wir beim Hinzufügen und Entfernen des Beobachters als Kontext übergeben, und vor dem Erstellen des asynchronen Abrufs die Fortschritts-HUD anzeigen anfordern. 

1
#import "SVProgressHUD/SVProgressHUD.h"
1
static void *ProgressContext = &ProgressContext;
1
- (void)viewDidLoad {
2
    [super viewDidLoad];
3
    
4
    // Helpers

5
    __weak TSPViewController *weakSelf = self;
6
    
7
    // Show Progress HUD

8
    [SVProgressHUD showWithStatus:@"Fetching Data" maskType:SVProgressHUDMaskTypeGradient];
9
    
10
    // Initialize Fetch Request

11
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"TSPItem"];
12
    
13
    // Add Sort Descriptors

14
    [fetchRequest setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:YES]]];
15
    
16
    // Initialize Asynchronous Fetch Request

17
    NSAsynchronousFetchRequest *asynchronousFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult *result) {
18
        dispatch_async(dispatch_get_main_queue(), ^{
19
            // Dismiss Progress HUD

20
            [SVProgressHUD dismiss];
21
            
22
            // Process Asynchronous Fetch Result

23
            [weakSelf processAsynchronousFetchResult:result];
24
            
25
            // Remove Observer

26
            [result.progress removeObserver:weakSelf forKeyPath:@"completedUnitCount" context:ProgressContext];
27
        });
28
    }];
29
    
30
    // Execute Asynchronous Fetch Request

31
    [self.managedObjectContext performBlock:^{
32
        // Create Progress

33
        NSProgress *progress = [NSProgress progressWithTotalUnitCount:1];
34
        
35
        // Become Current

36
        [progress becomeCurrentWithPendingUnitCount:1];
37
        
38
        // Execute Asynchronous Fetch Request

39
        NSError *asynchronousFetchRequestError = nil;
40
        NSAsynchronousFetchResult *asynchronousFetchResult = (NSAsynchronousFetchResult *)[weakSelf.managedObjectContext executeRequest:asynchronousFetchRequest error:&asynchronousFetchRequestError];
41
        
42
        if (asynchronousFetchRequestError) {
43
            NSLog(@"Unable to execute asynchronous fetch result.");
44
            NSLog(@"%@, %@", asynchronousFetchRequestError, asynchronousFetchRequestError.localizedDescription);
45
        }
46
        
47
        // Add Observer

48
        [asynchronousFetchResult.progress addObserver:self forKeyPath:@"completedUnitCount" options:NSKeyValueObservingOptionNew context:ProgressContext];
49
        
50
        // Resign Current

51
        [progress resignCurrent];
52
    }];
53
}

Schritt 5: Fortschrittsbericht

Alles, was wir noch tun müssen, ist die Implementierung von observeValueForKeyPath: ofObject: change: context: method.  Wir prüfen, ob der Kontext ProgressContext entspricht, erstellen ein Statusobjekt, indem wir die Anzahl abgeschlossener Datensätze aus dem Änderungswörterbuch extrahieren, und aktualisieren die Fortschritts-HUD.  Beachten Sie, dass wir die Benutzeroberfläche im Hauptthread aktualisieren.

1
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
2
    if (context == ProgressContext) {
3
        dispatch_async(dispatch_get_main_queue(), ^{
4
            // Create Status

5
            NSString *status = [NSString stringWithFormat:@"Fetched %li Records", (long)[[change objectForKey:@"new"] integerValue]];
6
            
7
            // Show Progress HUD

8
            [SVProgressHUD setStatus:status];
9
        });
10
    }
11
}

5. Dummy-Daten

Wenn wir unsere Anwendung richtig testen wollen, brauchen wir mehr Daten. Ich empfehle zwar nicht, den folgenden Ansatz in einer Produktionsanwendung zu verwenden, aber es ist eine schnelle und einfache Möglichkeit, die Datenbank mit Daten zu füllen. 

Öffnen Sie TSPAppDelegate.m und aktualisieren Sie die Anwendung: didFinishLaunchingWithOptions: Methode wie unten gezeigt.  Die populateDatabase-Methode ist eine einfache Hilfsmethode, in der Dummy-Daten zur Datenbank hinzugefügt werden.

1
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
2
    // Populate Database

3
    [self populateDatabase];
4
    
5
    ...
6
    
7
    return YES;
8
}

Die Implementierung ist unkompliziert.  Da wir nur Dummy-Daten einmal einfügen möchten, überprüfen wir die Benutzerstandarddatenbank für den Schlüssel @ "didPopulateDatabase".  Wenn der Schlüssel nicht gesetzt ist, fügen wir Dummy-Daten ein.

1
- (void)populateDatabase {
2
    // Helpers

3
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
4
    if ([ud objectForKey:@"didPopulateDatabase"]) return;
5
    
6
    for (NSInteger i = 0; i < 1000000; i++) {
7
        // Create Entity

8
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"TSPItem" inManagedObjectContext:self.managedObjectContext];
9
        
10
        // Initialize Record

11
        NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
12
        
13
        // Populate Record

14
        [record setValue:[NSString stringWithFormat:@"Item %li", (long)i] forKey:@"name"];
15
        [record setValue:[NSDate date] forKey:@"createdAt"];
16
    }
17
    
18
    // Save Managed Object Context

19
    [self saveManagedObjectContext];
20
    
21
    // Update User Defaults

22
    [ud setBool:YES forKey:@"didPopulateDatabase"];
23
}

Die Anzahl der Datensätze ist wichtig.  Wenn Sie die Anwendung auf dem iOS-Simulator ausführen möchten, können Sie 100.000 oder 1.000.000 Datensätze einfügen.  Dies funktioniert auf einem physischen Gerät nicht so gut und dauert zu lange.

In der for-Schleife erstellen wir ein verwaltetes Objekt und füllen es mit Daten.  Beachten Sie, dass die Änderungen des verwalteten Objektkontexts nicht bei jeder Iteration der for-Schleife gespeichert werden. 

Schließlich aktualisieren wir die Benutzerstandarddatenbank, um sicherzustellen, dass die Datenbank beim nächsten Start der Anwendung nicht ausgefüllt wird. 

Groß.  Führen Sie die Anwendung im iOS Simulator aus, um das Ergebnis zu sehen.  Sie werden feststellen, dass es einige Momente dauert, bis die asynchrone Abrufanforderung beginnt, Datensätze abzurufen und die Fortschritts-HUD zu aktualisieren.

6. Brechen von Änderungen

Durch Ersetzen der abgerufenen Ergebnis-Controller-Klasse durch eine asynchrone Abrufanforderung haben wir einige Teile der Anwendung abgebrochen.  Zum Beispiel scheint das Antippen des Häkchens eines Aufgabenobjekts nicht mehr zu funktionieren.  Während die Datenbank aktualisiert wird, spiegelt die Benutzeroberfläche die Änderung nicht wider.  Die Lösung ist relativ einfach zu beheben und ich überlasse es Ihnen, eine Lösung zu implementieren.  Sie sollten jetzt genügend Kenntnisse haben, um das Problem zu verstehen und eine geeignete Lösung zu finden.

Fazit

Ich bin sicher, Sie stimmen zu, dass das asynchrone Holen überraschend einfach ist.  Das Heavy-Lifting wird von Core Data durchgeführt, so dass die Ergebnisse der asynchronen Abrufanforderung nicht manuell mit dem Kontext des verwalteten Objekts zusammengeführt werden müssen.  Ihre einzige Aufgabe besteht darin, die Benutzeroberfläche zu aktualisieren, wenn die asynchrone Abrufanforderung Ihnen ihre Ergebnisse übermittelt.  Zusammen mit Batch-Updates ist es eine großartige Ergänzung zum Core Data Framework. 

Dieser Artikel schließt auch diese Serie über Core Data ab.  Sie haben viel über das Core-Data-Framework gelernt und Sie kennen alle Grundlagen, um Core Data in einer realen Anwendung zu verwenden.  Core Data ist ein leistungsfähiges Framework und mit der Veröffentlichung von iOS 8 hat Apple uns gezeigt, dass es jedes Jahr besser wird.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.