rest/json/coredata example code - a tour
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: [email protected]
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:[email protected]@CarlBrwn (Twitter/App.net)
Today's App was: https://github.com/carlbrown/SeismicJSON
113