rest/json/coredata example code - a tour

Post on 06-May-2015

5.141 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

DESCRIPTION

This is a talk given by Carl Brown at the 2/28/2013 CocoaCoders meeting in Austin (actually Round Rock) TX. It describes github/carlbrown/SeismicJSON - an MIT-licensed project Carl wrote to illustrate REST JSON CoreData and NSOperations based loosely on the functionality of Apple's old SeismicXML Sample Code.

TRANSCRIPT

github.com/carlbrown/SeismicJSON Carl BrownTwitter: @CarlBrwnEmail: CarlB@PDAgent.com

REST/JSON/CoreData Example Code

1Turn on Camera and ScreenFlow!!

Asynchronous iOS Programming Rules

•Threads are bad

•Use Queues instead

•Apple's Concurrency Programming Guide*:

• Page 10: "The Move Away from Threads"

• Page 74: "Migrating Away from Threads"

• Page 74: "Replacing Threads with Dispatch Queues"* http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html

2

Some things aren't thread-safe

•Any UIAnything in iOS

•CoreData

3

UI tasks Must be on the Main Thread

Often called the "UI" thread for that reason4

CoreData Contexts and Objects are tied to a thread

• Never share a CoreData object between threads

• Never share a CoreData object between contexts

• Pass objects only by ID and fetch them again

• Always notify other contexts when you've made changes

5

Don't cross the threads•Use the same serial queue to stay on the same thread

•Use dispatch_get_main_queue() or [NSOperationQueue mainQueue] to get to the Main Thread.

6

SeismicJSONThe only project we'll be working with today

Feel free to run it and play with it for a couple of minutes

Also on github so you can see how it was written.

7

First Exercise:Follow the Code

8

This is a vital skill to have. We don't expect people to write in English without reading a lot of English first, but programmers consistently spend more time writing code than reading it while learning a new language.

Please Open MasterViewController.mThis is pretty much a tableViewController like you've seen before.

Ignore the #if conditionals this run-through

9

Ignore the #if conditionals this run-through

MasterViewController• Cell labels filled in from Model Object

• custom XIB for TableViewCell

• dateFormatter in viewDidLoad

• segmentedController sets FRC sorting

• actionSheet for adding rows (you can figure out)

• some iPad stuff (not Rocket Science)

• #if conditional stuff (for later)

should be mostly familiar

10

DetailViewController•Really simple

•simpler than the tableViewCell

•Just a bunch of labels

•nothing to discuss here

11

ActivityIndicatingImageViewThis has a UIActivityIndicatorView, implements a new custom @protocol and refers to a NetworkManager.

12

So openActivityIndicatingImageView.m

13

Start Spinner upon waking

-(void) awakeFromNib { _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; [_activityIndicator setFrame:self.frame]; [_activityIndicator setHidesWhenStopped:YES]; [self addSubview:_activityIndicator]; [_activityIndicator startAnimating];}

14

Stop it when we get image

-(void) setImage:(UIImage *)image { [super setImage:image]; if (image) { [self.activityIndicator stopAnimating]; } else { [self.activityIndicator startAnimating]; }}

15

setImageFileName 1/3

-(void) setImageFileName:(NSString *)imageFileName {

_imageFileName = imageFileName;

if (_imageFileName==nil) { [self setImage:nil]; return; }

16

setImageFileName 2/3

//If the file already exists, don't bother to fetch it again NSString *fullFilePath = [[[NetworkManager sharedManager]

cachedImageDirectory] stringByAppendingPathComponent:_imageFileName];

if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath]) {

[self imageDidBecomeAvailableAtPath:fullFilePath]; return; }

17

setImageFileName 3/3

[[NetworkManager sharedManager] fetchImagewithFilename:

imageFileName andNotifyTarget:self]; }

18

OK, now we're getting somewhere

-(void) fetchImagewithFilename:(NSString *) filename andNotifyTarget:(NSObject <ImageFetchDelegate> *) target { //Stuff we've seen before ImageFetchOperation *imageFetchOperation =

[[ImageFetchOperation alloc] init];

[imageFetchOperation setUrlToFetch:[self imageURLForImageFileName:filename]];

[imageFetchOperation setNotificationTarget:target];

[self.fetchQueue addOperation:imageFetchOperation];

}

