The NetworkManager singleton class handles network requests for the application. It starts monitoring network reachability when initialized. It kicks off the initial data fetch by queuing a request for a specific URL path. The NetworkManager will inform the user of network status and reject requests if offline.
The Codex of Business Writing Software for Real-World Solutions 2.pptx
REST/JSON/CoreData Example Code - A Tour
1. REST/JSON/CoreData
Example Code
github.com/carlbrown/
SeismicJSON
Carl Brown
Twitter: @CarlBrwn
Email: CarlB@PDAgent.com
1
Turn on Camera and ScreenFlow!!
2. 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
4. UI tasks Must be on the Main
Thread
Often called the "UI" thread for that reason
4
5. 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
6. 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
7. SeismicJSON
The 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
8. 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.
9. Please Open
MasterView
Controller.m
This 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
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
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 we're expecting (race condition checking)
imageDidBecomeAvailableAtPath
1/2
27
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 disk
imageDidBecomeAvailableAtPath
2/2
28
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. 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
30
Some people argue with me about this, but it's served me well for years
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. 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_Computing
32
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. 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. 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. 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
38. 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
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
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 App
Delegate
#define kNSNOTIFICATIONS_HANDLED_IN_APPDELEGATE 0
Note: I'm 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 if's.
Project Variations
42
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 I'm adding a notification observer
52
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
78. -(void) finish {
[self setDone:YES];
if (self.delegate) {
[self.delegate decrementActiveFetches];
}
CFRunLoopStop(CFRunLoopGetCurrent());
}
Inform user we're done
78
79. -(void) finish {
[self setDone:YES];
if (self.delegate) {
[self.delegate decrementActiveFetches];
}
CFRunLoopStop(CFRunLoopGetCurrent());
}
Stop the runloop & get
off
79
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
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]];
#endif
If the JSON was good
87
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 new
ManagedObjectContext
88
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. 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. NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;
if (jsonDict) {
NSArray *events = [jsonDict objectForKey:@"features"];
if (events) {
for (NSDictionary *eventDict in events) {
Get Array of
Earthquakes
91
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. NSDictionary *jsonDict = (NSDictionary *) objectFromJSON;
if (jsonDict) {
NSArray *events = [jsonDict objectForKey:@"features"];
if (events) {
for (NSDictionary *eventDict in events) {
Iterate over it
93
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. 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"];
}
connectionDidFinish
Loading 6/n
102
103. 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"];
}
If there isn't already
one
103
104. 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"];
}
Make a new Object
104
105. 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"];
}
Set all its attributes
105
106. // Save the context.
error = nil;
if (![context save:&error]) {
// stuff
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
connectionDidFinish
Loading 7/n
106
107. // Save the context.
error = nil;
if (![context save:&error]) {
// stuff
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
Save and
check for errors
107