rest/json/coredata example code - a tour

113
github.com/carlbrown/ SeismicJSON Carl Brown Twitter: @CarlBrwn Email: [email protected] REST/JSON/CoreData Example Code 1 Turn on Camera and ScreenFlow!!

Upload: carl-brown

Post on 06-May-2015

5.130 views

Category:

Technology


2 download

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

Page 1: REST/JSON/CoreData Example Code - A Tour

github.com/carlbrown/SeismicJSON Carl BrownTwitter: @CarlBrwnEmail: [email protected]

REST/JSON/CoreData Example Code

1Turn on Camera and ScreenFlow!!

Page 2: REST/JSON/CoreData Example Code - A Tour

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

Page 3: REST/JSON/CoreData Example Code - A Tour

Some things aren't thread-safe

•Any UIAnything in iOS

•CoreData

3

Page 4: REST/JSON/CoreData Example Code - A Tour

UI tasks Must be on the Main Thread

Often called the "UI" thread for that reason4

Page 5: REST/JSON/CoreData Example Code - A Tour

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

Page 6: REST/JSON/CoreData Example Code - A Tour

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

Page 7: REST/JSON/CoreData Example Code - A Tour

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

Page 8: REST/JSON/CoreData Example Code - A Tour

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.

Page 9: REST/JSON/CoreData Example Code - A Tour

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

Page 10: REST/JSON/CoreData Example Code - A Tour

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

Page 11: REST/JSON/CoreData Example Code - A Tour

DetailViewController•Really simple

•simpler than the tableViewCell

•Just a bunch of labels

•nothing to discuss here

11

Page 12: REST/JSON/CoreData Example Code - A Tour

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

12

Page 13: REST/JSON/CoreData Example Code - A Tour

So openActivityIndicatingImageView.m

13

Page 14: REST/JSON/CoreData Example Code - A Tour

Start Spinner upon waking

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

14

Page 15: REST/JSON/CoreData Example Code - A Tour

Stop it when we get image

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

15

Page 16: REST/JSON/CoreData Example Code - A Tour

setImageFileName 1/3

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

_imageFileName = imageFileName;

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

16

Page 17: REST/JSON/CoreData Example Code - A Tour

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

Page 18: REST/JSON/CoreData Example Code - A Tour

setImageFileName 3/3

[[NetworkManager sharedManager] fetchImagewithFilename:

imageFileName andNotifyTarget:self]; }

18

Page 19: REST/JSON/CoreData Example Code - A Tour

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

Page 20: REST/JSON/CoreData Example Code - A Tour

ImageFetchOperation.m

20

Page 21: REST/JSON/CoreData Example Code - A Tour

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

Page 22: REST/JSON/CoreData Example Code - A Tour

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

Page 23: REST/JSON/CoreData Example Code - A Tour

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

Page 24: REST/JSON/CoreData Example Code - A Tour

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

Page 25: REST/JSON/CoreData Example Code - A Tour

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

Page 26: REST/JSON/CoreData Example Code - A Tour

[control]-[⌘]-Jback to ActivityIndicatingImageView

26

Page 27: REST/JSON/CoreData Example Code - A Tour

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

Page 28: REST/JSON/CoreData Example Code - A Tour

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

Page 29: REST/JSON/CoreData Example Code - A Tour

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

Page 30: REST/JSON/CoreData Example Code - A Tour

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

Page 31: REST/JSON/CoreData Example Code - A Tour

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

Page 32: REST/JSON/CoreData Example Code - A Tour

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

Page 33: REST/JSON/CoreData Example Code - A Tour

NSOperations and GCD

33

Page 34: REST/JSON/CoreData Example Code - A Tour

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

Page 35: REST/JSON/CoreData Example Code - A Tour

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

Page 36: REST/JSON/CoreData Example Code - A Tour

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

Page 37: REST/JSON/CoreData Example Code - A Tour

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

Page 38: REST/JSON/CoreData Example Code - A Tour

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

Page 39: REST/JSON/CoreData Example Code - A Tour

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

Page 40: REST/JSON/CoreData Example Code - A Tour

Back to our Application

40

Page 41: REST/JSON/CoreData Example Code - A Tour

Please OpenNotificationOrParentContext.h

41

Page 42: REST/JSON/CoreData Example Code - A Tour

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

Page 43: REST/JSON/CoreData Example Code - A Tour

OK, to theAppDelegate.m

43

Page 44: REST/JSON/CoreData Example Code - A Tour

Open Source Control View

44

Page 45: REST/JSON/CoreData Example Code - A Tour

Click the TimeMachine45

Page 46: REST/JSON/CoreData Example Code - A Tour

Pick the Initial Commit Scroll in the center of the screen

46

Page 47: REST/JSON/CoreData Example Code - A Tour

Got rid of some Xcode 4.4-isms

47

Page 48: REST/JSON/CoreData Example Code - A Tour

Removed Observer48

Page 49: REST/JSON/CoreData Example Code - A Tour

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

Page 50: REST/JSON/CoreData Example Code - A Tour

Added Observer50

Page 51: REST/JSON/CoreData Example Code - A Tour

Kicked off Network Fetch

51

Page 52: REST/JSON/CoreData Example Code - A Tour

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

Page 53: REST/JSON/CoreData Example Code - A Tour

This Runs "changesSaved:"

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

object:nil];

53

Page 54: REST/JSON/CoreData Example Code - A Tour

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

