Bummer! This is just a preview. You need to be signed in with a Basic account to view the entire video.
Start a free Basic trial
to watch this video
In this video, let's add functionality to the ItunesAPIClient to fetch a list of albums for a given artist.
-
0:00
To tap on an artist from the search results and
-
0:03
go to the next page, we have to do two things.
-
0:06
Define a method in the iTunes API client that performs the request for us and
-
0:10
returns a populated artist instance containing the albums and
-
0:15
update the code in the prepare for
-
0:17
Segway method on the search results controller where we're using step data.
-
0:22
So first up, iTunes API client.
-
0:25
And in here, let's add a second method.
-
0:28
And we'll call this one func lookupArtist(withId id: Int),
-
0:36
so here we also need a completion handler.
-
0:41
And this is also going to be an escaping closure.
-
0:46
And the signature here, since we're looking up a single artist and
-
0:51
returning it, is Artist, and so we'll make this optional, or
-
0:55
an ItunesError, also optional, so void.
-
1:01
Most of the body of this method is the same,
-
1:03
we just need to parse the resulting JSON differently.
-
1:07
So again, first we need an endpoint, let endpoint.
-
1:13
At some point, we'll learn how to type out endpoint.
-
1:17
So Itunes.lookup this time.
-
1:19
Pass the id through and the entity is MusicEntity.album.
-
1:25
Like before we can use the endpoint to create and carry out in JSON task.
-
1:30
So let task = downloader.JSONTask with endpoint.request.
-
1:38
And again, we'll use a trailing closure to get the JSON and
-
1:44
error object to work with, remember, do not forget to call task.resume.
-
1:51
Just like the previous method, I'm going to jump back onto the main thread here,
-
1:54
since everything we're doing now is synchronous and
-
1:57
won't block the main thread.
-
1:59
This removes the burden of having to remember to do it at the call sign.
-
2:03
So DispatchQueue.main.async.
-
2:07
Again here, the JSON body is optional and may be new, so
-
2:11
we need to unwrap it and check for an error first.
-
2:14
We can copy-paste this block.
-
2:17
So up at the top, we'll grab this guard let json = json, paste that in here,
-
2:22
and the only thing we need to change, instead of passing an empty array as
-
2:27
the argument to the completion handler, it's nil this time,
-
2:30
because we don't have an array of artists anymore, just a single one.
-
2:34
In this payload of data,
-
2:35
inside this JSON response, we still have a top level dictionary.
-
2:40
And all the information we want is housed under the result key.
-
2:44
So we can actually copy paste this second bit of code as well.
-
2:50
Now, come to think of it, this is also how the third method,
-
2:53
the lookup album method is going to be structured.
-
2:57
So already we're repeating the work, but
-
2:59
instead of repeating this work three times.
-
3:02
Let's factor this out into a single method that we can use.
-
3:08
We'll make this a private method.
-
3:10
We'll say private func, perform request with endpoint.
-
3:17
Hey, look at that, I typed endpoint correctly.
-
3:20
The completion handler here is also going to be escaping.
-
3:24
And the signature will be (Results?,
-
3:28
ItunesError?) -> Void).
-
3:34
Now, what is this results type here?
-
3:37
Well, it's just the type alias for the kind of data we're going to get
-
3:40
using the results key which is an array of dictionaries.
-
3:45
So we'll just create that right above that here.
-
3:47
We'll say type alias results equal and we'll paste that type in.
-
3:53
Now, we can go back to the top here and
-
3:58
let's just copy paste the body of the first method except this endpoint bit.
-
4:03
So let task all the way to resume and paste it in here inside this new method.
-
4:11
We'll change the empty arrays case here in the failure case to nil for
-
4:16
every single one.
-
4:20
We'll get rid of the artist-related code at the bottom and
-
4:25
just call the completion handler with this results object that we're parsing.
-
4:34
Now, we can refactor out our first method
-
4:37
to use this function instead of doing all of that work again.
-
4:40
That way we don't have to call create three JSON tasks, call resume three times,
-
4:45
call dispatchQueue.main.async three times and
-
4:48
then get to the result object three times.
-
4:51
So instead we'll get rid of all of this except the endpoint.
-
4:55
And inside searchForArtists we can say performRequest with endpoint and
-
5:02
the completion handler here is results comma error in.
-
5:09
And now, we need to make sure that we have a results object,
-
5:12
cuz this could be optional.
-
5:14
So we'll say guard let results equal results else.
-
5:20
Here we'll call completion.
-
5:22
And this time, since we're in the search for artists, which calls a closure
-
5:28
that accepts an array of artists, we'll parse an empty array and new and return.
-
5:35
And then at the bottom, we can say, let artists = results.flatMap Artist,
-
5:44
using the json initializer, and we'll parsing the argument.
-
5:48
And then again, we'll call the completion handler and
-
5:51
pass through the artists or nil.
-
5:54
Okay, so we've refactored it out and
-
5:56
we've moved much of the logic that we had in searchForArtists into this new method.
-
6:00
So we can now use it everywhere.
-
6:03
So again back in the second method, I can copy paste this in,
-
6:07
right, so we don't need any of this, get rid of it.
-
6:13
And get rid of this artist stuff at the bottom, so this time, we made a mistake.
-
6:19
So instead of empty array and nil here this is empty array and error.
-
6:25
And then down here this is nil and an error.
-
6:31
And now, so this bit is the same.
-
6:33
So perform request with endpoint and
-
6:35
check the results object, that bit is the same in every case.
-
6:38
And now, we can move on to actually parsing that results and
-
6:42
returning an artist.
-
6:44
In this case, the first object in the results array
-
6:47
is a dictionary containing information about the artist.
-
6:50
So let's get that out and create an artist instance with it.
-
6:54
First we'll make sure the results array isn't empty and we'll do that by calling
-
6:58
the first property to get the dictionary or return an error.
-
7:01
So inside here, we'll say guard let artistInfo =
-
7:06
results.first else { Completion,
-
7:12
so obviously we have an error case, so no artist.
-
7:15
And then the error is .jsonParsingFailure.
-
7:18
And the message we wanna pass through is Results does
-
7:23
not contain artist info and then we'll return.
-
7:31
Once we have this artist info dictionary,
-
7:33
we can use it to create an artist instance.
-
7:36
Again, we need to use a guard statement here since the initializer is fallible.
-
7:40
So we'll say guard let artist equal, we'll used that
-
7:45
JSON initializer, pass in the artistInfo dictionary.
-
7:50
And if this doesn't work, then, again, we'll call the completion handler.
-
7:54
We'll pass nil through for the artist and then, we'll say .jsonParsingFailure and
-
8:00
the message here is Could not parse artist information.
-
8:07
This is an all that different from the data we had when we use the search
-
8:11
endpoint.
-
8:12
The difference in this method is that the results array contains all
-
8:16
the albums pertaining to the artist at index 1 all the way to the end.
-
8:20
So now, let's grab those dictionaries and
-
8:23
create individual album instances out of each of them.
-
8:27
So, let albumResults = results.
-
8:32
That is our array of dictionaries and we'll use subscript notation,
-
8:36
along with a range, to create an array slice, which is a portion of the array.
-
8:41
So remember in the results array for this endpoint,
-
8:44
the first dictionary in there contains information about the artist, but
-
8:49
everything from index 1 to the end is information about the album.
-
8:52
So we'll start at index 1 and we'll go all the way to the end, raising results.count.
-
8:58
And notice this is a half open range.
-
9:01
So we're not going to include this value, which would mean index out of bounds.
-
9:07
So now we have all the album dictionaries and we can create albums out of that
-
9:11
pretty easily by just parsing it with the flatMap function.
-
9:18
So album, we'll use the JSON initializer, and $0.
-
9:22
Again here we're using flatMap to end up with a non-option array of albums.
-
9:27
We can now assign these albums back to the stored property on the artist object and
-
9:32
call the success case of the completion handler.
-
9:35
So artist.albums = albums.
-
9:41
And then completion(artist, nil).
-
9:45
So let's test this out.
-
9:46
Let's head over to the prepare, actually what was that error, let me go back.
-
9:52
I forgot to return, which is always important.
-
9:55
Okay, so in the search results controller,
-
9:58
I want to go to this prepare for Segway method.
-
10:03
After we figure out what artist that use your tabbed on which is right here.
-
10:10
Let's get rid of this stub data and look up the artist using the API client.
-
10:15
So I'll get rid of this and I'll say client.lookup artist with ID.
-
10:20
And this is artist.id.
-
10:23
And the completion handler here returns either an artist or an error,
-
10:30
and inside this closure we'll assign this artist instance containing the albums
-
10:35
to the storic property on the destination controller and reload the table view.
-
10:40
So let's move this code up and here's a little magic trick.
-
10:46
You can hold down command option and then use the opening square bracket to kind of
-
10:50
move it through like that all the way to the top.
-
10:52
And then we're going to move this into
-
10:56
the closure like that and now,
-
11:01
we'll just use the album list controllers table view and call reload data on it.
-
11:07
Now, if we test this out, type an artist name and
-
11:10
select it, you're actually going to crash.
-
11:13
And this is a bit of intentional work on my part
-
11:16
to remind you never to trust force unwrapping.
-
11:19
If you go to the AlbumListController and look at the artist property, this
-
11:24
uses an explicitly unwrapped property, because while we needed it to be variable,
-
11:28
we didn't want to unwrap it all over the place when we created it.
-
11:32
We're crashing because we tried to assign a title to the view controller in here.
-
11:39
Using an object that is nil when the view loads.
-
11:42
Even if you fix this line of code, that wouldn't be good enough
-
11:46
because the data source is also now being initialized with a new variable.
-
11:51
Just because all of this worked in our stubbed code
-
11:54
doesn't mean it'll work the same when we use asynchronous networking code, so
-
11:58
let's make a few changes.
-
12:00
First, we're going to change this artist property to a standard optional.
-
12:05
Next, we're going to get rid of this lazily created data source and
-
12:09
make that a regular variable stored property.
-
12:13
So no closure anymore.
-
12:15
Instead, We'll initialize it with an instance
-
12:22
of AlbumListDataSource and we'll pass in an empty array for the albums.
-
12:28
This means that once we get the data, we'll need to update the data source.
-
12:33
Since the data source object's data is private,
-
12:35
however, we need to go back to this type, and at the bottom,
-
12:39
we need to add a helper method, func update, to update
-
12:43
that underlying data source with a new set of albums, which is an array of album.
-
12:48
We'll say self.albums = album.
-
12:54
Now, when we set the artist property, in prepare for
-
12:57
Segway, we'll want to set the title.
-
13:00
So let's go back there.
-
13:01
So the album was controller.
-
13:02
So when we set this property, we'll want to set the title, update the data source,
-
13:07
and reload the table view.
-
13:09
And we can do all that by using a did-set property observer on the property, itself.
-
13:15
So inside we'll say didSet and we can say self.title
-
13:20
= artist.title, we'll need a question mark.
-
13:26
We can also say dataSource.update(with: artist!.albums) and
-
13:33
then finally tableView.reloadData().
-
13:38
And we'll get rid of all this or rather just this line of code.
-
13:45
All right, so what's going on here.
-
13:47
Nope, this is artist I believe .name.
-
13:52
Now, let's give it a run.
-
13:55
What's going on here?
-
13:59
Where is this error?
-
14:00
Okay, no errors.
-
14:04
We need to change albums on the AlbumListDataSource to now be variable
-
14:09
since we're updating it at some point with new data.
-
14:12
All right, so just like before, we'll launch the search.
-
14:15
We'll type out the artist name.
-
14:18
We get a bunch of results,
-
14:19
we can click it and now you'll see all of their albums, pretty awesome.
-
14:25
We've got the search results page and the album's page sorted, but we still have to
-
14:29
configure the file view controller to display the album and it's songs.
-
14:33
We've done this twice now, so
-
14:35
you should have a good understanding of how to tackle it.
-
14:38
For the last view controller, I want you to handle it.
-
14:41
In the next video, I'm going to walk through the implementation myself, but
-
14:45
please try and not go to that video, until you've tried to figure it out yourself.
-
14:50
So that means writing the networking code,
-
14:53
updating the view controller with the real data, and
-
14:55
then changing any of the data source stuff that you need to get it to work.
You need to sign up for Treehouse in order to download course files.
Sign up