19

ImageFetchOperation.m

20

connectionDidFinishLoading (abridged)

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { if (self.response.statusCode==200) { NSError *error=nil; NSString *filename = [self.urlToFetch lastPathComponent]; NSString *fullFilePath = [

[[NetworkManager sharedManager] cachedImageDirectory] stringByAppendingPathComponent:filename];

NSLog(@"About to write file: %@",fullFilePath); if (![self.fetchedData writeToFile:fullFilePath

options:NSDataWritingAtomic error:&error]) { NSLog(@"error occurred writing file: %@",

[error localizedDescription]); } if (self.notificationTarget) { [self.notificationTarget

imageDidBecomeAvailableAtPath:fullFilePath]; } }}

21

Only save if no error

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { if (self.response.statusCode==200) { NSError *error=nil; NSString *filename = [self.urlToFetch lastPathComponent]; NSString *fullFilePath = [

[[NetworkManager sharedManager] cachedImageDirectory] stringByAppendingPathComponent:filename];

NSLog(@"About to write file: %@",fullFilePath); if (![self.fetchedData writeToFile:fullFilePath

options:NSDataWritingAtomic error:&error]) { NSLog(@"error occurred writing file: %@",

[error localizedDescription]); } if (self.notificationTarget) { [self.notificationTarget

imageDidBecomeAvailableAtPath:fullFilePath]; } }}

22

Get filename/path

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { if (self.response.statusCode==200) { NSError *error=nil; NSString *filename = [self.urlToFetch lastPathComponent]; NSString *fullFilePath = [

[[NetworkManager sharedManager] cachedImageDirectory] stringByAppendingPathComponent:filename];

NSLog(@"About to write file: %@",fullFilePath); if (![self.fetchedData writeToFile:fullFilePath

options:NSDataWritingAtomic error:&error]) { NSLog(@"error occurred writing file: %@",

[error localizedDescription]); } if (self.notificationTarget) { [self.notificationTarget

imageDidBecomeAvailableAtPath:fullFilePath]; } }}

23

Save File

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { if (self.response.statusCode==200) { NSError *error=nil; NSString *filename = [self.urlToFetch lastPathComponent]; NSString *fullFilePath = [

[[NetworkManager sharedManager] cachedImageDirectory] stringByAppendingPathComponent:filename];

NSLog(@"About to write file: %@",fullFilePath); if (![self.fetchedData writeToFile:fullFilePath

options:NSDataWritingAtomic error:&error]) { NSLog(@"error occurred writing file: %@",

[error localizedDescription]); } if (self.notificationTarget) { [self.notificationTarget

imageDidBecomeAvailableAtPath:fullFilePath]; } }}

24

Let the View Know

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { if (self.response.statusCode==200) { NSError *error=nil; NSString *filename = [self.urlToFetch lastPathComponent]; NSString *fullFilePath = [

[[NetworkManager sharedManager] cachedImageDirectory] stringByAppendingPathComponent:filename];

NSLog(@"About to write file: %@",fullFilePath); if (![self.fetchedData writeToFile:fullFilePath

options:NSDataWritingAtomic error:&error]) { NSLog(@"error occurred writing file: %@",

[error localizedDescription]); } if (self.notificationTarget) { [self.notificationTarget

imageDidBecomeAvailableAtPath:fullFilePath]; } }}

25

[control]-[⌘]-Jback to ActivityIndicatingImageView

26

imageDidBecomeAvailableAtPath1/2

