内容简介:If you’ve been developing iOS apps for some time, you’ve probably needed to access data over the network. And for that you may have used Foundation’sAlamofire is a Swift-based, HTTP networking library. It provides an elegant interface on top of Apple’s Fou
If you’ve been developing iOS apps for some time, you’ve probably needed to access data over the network. And for that you may have used Foundation’s URLSession
. This is fine and all, but sometimes it becomes cumbersome to use. And that’s where this Alamofire tutorial comes in!
Alamofire is a Swift-based, HTTP networking library. It provides an elegant interface on top of Apple’s Foundation networking stack that simplifies common networking tasks. Its features include chainable request/response methods, JSON and Codable decoding, authentication and more.
In this Alamofire tutorial, you’ll perform basic networking tasks including:
- Requesting data from a third-party RESTful API.
- Sending request parameters.
- Converting the response into JSON.
- Converting the response into a Swift data model via the Codable protocol.
Note : Before starting this tutorial, you should have a conceptual understanding of HTTP networking. Some exposure to Apple’s networking classes is helpful, but not necessary. Alamofire obscures implementation details, but it’s good to have some background knowledge if you need to troubleshoot your network requests.
Getting Started
To kick things off, use the Download Materials button at the top or bottom of this article to download the begin project.
The app for this tutorial is StarWarsOpedia , which provides quick access to data about Star Wars films as well as the starships used in those films.
Start by opening StarWarsOpedia.xcworkspace inside the begin project.
Build and run. You’ll see this:
It’s a blank slate now, but you’ll populate it with data soon!
Note : You’d normally integrate Alamofire using CocoaPods or another dependency manager. In this case, it’s pre-installed in your downloaded projects. For help integrating Alamofire into your projects using CocoaPods, see CocoaPods Tutorial for Swift: Getting Started .
Using the SW API
SW API is a free and open API that provides Star Wars data. It’s only updated periodically, but it’s a fun way to get to know Alamofire. Access the API at swapi.co .
There are multiple endpoints to access specific data, but you’ll concentrate on https://swapi.co/api/films and https://swapi.co/api/starships .
For more information, explore the Swapi documentation .
Understanding HTTP, REST and JSON
If you’re new to accessing third-party services over the internet, this quick explanation will help.
HTTP is an application protocol used to transfer data from a server to a client, such as a web browser or an iOS app. HTTP defines several request methods that the client uses to indicate the desired action. For example:
- GET : Retrieves data, such as a web page, but doesn’t alter any data on the server.
- HEAD : Identical to GET, but only sends back the headers and not the actual data.
- POST : Sends data to the server. Use this, for example, when filling a form and clicking submit.
- PUT : Sends data to the specific location provided. Use this, for example, when updating a user’s profile.
- DELETE : Deletes data from the specific location provided.
JSON stands for JavaScript Object Notation. It provides a straightforward, human-readable and portable mechanism for transporting data between systems. JSON has a limited number of data types to choose from: string, boolean, array, object/dictionary, number and null.
Back in the dark days of Swift, pre-Swift 4, you needed to use the JSONSerialization
class to convert JSON to data objects and vice-versa.
It worked well and you can still use it today, but there’s a better way now: Codable
. By conforming your data models to Codable
, you get nearly automatic conversion from JSON to your data models and back.
REST , or REpresentational State Transfer, is a set of rules for designing consistent web APIs. REST has several architecture rules that enforce standards like not persisting states across requests, making requests cacheable and providing uniform interfaces. This makes it easy for app developers to integrate the API into their apps without having to track the state of data across requests.
HTTP, JSON and REST comprise a good portion of the web services available to you as a developer. Trying to understand how every piece works can be overwhelming. That’s where Alamofire comes in.
Why Use Alamofire?
You may be wondering why you should use Alamofire. Apple already provides URLSession
and other classes for accessing content via HTTP, so why add another dependency to your code base?
The short answer is that while Alamofire is based on URLSession, it obscures many of the difficulties of making networking calls, freeing you to concentrate on your business logic. You can access data on the internet with little effort, and your code will be cleaner and easier to read.
There are several major functions available with Alamofire:
- AF.upload : Upload files with multi-part, stream, file or data methods.
- AF.download : Download files or resume a download already in progress.
- AF.request : Other HTTP requests not associated with file transfers.
These Alamofire methods are global, so you don’t have to instantiate a class to use them. Underlying Alamofire elements include classes and structs like SessionManager
, DataRequest
and DataResponse
. However, you don’t need to fully understand the entire structure of Alamofire to start using it.
Enough theory. It’s time to start writing code!
Requesting Data
Before you can start making your awesome app, you need to do some setup.
Start by opening MainTableViewController.swift . Under import UIKit
, add the following:
import Alamofire
This allows you to use Alamofire in this view controller. At the bottom of the file, add:
extension MainTableViewController { func fetchFilms() { // 1 let request = AF.request("https://swapi.co/api/films") // 2 request.responseJSON { (data) in print(data) } } }
Here’s what’s happening with this code:
- Alamofire uses namespacing , so you need to prefix all calls that you use with
AF
.request(_:method:parameters:encoding:headers:interceptor:)
accepts the endpoint for your data. It can accept more parameters, but for now, you’ll just send the URL as a string and use the default parameter values. - Take the response given from the request as JSON. For now, you simply print the JSON data for debugging purposes.
Finally, at the end of viewDidLoad()
, add:
fetchFilms()
This triggers the Alamofire request you just implemented.
Build and run. At the top of the console, you’ll see something like this:
success({ count = 7; next = "<null>"; previous = "<null>"; results = ({...}) })
In a few very simple lines, you’ve fetched JSON data from a server. Good job!
Using a Codable Data Model
But, how do you work with the JSON data returned? Working with JSON directly can be messy due to its nested structure, so to help with that, you’ll create models to store your data.
In the Project navigator, find the Networking group and create a new Swift file in that group named Film.swift .
Then, add the following code to it:
struct Film: Decodable { let id: Int let title: String let openingCrawl: String let director: String let producer: String let releaseDate: String let starships: [String] enum CodingKeys: String, CodingKey { case id = "episode_id" case title case openingCrawl = "opening_crawl" case director case producer case releaseDate = "release_date" case starships } }
With this code, you’ve created the data properties and coding keys you need to pull data from the API’s film endpoint. Note how the struct is Decodable
, which makes it possible to turn JSON into the data model.
The project defines a protocol — Displayable
— to simplify showing detailed information later in the tutorial. You must make Film
conform to it. Add the following at the end of Film.swift :
extension Film: Displayable { var titleLabelText: String { title } var subtitleLabelText: String { "Episode \(String(id))" } var item1: (label: String, value: String) { ("DIRECTOR", director) } var item2: (label: String, value: String) { ("PRODUCER", producer) } var item3: (label: String, value: String) { ("RELEASE DATE", releaseDate) } var listTitle: String { "STARSHIPS" } var listItems: [String] { starships } }
This extension allows the detailed information display’s view controller to get the correct labels and values for a film from the model itself.
In the Networking group, create a new Swift file named Films.swift .
Add the following code to the file:
struct Films: Decodable { let count: Int let all: [Film] enum CodingKeys: String, CodingKey { case count case all = "results" } }
This struct denotes a collection of films. As you previously saw in the console, the endpoint swapi.co/api/films returns four main values: count
, next
, previous
and results
. For your app, you only need count
and results
, which is why your struct doesn’t have all properties.
The coding keys transform results
from the server into all
. This is because Films.results
doesn’t read as nicely as Films.all
. Again, by conforming the data model to Decodable
, Alamofire will be able to convert the JSON data into your data model.
Note : For more information on Codable
, see our tutorial on Encoding and Decoding in Swift .
Back in MainTableViewController.swift , in fetchFilms()
, replace:
request.responseJSON { (data) in print(data) }
With the following:
request.responseDecodable(of: Films.self) { (response) in guard let films = response.value else { return } print(films.all[0].title) }
Now, rather than converting the response into JSON, you’ll convert it into your internal data model, Films
. For debugging purposes, you print the title of the first film retrieved.
Build and run. In the Xcode console, you’ll see the name of the first film in the array. Your next task is to display the full list of movies.
Method Chaining
Alamofire uses method chaining , which works by connecting the response of one method as the input of another. This not only keeps the code compact, but it also makes your code clearer.
Give it a try now by replacing all of the code in fetchFilms()
with:
AF.request("https://swapi.co/api/films") .validate() .responseDecodable(of: Films.self) { (response) in guard let films = response.value else { return } print(films.all[0].title) }
This single line not only does exactly what took multiple lines to do before, but you also added validation.
From top to bottom, you request the endpoint, validate the response by ensuring the response returned an HTTP status code in the range 200–299 and decode the response into your data model. Nice! :]
Setting up Your Table View
Now, at the top of MainTableViewController
, add the following:
var items: [Displayable] = []
You’ll use this property to store the array of information you get back from the server. For now, it’s an array of films but there’s more coolness coming soon! In fetchFilms()
, replace:
print(films.all[0].title)
With:
self.items = films.all self.tableView.reloadData()
This assigns all retrieved films to items
and reloads the table view.
To get the table view to show the content, you must make some further changes. Replace the code in tableView(_:numberOfRowsInSection:)
with:
return items.count
This ensures that you show as many cells as there are films.
Next, in tableView(_:cellForRowAt:)
right below the declaration of cell
, add the following lines:
let item = items[indexPath.row] cell.textLabel?.text = item.titleLabelText cell.detailTextLabel?.text = item.subtitleLabelText
Here, you set up the cell with the film name and episode ID, using the properties provided via Displayable
.
Build and run. You’ll see a list of films:
Now you’re getting somewhere! You’re pulling data from a server, decoding it into an internal data model, assigning that model to a property in the view controller and using that property to populate a table view.
But, as wonderful as that is, there’s a small problem: When you tap one of the cells, you go to a detail view controller which isn’t updating properly. You’ll fix that next.
Updating the Detail View Controller
First, you’ll register the selected item. Under var items: [Displayable] = []
, add:
var selectedItem: Displayable?
You’ll store the currently-selected film to this property.
Now, replace the code in tableView(_:willSelectRowAt:)
with:
selectedItem = items[indexPath.row] return indexPath
Here, you’re taking the film from the selected row and saving it to selectedItem
.
Now, in prepare(for:sender:)
, replace:
destinationVC.data = nil
With:
destinationVC.data = selectedItem
This sets the user’s selection as the data to display.
Build and run. Tap any of the films. You should see a detail view that is mostly complete.
Fetching Multiple Asynchronous Endpoints
Up to this point, you’ve only requested films endpoint data, which returns an array of film data in a single request.
If you look at Film
, you’ll see starships
, which is of type [String]
. This property does not contain all of the starship data, but rather an array of endpoints to the starship data. This is a common pattern programmers use to provide access to data without providing more data than necessary.
For example, imagine that you never tap “The Phantom Menace” because, you know, Jar Jar. It’s a waste of resources and bandwidth for the server to send all of the starship data for “The Phantom Menace” because you may not use it. Instead, the server sends you a list of endpoints for each starship so that if you want the starship data, you can fetch it.
Creating a Data Model for Starships
Before fetching any starships, you first need a new data model to handle the starship data. Your next step is to create one.
In the Networking group, add a new Swift file. Name it Starship.swift and add the following code:
struct Starship: Decodable { var name: String var model: String var manufacturer: String var cost: String var length: String var maximumSpeed: String var crewTotal: String var passengerTotal: String var cargoCapacity: String var consumables: String var hyperdriveRating: String var starshipClass: String var films: [String] enum CodingKeys: String, CodingKey { case name case model case manufacturer case cost = "cost_in_credits" case length case maximumSpeed = "max_atmosphering_speed" case crewTotal = "crew" case passengerTotal = "passengers" case cargoCapacity = "cargo_capacity" case consumables case hyperdriveRating = "hyperdrive_rating" case starshipClass = "starship_class" case films } }
As with the other data models, you simply list all the response data you want to use, along with any relevant coding keys.
You also want to be able to display information about individual ships, so Starship
must conform to Displayable
. Add the following at the end of the file:
extension Starship: Displayable { var titleLabelText: String { name } var subtitleLabelText: String { model } var item1: (label: String, value: String) { ("MANUFACTURER", manufacturer) } var item2: (label: String, value: String) { ("CLASS", starshipClass) } var item3: (label: String, value: String) { ("HYPERDRIVE RATING", hyperdriveRating) } var listTitle: String { "FILMS" } var listItems: [String] { films } }
Just like you did with Film
before, this extension allows DetailViewController
to get the correct labels and values from the model itself.
Fetching the Starship Data
To fetch the starship data, you’ll need a new networking call. Open DetailViewController.swift and add the following import statement to the top:
import Alamofire
Then at the bottom of the file, add:
extension DetailViewController { // 1 private func fetch<T: Decodable & Displayable>(_ list: [String], of: T.Type) { var items: [T] = [] // 2 let fetchGroup = DispatchGroup() // 3 list.forEach { (url) in // 4 fetchGroup.enter() // 5 AF.request(url).validate().responseDecodable(of: T.self) { (response) in if let value = response.value { items.append(value) } // 6 fetchGroup.leave() } } fetchGroup.notify(queue: .main) { self.listData = items self.listTableView.reloadData() } } }
Here is what’s happening in this code:
- You may have noticed that
Starship
contains a list of films, which you’ll want to display. Since bothFilm
andStarship
areDisplayable
, you can write a generic helper to perform the network request. It needs only to know the type of item its fetching so it can properly decode the result. - You need to make multiple calls, one per list item, and these calls will be asynchronous and may return out of order. To handle them, you use a dispatch group so you’re notified when all the calls have completed.
- Loop through each item in the list.
- Inform the dispatch group that you are entering.
- Make an Alamofire request to the starship endpoint, validate the response, and decode the response into an item of the appropriate type.
- In the request’s completion handler, inform the dispatch group that you’re leaving.
- Once the dispatch group has received a
leave()
for eachenter()
, you ensure you’re running on the main queue, save the list tolistData
and reload the list table view.
Note : You fetch list items asynchronously to allow the total request to finish faster. Imagine you had 100 starships in a movie and had to fetch only one at a time, synchronously. If each request took 100ms, you would have to wait for 10 seconds to fetch all starships! Fetching more starships at the same time greatly reduces this.
Now that you have your helper built, you need to actually fetch the list of starships from a film. Add the following inside your extension:
func fetchList() { // 1 guard let data = data else { return } // 2 switch data { case is Film: fetch(data.listItems, of: Starship.self) default: print("Unknown type: ", String(describing: type(of: data))) } }
Here’s what this does:
- Since
data
is optional, ensure it’s notnil
before doing anything else. - Use the type of
data
to decide how to invoke your helper method. - If the data is a
Film
, the associated list is of starships.
Now that you’re able to fetch the starships, you need to be able to display it in your app. That’s what you’ll do in your next step.
Updating Your Table View
In tableView(_:cellForRowAt:)
, add the following before return cell
:
cell.textLabel?.text = listData[indexPath.row].titleLabelText
This code sets the cell’s textLabel
with the appropriate title from your list data.
Finally, add the following at the end of viewDidLoad()
:
fetchList()
Build and run, then tap any film. You’ll see a detail view that’s fully populated with film data and starship data. Neat, right?
The app is starting to look pretty solid. However, look at the main view controller and notice that there’s a search bar that isn’t working. You want to be able to search for starships by name or model, and you’ll tackle that next.
Sending Parameters With a Request
For the search to work, you need a list of the starships that match the search criteria. To accomplish this, you need to send the search criteria to the endpoint for getting starships.
Earlier, you used the films’ endpoint, https://swapi.co/api/films , to get the list of films. You can also get a list of all starships with the https://swapi.co/api/starships endpoint.
Take a look at the endpoint, and you’ll see a response similar to the film’s response:
success({ count = 37; next = "<null>"; previous = "<null>"; results = ({...}) })
The only difference is that this time, the results data is a list of all starships.
Alamofire’s request
can accept more than just the URL string that you’ve sent so far. It can also accept an array of key/value pairs as parameters.
The swapi.co API allows you to send parameters to the starships endpoint to perform a search. To do this, you use a key of search
with the search criteria as the value.
But before you dive into that, you need to set up a new model called Starships
so that you can decode the response just like you do with the other responses.
Decoding Starships
Create a new Swift file in the Networking group. Name it Starships.swift and enter the following code:
struct Starships: Decodable { var count: Int var all: [Starship] enum CodingKeys: String, CodingKey { case count case all = "results" } }
Like with Films
you only care about count
and results
.
Next, open MainTableViewController.swift and, after fetchFilms()
, add the following method for searching for starships:
func searchStarships(for name: String) { // 1 let url = "https://swapi.co/api/starships" // 2 let parameters: [String: String] = ["search": name] // 3 AF.request(url, parameters: parameters) .validate() .responseDecodable(of: Starships.self) { response in // 4 guard let starships = response.value else { return } self.items = starships.all self.tableView.reloadData() } }
This method does the following:
- Sets the URL that you’ll use to access the starship data.
- Sets the key-value parameters that you’ll send to the endpoint.
- Here, you’re making a request like before, but this time you’ve added parameters. You’re also performing a
validate
and decoding the response intoStarships
. - Finally, once the request completes, you assign the list of starships as the table view’s data and reload the table view.
Executing this request results in a URL https://swapi.co/api/starships?search={name}
where {name}
is the search query passed in.
Searching for Ships
Start by adding the following code to searchBarSearchButtonClicked(_:)
:
guard let shipName = searchBar.text else { return } searchStarships(for: shipName)
This code gets the text typed into the search bar and calls the new searchStarships(for:)
method you just implemented.
When the user cancels a search, you want to redisplay the list of films. You could fetch it again from the API, but that’s a poor design practice. Instead, you’re going to cache the list of films to make displaying it again quick and efficient. Add the following property at the top of the class to cache the list of films:
var films: [Film] = []
Next, add the following code after the guard
statement in fetchFilms()
:
self.films = films.all
This saves away the list for films for easy access later.
Now, add the following code to searchBarCancelButtonClicked(_:)
:
searchBar.text = nil searchBar.resignFirstResponder() items = films tableView.reloadData()
Here, you remove any search text entered, hide the keyboard using resignFirstResponder()
and reload the table view, which causes it to show films again.
Build and run. Search for wing . You’ll see all the ships with the word “wing” in their name or model.
That’s great! But, it’s not quite complete. If you tap one of the ships, the list of films that ship appears in is empty. This is easy to fix thanks to all the work you did before. There’s even a huge hint in the debug console!
Display a Ship’s List of Films
Open DetailViewController.swift and find fetchList()
. Right now, it only knows how to fetch the list associated with a film. You need to fetch the list for a starship. Add the following just before the default:
label in the switch
statement:
case is Starship: fetch(data.listItems, of: Film.self)
This tells your generic helper to fetch a list of films for a given starship.
Build and run. Search for a starship. Select it. You’ll see the starship details and the list of films it appeared in.
You now have a fully functioning app! Congratulations.
Where to Go From Here?
You can download the completed project using the Download Materials button at the top or bottom of this article.
While building your app, you’ve learned a lot about Alamofire’s basics. You learned that Alamofire can make networking calls with very little setup and how to make basic calls using the request function by sending just the URL string.
Also, you learned to make more complex calls to do things like searching by sending parameters.
You learned how to use request chaining and request validation, how to convert the response into JSON and how to convert the response data into a custom data model.
This article covered the very basics. You can take a deeper dive by looking at the documentation on the Alamofire site at https://github.com/Alamofire/Alamofire .
I highly suggest learning more about Apple’s URLSession, which Alamofire uses under the hood:
I hope you enjoyed this tutorial. Please share any comments or questions about this article in the forum discussion below!
以上所述就是小编给大家介绍的《Alamofire 5 Tutorial for iOS: Getting Started [FREE]》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。