Out of the Woods
Are we out of the woods yet?
Are we out of the woods yet?
Are we out of the woods yet?
Are we out of the woods?
Are we in the clear yet?
Are we in the clear yet?
Are we in the clear yet?
In the clear yet, good!
This is what I ask myself when making an API request and waiting for the rest of my methods to run.
One of the interesting things about pulling data from an API is how we coordinate the order of events in our app.
I am working on an app that grabs Github repositories and lists them in a table view. I’ve placed NSLogs throughout my code so that I can see the order of events in my app. Let’s see what happens when I run my app.
1. TVC: Before calling getRepo
2. Data Store: getRepo began
3. GithubAPIClient: getRepo method began
4. GithubAPIClient: Before dataTask created
5. GithubAPIClient:After dataTask created
6. GithubAPIClient: After resume and method end
7. GithubAPIClient: In Data Task Completion
8. GithubAPIClient: About to call completionBlock
9. Data Store: In getRepo Completion Block
10. FISGithubRepo: Creating Github Repo (prints lots of times, once for each repo found)
11. Data Store: Finished creating GithubRepo Objects
12. Data Store: About to call completionBlock
13. TVC: In completionBlock
14. TVC: After reloadData
15. DataStore: Called CompletionBlock
16. GithubAPIClient: After calling completionBlock
All of these things happen quite quickly, but its interesting to note what happens first. First, all my ‘began’ NSLogs print except for the NSLog in FISGithubRepository. Thats because NSLog(@”FISGithubRepo: Creating Github Repo”) only happens once the method that it’s in is called. That method is called in a completion block in the data store. The completion block only gets called after the API request finishes running. It’s easy to see why this would be when you look inside my method:
-(void)getRepositories:(void (^)(BOOL))completionBlock
{
NSLog(@"Data Store: getRepo began");
[FISGithubAPIClient getRepositoriesWithCompletionBlock:^
(NSArray *repositoryDictionaries) {
NSLog(@"Data Store: In getRepo Completion Block");
NSMutableArray *allRepositories = [[NSMutableArray alloc] init];
for (NSDictionary *repositoryDictionary in repositoryDictionaries) {
FISGithubRepository *newRepo = [FISGithubRepository repoWithRepoDictionary:repositoryDictionary];
[allRepositories addObject:newRepo];
}
self.repositories = allRepositories;
NSLog*@"Data Store: Finished creating GithubRepo Objects");
if ([self.repositories count]>0) {
NSLog(@"Data Store: About to call completionBlock");
completionBlock(YES);NSLog(@"Data Store: Called Completion Block");
} else
{
completionBlock(NO);
}
}];
}
I can’t quite get Medium to do what I want as far as code snippets go…But as you can see, the completion block uses the method in our Github repo class repoWithRepoDictionary…so when the whole block that method is in (our getRepositories method) gets run, repoWithRepoDictionary can run and its the RepoDictionary NSLog gets printed. Fascinating stuff. And it really gets to the design and *choreography* of our code.
After the ‘began’ NSLogs get run, all* the NSLogs in the Github API client class get run:
GithubAPIClient: getRepo method began
GithubAPIClient: Before dataTask created
GithubAPIClient:After dataTask created
GithubAPIClient: After resume and method end
GithubAPIClient: In Data Task Completion
GithubAPIClient: About to call completionBlock
*except for NSLog(@(After calling completionBlock”) in the API client
These NSLogs show the chronology of our getRepositoriesWithCompletionBlock method.
-Our method begins
-We make an NSURL object and a NSURLSession object
-We NSLog that the dataTask is about to be created.
-The data task variable is created and we send it the dataTaskWithURL method. We ultimately use the data task to get data from a URL (stored in repositoriesURL here) and then to resume the other events of the app when the method is finished. It’s a very important object here.
-We NSLog that we are in the process of completing our block.
-We convert our data from JSON to to an NSArray called jsonArray.
-We NSLog that we are about to call the completion block.
-We call the completion block → completionBlock(jsonArray);
-We NSLog “after calling completionBlock.
-We NSLog that we have created our data task.
-We send the resume method onto our dataTask variable.
-We NSLog that the block has been completed and that things can resume.
Once we are our of the API Client (are we out of the woods yet?) we get a NSLog in our completion block in the data store. The completion block is running. Cool. Now, woah! A million identical NSLogs from our Github repo class. This indicates an NSLog for each repo found by our APIClient class. Every time the repoWithRepoDictionary class method runs, this gets NSLogged.
The last few NSLogs are interesting too:
11. Data Store: Finished creating GithubRepo Objects
12. Data Store: About to call completionBlock
13. TVC: In completionBlock
14. TVC: After reloadData
15. DataStore: Called CompletionBlock
16. GithubAPIClient: After calling completionBlock
First, our data store prints that we finished making all the Github repo objects our APIClient could find.
Then, our data store (in the if statement of our getRepositories method) prints that we are about to call our completion block.
We call our completion block → completionBlock(YES);
Then we see an NSLog from the data store that says that we called the completion block.
So at this point, we get an NSLog from our table view controller. This comes after:
self.dataStore = [FISGithubDataStore sharedDataStore];[self.dataStore getRepositories:^(BOOL success) {
Those lines of code involve a whole lot of work from our server, our API client, and our entire application, really. We’re out of the woods! (Almost!)
Then we have an NSLog that we are in our getRepositories completion block. Following the block (an if statement that reloads the tableView’s data if our API request is successful) we NSLog that we have reloaded our data, and that the data in the table view is updated: NSLog(@”TVC: After reloadData”);
Now we have our last two NSLogs, almost simultaneously: Data store prints “Called completionBlock” suggesting that the completionBlock has been called, and the Github API client class prints that the completionBlock has been called: NSLog(@“After calling completionBlock).
OH MAN. Hope you were able to follow that without seeing all of my code. It’s really interesting to think about the timing of my events in this app, especially in terms of what it shows about the relationships of the components of my program.
In summary:
We get a bunch of NSLogs before our methods begin.
Our API Client starts looking for data.
Our data store starts using the block.
Our Github repo class method gets run for each repository we find via the API client.
Our data gets ready to call its completion block. In our block we add each new repository to our allRepositories array
Our table view controller finally wakes up from a long (maybe a millisecond long?) nap and starts its completion block, which basically says if our API request runs successfully, reload the data on our table view.
It also lets us know when its done reloading data.
Finally, our data store and API client both peace out.
TL;DR
Timing is extremely important when designing apps that pull from APIS. It’s useful to NSLog each event in your app to understand the true order of events (they all seem to happen instanteously, because they are seconds apart).
Let me now if you have any comments or questions. It’s only my third day playing with APIS ☺.