REST/JSON/CoreData      Example Code               github.com/carlbrown/               SeismicJSON               Carl Brow...
Asynchronous iOS Programming Rules    • Threads are bad    • Use Queues instead    • Apples Concurrency Programming Guide*...
Some things arent thread-safe•Any UIAnything in iOS•CoreData                                 3
UI tasks Must be on the MainThreadOften called the "UI" thread for that reason                                            ...
CoreData Contexts and Objects are tied to athread• Never share a CoreData object between threads• Never share a CoreData o...
Dont cross the threads•Use the same serial queue to stay on the same thread•Use dispatch_get_main_queue() or [NSOperationQ...
SeismicJSONThe only project well beworking with todayFeel free to run it and playwith it for a couple of minutesAlso on gi...
First Exercise:  Follow the Code                                                              8This is a vital skill to ha...
Please Open  MasterView  Controller.m  This is pretty much a  tableViewController like  youve seen before.  Ignore the #if...
MasterViewController               should be mostly familiar• Cell labels filled in from Model Object• custom XIB for Table...
DetailViewController•Really simple•simpler than the tableViewCell•Just a bunch of labels•nothing to discuss here          ...
ActivityIndicatingImageViewThis has aUIActivityIndicatorView,implements a newcustom @protocol andrefers to aNetworkManager...
So openActivityIndicatingImageView.m                     13
-(void) awakeFromNib {    _activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIn...
-(void) setImage:(UIImage *)image {    [super setImage:image];    if (image) {        [self.activityIndicator stopAnimatin...
-(void) setImageFileName:(NSString *)imageFileName {   _imageFileName = imageFileName;   if (_imageFileName==nil) {       ...
//If the file already exists, dont bother to fetch it again  NSString *fullFilePath = [[[NetworkManager sharedManager]    ...
[[NetworkManager sharedManager]           fetchImagewithFilename:                 imageFileName andNotifyTarget:self];}set...
-(void) fetchImagewithFilename:(NSString *) filenameandNotifyTarget:(NSObject <ImageFetchDelegate> *) target{    //Stuff w...
ImageFetchOperation.m              20
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {    if (self.response.statusCode==200) {        NSError ...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {    if (self.response.statusCode==200) {        NSError ...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {    if (self.response.statusCode==200) {        NSError ...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {    if (self.response.statusCode==200) {        NSError ...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {    if (self.response.statusCode==200) {        NSError ...
[control]-[⌘]-Jback to ActivityIndicatingImageView                                        26
-(void) imageDidBecomeAvailableAtPath:(NSString *) path {    if (![[path lastPathComponent]            isEqualToString:sel...
//load image off the main queue    UIImage *imageToLoad=[UIImage imageWithContentsOfFile:path];    dispatch_async(dispatch...
Summary of ActivityIndicatingImageView• Start the view with a spinner telling the user we are working on  something• See i...
Recommended   Networking Strategy   •Always* load the UI from local storage      •Core Data or local file or something   •A...
Why do it that way?•Separates network code from UI code•Easier to test•Much faster response if previous data•Much better u...
Why wouldnt you?    •Pointless if the network is infinitely fast     and infinitely reliable*    •More effort than "Unbreakab...
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 queri...
NSOperationQueue• Long-lived (presumably) queue that can contain  numerous operations• Can be serial or concurrent• Can be...
Dispatch Queues•C-style (concise) syntax•quicker to use in-place•much less typing than declaring an NSOperation and adding...
Which to use?• No hard-and-fast rules, but...• I tend to use NSOperations for:  • things Im going to do several times  • t...
Waiting in Cocoa•Dont Sleep•Dont use locks•Yield to the RunLoop•See the FetchOperation for example•Sleeping or Locking Fre...
Be Nice to Threads• POSIX Threads are a finite resource• The system will spin up more if tasks are  waiting• But when no mo...
Back to our Application                          40
Please OpenNotificationOrParentContext.h                  41
//Make this a 1 to show notifications, and a 0 to show parent contexts#define kUSE_NSNOTIFICATIONS_FOR_CONTEXT_MERGE 0//if...
OK, to theAppDelegate.m               43
Open SourceControl View               44
Click the TimeMachine                        45
Scroll in the center ofPick the Initial Commit   the screen                                                    46
Got rid of some Xcode              4.4-isms                         47
Removed Observer                   48
applicationWillResignActive:!(UIApplication *) application•Happens when user gets Texts, notifications, Alerts, phone calls...
Added Observer                 50
Kicked off Network             Fetch                     51
applicationDidBecomeActive:(UIApplication *)application• Happens when App becomes full-focus• After launch• Or after retur...
[[NSNotificationCenter defaultCenter] addObserver:self      selector:@selector(changesSaved:)      name:NSManagedObjectCon...
- (void)changesSaved:(NSNotification *)notification {    if (![NSThread isMainThread]) {        dispatch_async(dispatch_ge...
- (void)changesSaved:(NSNotification *)notification {    if (![NSThread isMainThread]) {        dispatch_async(dispatch_ge...
- (void)changesSaved:(NSNotification *)notification {    if (![NSThread isMainThread]) {        dispatch_async(dispatch_ge...
Queue Concurrency             Type                    57
Reset DB each run                    58
Back tonormal viewnow              59
NetworkManager.m            60
+ (NetworkManager *)sharedManager {    static dispatch_once_t pred; dispatch_once(&pred, ^{        sharedManager = [[self ...
-(void) startMainPageFetch {    [self setHostReach:[Reachability          reachabilityWithHostName:[self.baseURL host]]]; ...
-(void) startMainPageFetch {    [self setHostReach:[Reachability          reachabilityWithHostName:[self.baseURL host]]]; ...
Just do it. If you want tounderstand it, read Apples writeup                                      64
-(void) startMainPageFetch {    [self setHostReach:[Reachability          reachabilityWithHostName:[self.baseURL host]]]; ...
-(void) queuePageFetchForRelativePath:(NSString *) relativePath {    EarthquakeFetchOperation *earthquakeFetchOperation = ...
#import <Foundation/Foundation.h>#import "BaseFetchOperation.h"@interface EarthquakeFetchOperation : BaseFetchOperation@pr...
@interface BaseFetchOperation : NSOperation                       <NSURLConnectionDataDelegate>@property   (nonatomic,   s...
Methods needed for      URL fetching                     69
BaseFetchOperation.m              70
- (void)main {    if ([self isCancelled]) {        return;    }    if (!_urlForJSONData) {        NSLog(@"Cannot start wit...
- (void)main {    if ([self isCancelled]) {        return;    }    if (!_urlForJSONData) {        NSLog(@"Cannot start wit...
- (void)main {    if ([self isCancelled]) {        return;    }    if (!_urlForJSONData) {        NSLog(@"Cannot start wit...
- (void)main {    if ([self isCancelled]) {        return;    }    if (!_urlForJSONData) {        NSLog(@"Cannot start wit...
- (void)main {    if ([self isCancelled]) {        return;    }    if (!_urlForJSONData) {        NSLog(@"Cannot start wit...
- (void)main {    if ([self isCancelled]) {        return;    }    if (!_urlForJSONData) {        NSLog(@"Cannot start wit...
-(void) finish {    [self setDone:YES];    if (self.delegate) {        [self.delegate decrementActiveFetches];    }    CFR...
-(void) finish {    [self setDone:YES];    if (self.delegate) {        [self.delegate decrementActiveFetches];    }    CFR...
-(void) finish {    [self setDone:YES];    if (self.delegate) {        [self.delegate decrementActiveFetches];    }    CFR...
Other methods there• didReceiveResponse  • remember response  • truncate data  • (can get more than one response)• didRece...
EarthquakeFetchOperation                 81
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{    if ([self isCancelled]) {        [self finish];      ...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{    if ([self isCancelled]) {        [self finish];      ...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{    if ([self isCancelled]) {        [self finish];      ...
id objectFromJSON = [NSJSONSerialization  JSONObjectWithData:self.fetchedData options:0 error:&error];if (objectFromJSON) ...
id objectFromJSON = [NSJSONSerialization  JSONObjectWithData:self.fetchedData options:0 error:&error];if (objectFromJSON) ...
id objectFromJSON = [NSJSONSerialization  JSONObjectWithData:self.fetchedData options:0 error:&error];if (objectFromJSON) ...
id objectFromJSON = [NSJSONSerialization  JSONObjectWithData:self.fetchedData options:0 error:&error];if (objectFromJSON) ...
NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;if (jsonDict) {   NSArray *events = [jsonDict objectForKey:@"feat...
NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;if (jsonDict) {   NSArray *events = [jsonDict objectForKey:@"feat...
NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;if (jsonDict) {   NSArray *events = [jsonDict objectForKey:@"feat...
NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;if (jsonDict) {   NSArray *events = [jsonDict objectForKey:@"feat...
NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;if (jsonDict) {   NSArray *events = [jsonDict objectForKey:@"feat...
NSString *eventLocation = [eventDict                    valueForKeyPath:@"properties.place"];NSDate *eventDate = [NSDate d...
NSString *eventLocation = [eventDict                    valueForKeyPath:@"properties.place"];NSDate *eventDate = [NSDate d...
NSString *eventLocation = [eventDict                    valueForKeyPath:@"properties.place"];NSDate *eventDate = [NSDate d...
NSString *eventLocation = [eventDict                    valueForKeyPath:@"properties.place"];NSDate *eventDate = [NSDate d...
NSFetchRequest *fetchRequest =  [NSFetchRequest fetchRequestWithEntityName:          NSStringFromClass([Earthquake class])...
NSFetchRequest *fetchRequest =  [NSFetchRequest fetchRequestWithEntityName:          NSStringFromClass([Earthquake class])...
NSFetchRequest *fetchRequest =  [NSFetchRequest fetchRequestWithEntityName:          NSStringFromClass([Earthquake class])...
NSFetchRequest *fetchRequest =  [NSFetchRequest fetchRequestWithEntityName:          NSStringFromClass([Earthquake class])...
if ([existingEventsMatchingThisOne count]==0) {    //Didnt find one already, make a new one    NSManagedObject *newManaged...
if ([existingEventsMatchingThisOne count]==0) {    //Didnt find one already, make a new one    NSManagedObject *newManaged...
if ([existingEventsMatchingThisOne count]==0) {    //Didnt find one already, make a new one    NSManagedObject *newManaged...
if ([existingEventsMatchingThisOne count]==0) {    //Didnt find one already, make a new one    NSManagedObject *newManaged...
// Save the context.error = nil;if (![context save:&error]) {    // stuff    NSLog(@"Unresolved error %@, %@", error, [err...
// Save the context.error = nil;if (![context save:&error]) {    // stuff    NSLog(@"Unresolved error %@, %@", error, [err...
#if kUSE_PARENT_CONTEXTS_FOR_CONTEXT_MERGE  dispatch_sync(dispatch_get_main_queue(), ^{    NSError *error = nil;    if (![...
#if kUSE_PARENT_CONTEXTS_FOR_CONTEXT_MERGE  dispatch_sync(dispatch_get_main_queue(), ^{    NSError *error = nil;    if (![...
#if kUSE_PARENT_CONTEXTS_FOR_CONTEXT_MERGE  dispatch_sync(dispatch_get_main_queue(), ^{    NSError *error = nil;    if (![...
#if kUSE_PARENT_CONTEXTS_FOR_CONTEXT_MERGE  dispatch_sync(dispatch_get_main_queue(), ^{    NSError *error = nil;    if (![...
Review • Asynchronous programming • NSOperations • Grand Central Dispatch (GCD) • More Blocks • Notifications • App Lifecyc...
Questions? Now, Or Later: CarlB@PDAgent.com @CarlBrwn (Twitter/App.net) Todays App was:  https://github.com/carlbrown/Seis...
Upcoming SlideShare
Loading in...5
×

REST/JSON/CoreData Example Code - A Tour

5,548

Published on

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.

Published in: Technology
0 Comments
6 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
5,548
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
119
Comments
0
Likes
6
Embeds 0
No embeds

No notes for slide

REST/JSON/CoreData Example Code - A Tour

  1. 1. REST/JSON/CoreData Example Code github.com/carlbrown/ SeismicJSON Carl Brown Twitter: @CarlBrwn Email: CarlB@PDAgent.com 1Turn on Camera and ScreenFlow!!
  2. 2. Asynchronous iOS Programming Rules • Threads are bad • Use Queues instead • Apples 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
  3. 3. Some things arent thread-safe•Any UIAnything in iOS•CoreData 3
  4. 4. UI tasks Must be on the MainThreadOften called the "UI" thread for that reason 4
  5. 5. CoreData Contexts and Objects are tied to athread• 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 youve made changes 5
  6. 6. Dont 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
  7. 7. SeismicJSONThe only project well beworking with todayFeel free to run it and playwith it for a couple of minutesAlso on github so you cansee how it was written. 7
  8. 8. First Exercise: Follow the Code 8This is a vital skill to have. We dont expect people towrite in English without reading a lot of English first, butprogrammers consistently spend more time writing codethan reading it while learning a new language.
  9. 9. Please Open MasterView Controller.m This is pretty much a tableViewController like youve seen before. Ignore the #if conditionals this run-through 9Ignore the #if conditionals this run-through
  10. 10. MasterViewController should be mostly familiar• 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) 10
  11. 11. DetailViewController•Really simple•simpler than the tableViewCell•Just a bunch of labels•nothing to discuss here 11
  12. 12. ActivityIndicatingImageViewThis has aUIActivityIndicatorView,implements a newcustom @protocol andrefers to aNetworkManager. 12
  13. 13. So openActivityIndicatingImageView.m 13
  14. 14. -(void) awakeFromNib { _activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; [_activityIndicator setFrame:self.frame]; [_activityIndicator setHidesWhenStopped:YES]; [self addSubview:_activityIndicator]; [_activityIndicator startAnimating];} Start Spinner upon waking 14
  15. 15. -(void) setImage:(UIImage *)image { [super setImage:image]; if (image) { [self.activityIndicator stopAnimating]; } else { [self.activityIndicator startAnimating]; }} Stop it when we get image 15
  16. 16. -(void) setImageFileName:(NSString *)imageFileName { _imageFileName = imageFileName; if (_imageFileName==nil) { [self setImage:nil]; return; }setImageFileName 1/3 16
  17. 17. //If the file already exists, dont bother to fetch it again NSString *fullFilePath = [[[NetworkManager sharedManager] cachedImageDirectory] stringByAppendingPathComponent:_imageFileName]; if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath]) { [self imageDidBecomeAvailableAtPath:fullFilePath]; return; }setImageFileName 2/3 17
  18. 18. [[NetworkManager sharedManager] fetchImagewithFilename: imageFileName andNotifyTarget:self];}setImageFileName 3/3 18
  19. 19. -(void) fetchImagewithFilename:(NSString *) filenameandNotifyTarget:(NSObject <ImageFetchDelegate> *) target{ //Stuff weve seen before ImageFetchOperation *imageFetchOperation = [[ImageFetchOperation alloc] init]; [imageFetchOperation setUrlToFetch: [self imageURLForImageFileName:filename]]; [imageFetchOperation setNotificationTarget:target]; [self.fetchQueue addOperation:imageFetchOperation];}OK, now were getting somewhere 19
  20. 20. ImageFetchOperation.m 20
  21. 21. - (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]; } }} connectionDidFinish Loading (abridged) 21
  22. 22. - (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]; } }}Only save if no error 22
  23. 23. - (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]; } }} Get filename/path 23
  24. 24. - (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]; } }} Save File 24
  25. 25. - (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]; } }}Let the View Know 25
  26. 26. [control]-[⌘]-Jback to ActivityIndicatingImageView 26
  27. 27. -(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 were expecting (race condition checking)imageDidBecomeAvailableAtPath 1/2 27
  28. 28. //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 diskimageDidBecomeAvailableAtPath 2/2 28
  29. 29. 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
  30. 30. Recommended 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 *Except with live web pages or HTTP streaming 30Some people argue with me about this, but its served me well for years
  31. 31. 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
  32. 32. Why wouldnt 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_Computing 32
  33. 33. NSOperations and GCD 33
  34. 34. 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
  35. 35. 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
  36. 36. 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
  37. 37. Which to use?• No hard-and-fast rules, but...• I tend to use NSOperations for: • things Im 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 wont need to change when spec changes 37
  38. 38. Waiting in Cocoa•Dont Sleep•Dont use locks•Yield to the RunLoop•See the FetchOperation for example•Sleeping or Locking Freezes the Thread 38
  39. 39. 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
  40. 40. Back to our Application 40
  41. 41. Please OpenNotificationOrParentContext.h 41
  42. 42. //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 AppDelegate#define kNSNOTIFICATIONS_HANDLED_IN_APPDELEGATE 0Note: Im not usually a fan of this kind of conditional compilation, but for this case, I thought it would let you play with project in the debugger in a cleaner way than with traditional ifs. Project Variations 42
  43. 43. OK, to theAppDelegate.m 43
  44. 44. Open SourceControl View 44
  45. 45. Click the TimeMachine 45
  46. 46. Scroll in the center ofPick the Initial Commit the screen 46
  47. 47. Got rid of some Xcode 4.4-isms 47
  48. 48. Removed Observer 48
  49. 49. applicationWillResignActive:!(UIApplication *) application•Happens when user gets Texts, notifications, Alerts, phone calls or hits the home button•Here Im removing the notification observer so we wont try to get notifications while not Active 49
  50. 50. Added Observer 50
  51. 51. Kicked off Network Fetch 51
  52. 52. 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 Im adding a notification observer 52
  53. 53. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changesSaved:) name:NSManagedObjectContextDidSaveNotification object:nil]; This Runs "changesSaved:" 53
  54. 54. - (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]; }} Handler Code 54
  55. 55. - (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]; }}If not on Main, go there 55
  56. 56. - (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]; }} Merge changes 56
  57. 57. Queue Concurrency Type 57
  58. 58. Reset DB each run 58
  59. 59. Back tonormal viewnow 59
  60. 60. NetworkManager.m 60
  61. 61. + (NetworkManager *)sharedManager { static dispatch_once_t pred; dispatch_once(&pred, ^{ sharedManager = [[self alloc] init]; //Initialization Stuff }); return sharedManager;} Singleton Pattern 61
  62. 62. -(void) startMainPageFetch { [self setHostReach:[Reachability reachabilityWithHostName:[self.baseURL host]]]; [self.hostReach startNotifier]; [self queuePageFetchForRelativePath: @"/earthquakes/feed/geojson/significant/month"];} Kicked off from AppDelegate 62
  63. 63. -(void) startMainPageFetch { [self setHostReach:[Reachability reachabilityWithHostName:[self.baseURL host]]]; [self.hostReach startNotifier]; [self queuePageFetchForRelativePath: @"/earthquakes/feed/geojson/significant/month"];}Inform users of network status or be Rejected 63
  64. 64. Just do it. If you want tounderstand it, read Apples writeup 64
  65. 65. -(void) startMainPageFetch { [self setHostReach:[Reachability reachabilityWithHostName:[self.baseURL host]]]; [self.hostReach startNotifier]; [self queuePageFetchForRelativePath: @"/earthquakes/feed/geojson/significant/month"];}Start fetch of first batch 65
  66. 66. -(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];}Make NSOp & Queue it 66
  67. 67. #import <Foundation/Foundation.h>#import "BaseFetchOperation.h"@interface EarthquakeFetchOperation : BaseFetchOperation@property (nonatomic, weak) NSManagedObjectContext *mainContext;@endEarthquakeFetchOperation.h 67
  68. 68. @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;@endBaseFetchOperation.h 68
  69. 69. Methods needed for URL fetching 69
  70. 70. BaseFetchOperation.m 70
  71. 71. - (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();} Entry Point 71
  72. 72. - (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();} Sanity Check 72
  73. 73. - (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();} Make request 73
  74. 74. - (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();} Inform user were active 74
  75. 75. - (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();} Start Connection 75
  76. 76. - (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();} Give Up Control of thread 76
  77. 77. -(void) finish { [self setDone:YES]; if (self.delegate) { [self.delegate decrementActiveFetches]; } CFRunLoopStop(CFRunLoopGetCurrent());} Finish 77
  78. 78. -(void) finish { [self setDone:YES]; if (self.delegate) { [self.delegate decrementActiveFetches]; } CFRunLoopStop(CFRunLoopGetCurrent());}Inform user were done 78
  79. 79. -(void) finish { [self setDone:YES]; if (self.delegate) { [self.delegate decrementActiveFetches]; } CFRunLoopStop(CFRunLoopGetCurrent());}Stop the runloop & get off 79
  80. 80. Other methods there• didReceiveResponse • remember response • truncate data • (can get more than one response)• didReceiveData • append data• didFailWithError • report error to our delegate 80
  81. 81. EarthquakeFetchOperation 81
  82. 82. - (void)connectionDidFinishLoading:(NSURLConnection *)connection{ if ([self isCancelled]) { [self finish]; return; } if (self.response.statusCode==200) {connectionDidFinishLoading 1/n 82
  83. 83. - (void)connectionDidFinishLoading:(NSURLConnection *)connection{ if ([self isCancelled]) { [self finish]; return; } if (self.response.statusCode==200) { Sanity Check/ Housekeeping 83
  84. 84. - (void)connectionDidFinishLoading:(NSURLConnection *)connection{ if ([self isCancelled]) { [self finish]; return; } if (self.response.statusCode==200) { Dont parse bad response 84
  85. 85. 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 connectionDidFinish Loading 2/n 85
  86. 86. 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 Parse JSON 86
  87. 87. 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]];#endifIf the JSON was good 87
  88. 88. 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 Make newManagedObjectContext 88
  89. 89. NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;if (jsonDict) { NSArray *events = [jsonDict objectForKey:@"features"]; if (events) { for (NSDictionary *eventDict in events) { connectionDidFinish Loading 3/n 89
  90. 90. NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;if (jsonDict) { NSArray *events = [jsonDict objectForKey:@"features"]; if (events) { for (NSDictionary *eventDict in events) {If we got a dictionary 90
  91. 91. NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;if (jsonDict) { NSArray *events = [jsonDict objectForKey:@"features"]; if (events) { for (NSDictionary *eventDict in events) { Get Array of Earthquakes 91
  92. 92. NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;if (jsonDict) { NSArray *events = [jsonDict objectForKey:@"features"]; if (events) { for (NSDictionary *eventDict in events) {If Array/JSON is valid 92
  93. 93. NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;if (jsonDict) { NSArray *events = [jsonDict objectForKey:@"features"]; if (events) { for (NSDictionary *eventDict in events) { Iterate over it 93
  94. 94. 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"]]; connectionDidFinish Loading 4/n 94
  95. 95. 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"]]; Extract values from eventDict 95
  96. 96. 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"]]; Using keyPaths 96
  97. 97. 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"]];and/or Array elements 97
  98. 98. 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]; connectionDidFinish Loading 5/n 98
  99. 99. 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];Make a fetch request 99
  100. 100. 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];matching our event 100
  101. 101. 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]; And run it 101
  102. 102. if ([existingEventsMatchingThisOne count]==0) { //Didnt 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"];} connectionDidFinish Loading 6/n 102
  103. 103. if ([existingEventsMatchingThisOne count]==0) { //Didnt 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"];} If there isnt already one 103
  104. 104. if ([existingEventsMatchingThisOne count]==0) { //Didnt 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"];}Make a new Object 104
  105. 105. if ([existingEventsMatchingThisOne count]==0) { //Didnt 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"];}Set all its attributes 105
  106. 106. // Save the context.error = nil;if (![context save:&error]) { // stuff NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort();} connectionDidFinish Loading 7/n 106
  107. 107. // Save the context.error = nil;if (![context save:&error]) { // stuff NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort();} Save and check for errors 107
  108. 108. #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 connectionDidFinish Loading 8/n 108
  109. 109. #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 If were merging via Parent 109
  110. 110. #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(); } });#endifOn the Main Thread 110
  111. 111. #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(); } });#endifTell the main context to save 111
  112. 112. Review • Asynchronous programming • NSOperations • Grand Central Dispatch (GCD) • More Blocks • Notifications • App Lifecycle • Network I/O (HTTP/REST) • JSON parsing 112
  113. 113. Questions? Now, Or Later: CarlB@PDAgent.com @CarlBrwn (Twitter/App.net) Todays App was: https://github.com/carlbrown/SeismicJSON 113
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×