-(void) imageDidBecomeAvailableAtPath:(NSString *) path { if (![[path lastPathComponent]

isEqualToString:self.imageFileName]) { NSLog(@"Warning: notified of incorrect file:

%@, should have been %@",[path lastPathComponent],self.imageFileName);

//try again [self setImageFileName:self.imageFileName]; return; }

Only load the file we're expecting (race condition checking)

27

imageDidBecomeAvailableAtPath2/2

//load image off the main queue UIImage *imageToLoad=[UIImage imageWithContentsOfFile:path]; dispatch_async(dispatch_get_main_queue(), ^{ [self setImage:imageToLoad]; [self setNeedsDisplay]; });}

Set our image with the file now on disk

28

Summary of ActivityIndicatingImageView

• Start the view with a spinner telling the user we are working on something

• See if the file is already on disk, and use it if so.

• If not, we ask the Network Manager to get the file for us

• The Network Manager creates an operation to get our file (presumably from the network) and write it to disk

• The Network Manager tells us the file is ready

• We load the file into our image property

• Now that we have an image, the spinner hides

29

Networking Strategy•Always* load the UI from local storage

•Core Data or local file or something

•Always* put network data in local storage

•Then tell the UI to refresh itself

•Put up a placeholder if no data

Carl's Recommended

*Except with live web pages or HTTP streaming30

Some people argue with me about this, but it's served me well for years

Why do it that way?•Separates network code from UI code

•Easier to test

•Much faster response if previous data

•Much better user experience offline

31

Why wouldn't you?•Pointless if the network is infinitely fast and infinitely reliable*

•More effort than "Unbreakable Glass" loading screens

*c.f. http://en.wikipedia.org/wiki/Fallacies_of_Distributed_Computing32

NSOperations and GCD

33

NSOperation•Been around since the first iPhone OS SDK

•Way to encapsulate the pieces of a task in one place

•Can be queried, suspended or canceled

•Simple selector call or block variants

•NSOperations are placed in NSOperationQueues

34

NSOperationQueue• Long-lived (presumably) queue that can contain

numerous operations

• Can be serial or concurrent

• Can be suspended or canceled

• Nice (but verbose) Objective-C syntax

• Will stay on the same thread, if serial

• [NSOperationQueue mainQueue] is always on the Main Thread

35

Dispatch Queues•C-style (concise) syntax

•quicker to use in-place

•much less typing than declaring an NSOperation and adding to Queue

•Harder to manage or cancel

36

Which to use?• No hard-and-fast rules, but...

• I tend to use NSOperations for:

• things I'm going to do several times

• things that have non-trivial complexity

• I tend to use dispatch_async() for things:

• with less than 10 or so lines of code

• done only once in the App

• that won't need to change when spec changes

37

Waiting in Cocoa•Don't Sleep

•Don't use locks

•Yield to the RunLoop

•See the FetchOperation for example

•Sleeping or Locking Freezes the Thread

38

Be Nice to Threads•POSIX Threads are a finite resource

•The system will spin up more if tasks are waiting

•But when no more can start, things will hang

•See: WWDC2012 Session Session 712 - Asynchronous Design Patterns with Blocks, GCD, and XPC

39

Back to our Application

40

Please OpenNotificationOrParentContext.h

41

Project Variations

//Make this a 1 to show notifications, and a 0 to show parent contexts#define kUSE_NSNOTIFICATIONS_FOR_CONTEXT_MERGE 0//if using notifications, set this to 1 to have them in the App Delegate#define kNSNOTIFICATIONS_HANDLED_IN_APPDELEGATE 0

Note: I'm not usually a fan of this kind of conditionalcompilation, but for this case, I thought it would

let you play with project in the debugger in acleaner way than with traditional if's.

42

OK, to theAppDelegate.m

43

Open Source Control View

44

Click the TimeMachine45

Pick the Initial Commit Scroll in the center of the screen

46

Got rid of some Xcode 4.4-isms

47

Removed Observer48

applicationWillResignActive:!(UIApplication *) application

•Happens when user gets Texts, notifications, Alerts, phone calls or hits the home button

•Here I'm removing the notification observer so we won't try to get notifications while not Active

49

Added Observer50

Kicked off Network Fetch

51

applicationDidBecomeActive:(UIApplication *)application

•Happens when App becomes full-focus

•After launch

•Or after returning from dealing with alert

•Or after dealing with "most recently used apps" along bottom of screen

•Here I'm adding a notification observer

52

This Runs "changesSaved:"

[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(changesSaved:) name:NSManagedObjectContextDidSaveNotification

object:nil];

53

Handler Code

- (void)changesSaved:(NSNotification *)notification { if (![NSThread isMainThread]) { dispatch_async(dispatch_get_main_queue(), ^{ [self changesSaved:notification]; }); return; } if ([notification object] != self.managedObjectContext) { [self.managedObjectContext

mergeChangesFromContextDidSaveNotification:notification]; }}

54

If not on Main, go there

- (void)changesSaved:(NSNotification *)notification { if (![NSThread isMainThread]) { dispatch_async(dispatch_get_main_queue(), ^{ [self changesSaved:notification]; }); return; } if ([notification object] != self.managedObjectContext) { [self.managedObjectContext

mergeChangesFromContextDidSaveNotification:notification]; }}

55

Merge changes

- (void)changesSaved:(NSNotification *)notification { if (![NSThread isMainThread]) { dispatch_async(dispatch_get_main_queue(), ^{ [self changesSaved:notification]; }); return; } if ([notification object] != self.managedObjectContext) { [self.managedObjectContext

mergeChangesFromContextDidSaveNotification:notification]; }}

56

Queue Concurrency Type

57

Reset DB each run58

Back to normal view now

59

NetworkManager.m

60

Singleton Pattern

+ (NetworkManager *)sharedManager { static dispatch_once_t pred; dispatch_once(&pred, ^{ sharedManager = [[self alloc] init]; //Initialization Stuff }); return sharedManager;}

61

Kicked off from AppDelegate

-(void) startMainPageFetch { [self setHostReach:[Reachability

reachabilityWithHostName:[self.baseURL host]]]; [self.hostReach startNotifier];

[self queuePageFetchForRelativePath:@"/earthquakes/feed/geojson/significant/month"];

}

62

Inform users of network status or be Rejected

-(void) startMainPageFetch { [self setHostReach:[Reachability

reachabilityWithHostName:[self.baseURL host]]]; [self.hostReach startNotifier];

[self queuePageFetchForRelativePath:@"/earthquakes/feed/geojson/significant/month"];

}

63

Just do it. If you want to understand it, read Apple's writeup

64

Start fetch of first batch

-(void) startMainPageFetch { [self setHostReach:[Reachability

reachabilityWithHostName:[self.baseURL host]]]; [self.hostReach startNotifier];

[self queuePageFetchForRelativePath:@"/earthquakes/feed/geojson/significant/month"];

}

65

Make NSOp & Queue it

-(void) queuePageFetchForRelativePath:(NSString *) relativePath { EarthquakeFetchOperation *earthquakeFetchOperation =

[[EarthquakeFetchOperation alloc] init]; [earthquakeFetchOperation setUrlToFetch:

[self urlForRelativePath:relativePath]]; [earthquakeFetchOperation setMainContext:self.mainContext]; [earthquakeFetchOperation setDelegate:self]; [self.fetchQueue addOperation:earthquakeFetchOperation];}

66

EarthquakeFetchOperation.h

#import <Foundation/Foundation.h>#import "BaseFetchOperation.h"

@interface EarthquakeFetchOperation : BaseFetchOperation@property (nonatomic, weak) NSManagedObjectContext *mainContext;

@end

67

BaseFetchOperation.h

@interface BaseFetchOperation : NSOperation <NSURLConnectionDataDelegate>

@property (nonatomic, strong) NSURL *urlToFetch;@property (nonatomic, strong) NSMutableData *fetchedData;@property (nonatomic, assign, getter=isDone) BOOL done;@property (nonatomic, assign) NSURLConnection *connection;@property (nonatomic, retain) NSHTTPURLResponse *response;

@property (nonatomic, weak) NSObject<FetchNotifierDelegate> *delegate;

-(void) finish;

@end

@protocol FetchNotifierDelegate <NSObject>-(void) fetchDidFailWithError:(NSError *) error;-(void) incrementActiveFetches;-(void) decrementActiveFetches;@end

68

Methods needed for URL fetching

69

BaseFetchOperation.m

70

Entry Point

- (void)main { if ([self isCancelled]) { return; } if (!_urlForJSONData) { NSLog(@"Cannot start without a URL"); return; } [self setFetchedData:[NSMutableData data]]; //Initialize NSURLRequest *request =

[NSURLRequest requestWithURL:[self urlToFetch]]; if (self.delegate) { [self.delegate incrementActiveFetches]; } [self setConnection:[NSURLConnection

connectionWithRequest:request delegate:self]]; CFRunLoopRun();}

71

Sanity Check

- (void)main { if ([self isCancelled]) { return; } if (!_urlForJSONData) { NSLog(@"Cannot start without a URL"); return; } [self setFetchedData:[NSMutableData data]]; //Initialize NSURLRequest *request =

[NSURLRequest requestWithURL:[self urlToFetch]]; if (self.delegate) { [self.delegate incrementActiveFetches]; } [self setConnection:[NSURLConnection

connectionWithRequest:request delegate:self]]; CFRunLoopRun();}

72

Make request

- (void)main { if ([self isCancelled]) { return; } if (!_urlForJSONData) { NSLog(@"Cannot start without a URL"); return; } [self setFetchedData:[NSMutableData data]]; //Initialize NSURLRequest *request =

[NSURLRequest requestWithURL:[self urlToFetch]]; if (self.delegate) { [self.delegate incrementActiveFetches]; } [self setConnection:[NSURLConnection

connectionWithRequest:request delegate:self]]; CFRunLoopRun();}

73

Inform user we're active

- (void)main { if ([self isCancelled]) { return; } if (!_urlForJSONData) { NSLog(@"Cannot start without a URL"); return; } [self setFetchedData:[NSMutableData data]]; //Initialize NSURLRequest *request =

[NSURLRequest requestWithURL:[self urlToFetch]]; if (self.delegate) { [self.delegate incrementActiveFetches]; } [self setConnection:[NSURLConnection

connectionWithRequest:request delegate:self]]; CFRunLoopRun();}

74

Start Connection

- (void)main { if ([self isCancelled]) { return; } if (!_urlForJSONData) { NSLog(@"Cannot start without a URL"); return; } [self setFetchedData:[NSMutableData data]]; //Initialize NSURLRequest *request =

[NSURLRequest requestWithURL:[self urlToFetch]]; if (self.delegate) { [self.delegate incrementActiveFetches]; } [self setConnection:[NSURLConnection

connectionWithRequest:request delegate:self]]; CFRunLoopRun();}

75

Give Up Control of thread

- (void)main { if ([self isCancelled]) { return; } if (!_urlForJSONData) { NSLog(@"Cannot start without a URL"); return; } [self setFetchedData:[NSMutableData data]]; //Initialize NSURLRequest *request =

[NSURLRequest requestWithURL:[self urlToFetch]]; if (self.delegate) { [self.delegate incrementActiveFetches]; } [self setConnection:[NSURLConnection

connectionWithRequest:request delegate:self]]; CFRunLoopRun();}

76

Finish

-(void) finish { [self setDone:YES]; if (self.delegate) { [self.delegate decrementActiveFetches]; } CFRunLoopStop(CFRunLoopGetCurrent());}

77

Inform user we're done

-(void) finish { [self setDone:YES]; if (self.delegate) { [self.delegate decrementActiveFetches]; } CFRunLoopStop(CFRunLoopGetCurrent());}

78

Stop the runloop & get off

-(void) finish { [self setDone:YES]; if (self.delegate) { [self.delegate decrementActiveFetches]; } CFRunLoopStop(CFRunLoopGetCurrent());}

79

Other methods there

• didReceiveResponse

• remember response

• truncate data

• (can get more than one response)

• didReceiveData

• append data

• didFailWithError

• report error to our delegate80

EarthquakeFetchOperation

81

connectionDidFinishLoading 1/n

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { if ([self isCancelled]) { [self finish]; return; } if (self.response.statusCode==200) {

82

Sanity Check/Housekeeping

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { if ([self isCancelled]) { [self finish]; return; } if (self.response.statusCode==200) {

83

Don't parse bad response

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { if ([self isCancelled]) { [self finish]; return; } if (self.response.statusCode==200) {

84

connectionDidFinishLoading 2/n

id objectFromJSON = [NSJSONSerialization JSONObjectWithData:self.fetchedData options:0 error:&error];

if (objectFromJSON) {#if kUSE_NSNOTIFICATIONS_FOR_CONTEXT_MERGE NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init]; [context setPersistentStoreCoordinator:

self.mainContext.persistentStoreCoordinator];#else

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

[context setParentContext:[self mainContext]];#endif

85

Parse JSON

id objectFromJSON = [NSJSONSerialization JSONObjectWithData:self.fetchedData options:0 error:&error];

if (objectFromJSON) {#if kUSE_NSNOTIFICATIONS_FOR_CONTEXT_MERGE NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init]; [context setPersistentStoreCoordinator:

self.mainContext.persistentStoreCoordinator];#else

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

[context setParentContext:[self mainContext]];#endif

86

If the JSON was good

id objectFromJSON = [NSJSONSerialization JSONObjectWithData:self.fetchedData options:0 error:&error];

if (objectFromJSON) {#if kUSE_NSNOTIFICATIONS_FOR_CONTEXT_MERGE NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init]; [context setPersistentStoreCoordinator:

self.mainContext.persistentStoreCoordinator];#else

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

[context setParentContext:[self mainContext]];#endif

87

Make new ManagedObjectContext

id objectFromJSON = [NSJSONSerialization JSONObjectWithData:self.fetchedData options:0 error:&error];

if (objectFromJSON) {#if kUSE_NSNOTIFICATIONS_FOR_CONTEXT_MERGE NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init]; [context setPersistentStoreCoordinator:

self.mainContext.persistentStoreCoordinator];#else

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

[context setParentContext:[self mainContext]];#endif

88

connectionDidFinishLoading 3/n

NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;

if (jsonDict) { NSArray *events = [jsonDict objectForKey:@"features"]; if (events) { for (NSDictionary *eventDict in events) {

89

If we got a dictionary

NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;

if (jsonDict) { NSArray *events = [jsonDict objectForKey:@"features"]; if (events) { for (NSDictionary *eventDict in events) {

90

Get Array of Earthquakes

NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;

if (jsonDict) { NSArray *events = [jsonDict objectForKey:@"features"]; if (events) { for (NSDictionary *eventDict in events) {

91

If Array/JSON is valid

NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;

if (jsonDict) { NSArray *events = [jsonDict objectForKey:@"features"]; if (events) { for (NSDictionary *eventDict in events) {

92

Iterate over it

NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;

if (jsonDict) { NSArray *events = [jsonDict objectForKey:@"features"]; if (events) { for (NSDictionary *eventDict in events) {

93

connectionDidFinishLoading 4/n

NSString *eventLocation = [eventDict valueForKeyPath:@"properties.place"];

NSDate *eventDate = [NSDate dateWithTimeIntervalSince1970:[[eventDict valueForKeyPath:@"properties.time"] doubleValue]];

NSNumber *eventLong = [NSNumber numberWithDouble:[[[eventDict valueForKeyPath:@"geometry.coordinates"] objectAtIndex:0]

doubleValue]];NSNumber *eventLat =[NSNumber numberWithDouble:[[[eventDict

valueForKeyPath:@"geometry.coordinates"] objectAtIndex:1] doubleValue]];

NSNumber *eventMagnitude = [NSNumber numberWithFloat:[[eventDict valueForKeyPath:@"properties.mag"] floatValue]];

NSString *eventWebPath = [@"http://earthquake.usgs.gov" stringByAppendingPathComponent:[eventDict valueForKeyPath:@"properties.url"]];

94

Extract values from eventDict

NSString *eventLocation = [eventDict valueForKeyPath:@"properties.place"];

NSDate *eventDate = [NSDate dateWithTimeIntervalSince1970:[[eventDict valueForKeyPath:@"properties.time"] doubleValue]];

NSNumber *eventLong = [NSNumber numberWithDouble:[[[eventDict valueForKeyPath:@"geometry.coordinates"] objectAtIndex:0]

doubleValue]];NSNumber *eventLat =[NSNumber numberWithDouble:[[[eventDict

valueForKeyPath:@"geometry.coordinates"] objectAtIndex:1] doubleValue]];

NSNumber *eventMagnitude = [NSNumber numberWithFloat:[[eventDict valueForKeyPath:@"properties.mag"] floatValue]];

NSString *eventWebPath = [@"http://earthquake.usgs.gov" stringByAppendingPathComponent:[eventDict valueForKeyPath:@"properties.url"]];

95

Using keyPaths

NSString *eventLocation = [eventDict valueForKeyPath:@"properties.place"];

NSDate *eventDate = [NSDate dateWithTimeIntervalSince1970:[[eventDict valueForKeyPath:@"properties.time"] doubleValue]];

NSNumber *eventLong = [NSNumber numberWithDouble:[[[eventDict valueForKeyPath:@"geometry.coordinates"] objectAtIndex:0]

doubleValue]];NSNumber *eventLat =[NSNumber numberWithDouble:[[[eventDict

valueForKeyPath:@"geometry.coordinates"] objectAtIndex:1] doubleValue]];

NSNumber *eventMagnitude = [NSNumber numberWithFloat:[[eventDict valueForKeyPath:@"properties.mag"] floatValue]];

NSString *eventWebPath = [@"http://earthquake.usgs.gov" stringByAppendingPathComponent:[eventDict valueForKeyPath:@"properties.url"]];

96

and/or Array elements

NSString *eventLocation = [eventDict valueForKeyPath:@"properties.place"];

NSDate *eventDate = [NSDate dateWithTimeIntervalSince1970:[[eventDict valueForKeyPath:@"properties.time"] doubleValue]];

NSNumber *eventLong = [NSNumber numberWithDouble:[[[eventDict valueForKeyPath:@"geometry.coordinates"] objectAtIndex:0]

doubleValue]];NSNumber *eventLat =[NSNumber numberWithDouble:[[[eventDict

valueForKeyPath:@"geometry.coordinates"] objectAtIndex:1] doubleValue]];

NSNumber *eventMagnitude = [NSNumber numberWithFloat:[[eventDict valueForKeyPath:@"properties.mag"] floatValue]];

NSString *eventWebPath = [@"http://earthquake.usgs.gov" stringByAppendingPathComponent:[eventDict valueForKeyPath:@"properties.url"]];

97

connectionDidFinishLoading 5/n

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:

NSStringFromClass([Earthquake class])];[fetchRequest setFetchLimit:1];NSPredicate *eventInfo =

[NSPredicate predicateWithFormat:@"location = %@ AND date = %@",

eventLocation, eventDate];[fetchRequest setPredicate:eventInfo];NSError *fetchError=nil;NSArray *existingEventsMatchingThisOne =

[context executeFetchRequest:fetchRequest error:&fetchError];

98

Make a fetch request

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:

NSStringFromClass([Earthquake class])];[fetchRequest setFetchLimit:1];NSPredicate *eventInfo =

[NSPredicate predicateWithFormat:@"location = %@ AND date = %@",

eventLocation, eventDate];[fetchRequest setPredicate:eventInfo];NSError *fetchError=nil;NSArray *existingEventsMatchingThisOne =

[context executeFetchRequest:fetchRequest error:&fetchError];

99

matching our event

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:

NSStringFromClass([Earthquake class])];[fetchRequest setFetchLimit:1];NSPredicate *eventInfo =

[NSPredicate predicateWithFormat:@"location = %@ AND date = %@",

eventLocation, eventDate];[fetchRequest setPredicate:eventInfo];NSError *fetchError=nil;NSArray *existingEventsMatchingThisOne =

[context executeFetchRequest:fetchRequest error:&fetchError];

100

And run it

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:

NSStringFromClass([Earthquake class])];[fetchRequest setFetchLimit:1];NSPredicate *eventInfo =

[NSPredicate predicateWithFormat:@"location = %@ AND date = %@",

eventLocation, eventDate];[fetchRequest setPredicate:eventInfo];NSError *fetchError=nil;NSArray *existingEventsMatchingThisOne =

[context executeFetchRequest:fetchRequest error:&fetchError];

101

connectionDidFinishLoading 6/n

if ([existingEventsMatchingThisOne count]==0) { //Didn't find one already, make a new one NSManagedObject *newManagedObject =

[NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Earthquake class])

inManagedObjectContext:context]; [newManagedObject setValue:eventLocation forKey:@"location"]; [newManagedObject setValue:eventDate forKey:@"date"]; [newManagedObject setValue:eventLat forKey:@"latitude"]; [newManagedObject setValue:eventLong forKey:@"longitude"]; [newManagedObject setValue:eventMagnitude forKey:@"magnitude"]; [newManagedObject setValue:eventWebPath forKey:@"webLinkToUSGS"];}

102

If there isn't already one

if ([existingEventsMatchingThisOne count]==0) { //Didn't find one already, make a new one NSManagedObject *newManagedObject =

[NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Earthquake class])

inManagedObjectContext:context]; [newManagedObject setValue:eventLocation forKey:@"location"]; [newManagedObject setValue:eventDate forKey:@"date"]; [newManagedObject setValue:eventLat forKey:@"latitude"]; [newManagedObject setValue:eventLong forKey:@"longitude"]; [newManagedObject setValue:eventMagnitude forKey:@"magnitude"]; [newManagedObject setValue:eventWebPath forKey:@"webLinkToUSGS"];}

103

Make a new Object

if ([existingEventsMatchingThisOne count]==0) { //Didn't find one already, make a new one NSManagedObject *newManagedObject =

[NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Earthquake class])

inManagedObjectContext:context]; [newManagedObject setValue:eventLocation forKey:@"location"]; [newManagedObject setValue:eventDate forKey:@"date"]; [newManagedObject setValue:eventLat forKey:@"latitude"]; [newManagedObject setValue:eventLong forKey:@"longitude"]; [newManagedObject setValue:eventMagnitude forKey:@"magnitude"]; [newManagedObject setValue:eventWebPath forKey:@"webLinkToUSGS"];}

104

Set all its attributes

if ([existingEventsMatchingThisOne count]==0) { //Didn't find one already, make a new one NSManagedObject *newManagedObject =

[NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Earthquake class])

inManagedObjectContext:context]; [newManagedObject setValue:eventLocation forKey:@"location"]; [newManagedObject setValue:eventDate forKey:@"date"]; [newManagedObject setValue:eventLat forKey:@"latitude"]; [newManagedObject setValue:eventLong forKey:@"longitude"]; [newManagedObject setValue:eventMagnitude forKey:@"magnitude"]; [newManagedObject setValue:eventWebPath forKey:@"webLinkToUSGS"];}

105

connectionDidFinishLoading 7/n

// Save the context.error = nil;if (![context save:&error]) { // stuff NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort();}

106

Save and check for errors

// Save the context.error = nil;if (![context save:&error]) { // stuff NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort();}

107

connectionDidFinishLoading 8/n

#if kUSE_PARENT_CONTEXTS_FOR_CONTEXT_MERGE dispatch_sync(dispatch_get_main_queue(), ^{ NSError *error = nil; if (![self.mainContext save:&error]) { // Stuff NSLog(@"Unresolved error %@, %@", error,

[error userInfo]); abort(); } });#endif

108

If we're merging via Parent

#if kUSE_PARENT_CONTEXTS_FOR_CONTEXT_MERGE dispatch_sync(dispatch_get_main_queue(), ^{ NSError *error = nil; if (![self.mainContext save:&error]) { // Stuff NSLog(@"Unresolved error %@, %@", error,

[error userInfo]); abort(); } });#endif

109

On the Main Thread

#if kUSE_PARENT_CONTEXTS_FOR_CONTEXT_MERGE dispatch_sync(dispatch_get_main_queue(), ^{ NSError *error = nil; if (![self.mainContext save:&error]) { // Stuff NSLog(@"Unresolved error %@, %@", error,

[error userInfo]); abort(); } });#endif

110

Tell the main context to save

#if kUSE_PARENT_CONTEXTS_FOR_CONTEXT_MERGE dispatch_sync(dispatch_get_main_queue(), ^{ NSError *error = nil; if (![self.mainContext save:&error]) { // Stuff NSLog(@"Unresolved error %@, %@", error,

[error userInfo]); abort(); } });#endif

111

Review

• Asynchronous programming

• NSOperations

• Grand Central Dispatch (GCD)

• More Blocks

• Notifications

• App Lifecycle

• Network I/O (HTTP/REST)

• JSON parsing112

Questions?Now, Or Later:CarlB@PDAgent.com@CarlBrwn (Twitter/App.net)

Today's App was: https://github.com/carlbrown/SeismicJSON

113

top related