Page 55: REST/JSON/CoreData Example Code - A Tour

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

Page 56: REST/JSON/CoreData Example Code - A Tour

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

Page 57: REST/JSON/CoreData Example Code - A Tour

Queue Concurrency Type

57

Page 58: REST/JSON/CoreData Example Code - A Tour

Reset DB each run58

Page 59: REST/JSON/CoreData Example Code - A Tour

Back to normal view now

59

Page 60: REST/JSON/CoreData Example Code - A Tour

NetworkManager.m

60

Page 61: REST/JSON/CoreData Example Code - A Tour

Singleton Pattern

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

61

Page 62: REST/JSON/CoreData Example Code - A Tour

Kicked off from AppDelegate

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

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

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

}

62

Page 63: REST/JSON/CoreData Example Code - A Tour

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

Page 64: REST/JSON/CoreData Example Code - A Tour

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

64

Page 65: REST/JSON/CoreData Example Code - A Tour

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

Page 66: REST/JSON/CoreData Example Code - A Tour

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

Page 67: REST/JSON/CoreData Example Code - A Tour

EarthquakeFetchOperation.h

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

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

@end

67

Page 68: REST/JSON/CoreData Example Code - A Tour

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

Page 69: REST/JSON/CoreData Example Code - A Tour

Methods needed for URL fetching

69

Page 70: REST/JSON/CoreData Example Code - A Tour

BaseFetchOperation.m

70

Page 71: REST/JSON/CoreData Example Code - A Tour

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

Page 72: REST/JSON/CoreData Example Code - A Tour

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

Page 73: REST/JSON/CoreData Example Code - A Tour

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

Page 74: REST/JSON/CoreData Example Code - A Tour

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

Page 75: REST/JSON/CoreData Example Code - A Tour

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

Page 76: REST/JSON/CoreData Example Code - A Tour

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

Page 77: REST/JSON/CoreData Example Code - A Tour

Finish

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

77

Page 78: REST/JSON/CoreData Example Code - A Tour

Inform user we're done

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

78

Page 79: REST/JSON/CoreData Example Code - A Tour

Stop the runloop & get off

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

79

Page 80: REST/JSON/CoreData Example Code - A Tour

Other methods there

• didReceiveResponse

• remember response

• truncate data

• (can get more than one response)

• didReceiveData

• append data

• didFailWithError

• report error to our delegate80

Page 81: REST/JSON/CoreData Example Code - A Tour

EarthquakeFetchOperation

81

Page 82: REST/JSON/CoreData Example Code - A Tour

connectionDidFinishLoading 1/n

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

82

Page 83: REST/JSON/CoreData Example Code - A Tour

Sanity Check/Housekeeping

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

83

Page 84: REST/JSON/CoreData Example Code - A Tour

Don't parse bad response

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

84

Page 85: REST/JSON/CoreData Example Code - A Tour

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

Page 86: REST/JSON/CoreData Example Code - A Tour

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

Page 87: REST/JSON/CoreData Example Code - A Tour

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

Page 88: REST/JSON/CoreData Example Code - A Tour

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

Page 89: REST/JSON/CoreData Example Code - A Tour

connectionDidFinishLoading 3/n

NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;

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

89

Page 90: REST/JSON/CoreData Example Code - A Tour

If we got a dictionary

NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;

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

90

Page 91: REST/JSON/CoreData Example Code - A Tour

Get Array of Earthquakes

NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;

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

91

Page 92: REST/JSON/CoreData Example Code - A Tour

If Array/JSON is valid

NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;

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

92

Page 93: REST/JSON/CoreData Example Code - A Tour

Iterate over it

NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;

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

93

Page 94: REST/JSON/CoreData Example Code - A Tour

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

Page 95: REST/JSON/CoreData Example Code - A Tour

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

Page 96: REST/JSON/CoreData Example Code - A Tour

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

Page 97: REST/JSON/CoreData Example Code - A Tour

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

Page 98: REST/JSON/CoreData Example Code - A Tour

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

Page 99: REST/JSON/CoreData Example Code - A Tour

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

Page 100: REST/JSON/CoreData Example Code - A Tour

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

Page 101: REST/JSON/CoreData Example Code - A Tour

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

Page 102: REST/JSON/CoreData Example Code - A Tour

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

Page 103: REST/JSON/CoreData Example Code - A Tour

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

Page 104: REST/JSON/CoreData Example Code - A Tour

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

Page 105: REST/JSON/CoreData Example Code - A Tour

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

Page 106: REST/JSON/CoreData Example Code - A Tour

connectionDidFinishLoading 7/n

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

106

Page 107: REST/JSON/CoreData Example Code - A Tour

Save and check for errors

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

107

Page 108: REST/JSON/CoreData Example Code - A Tour

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

Page 109: REST/JSON/CoreData Example Code - A Tour

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

Page 110: REST/JSON/CoreData Example Code - A Tour

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

Page 111: REST/JSON/CoreData Example Code - A Tour

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

Page 112: REST/JSON/CoreData Example Code - A Tour

Review

• Asynchronous programming

• NSOperations

• Grand Central Dispatch (GCD)

• More Blocks

• Notifications

• App Lifecycle

• Network I/O (HTTP/REST)

• JSON parsing112

Page 113: REST/JSON/CoreData Example Code - A Tour

Questions?Now, Or Later:[email protected]@CarlBrwn (Twitter/App.net)

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

113