Alamofire is a Swift-based HTTP networking library for iOS and macOS. It provides an elegant interface on top of Apple’s Foundation networking stack that simplifies a number of common networking tasks.
Alamofire provides chainable request/response methods, JSON parameter and response serialization, authentication, and many other features.
In this Alamofire tutorial, you’ll use Alamofire to perform basic networking tasks like uploading files and requesting data from a third-party RESTful API.
Alamofire’s elegance comes from the fact it was written from the ground up in Swift and does not inherit anything from its Objective-C counterpart, AFNetworking.
You should have a conceptual understanding of HTTP networking and some exposure to Apple’s networking classes such as URLSession
.
While Alamofire does obscure some implementation details, it’s good to have some background knowledge if you ever need to troubleshoot your network requests.
Getting Started
Use the Download Materials button at the top or bottom of this tutorial to download the starter project.
The app for this Alamofire tutorial is named PhotoTagger. When complete, it will let you select an image from your library (or camera if you’re running on an actual device) and upload the image to a third-party service called Imagga. This service will perform some image recognition tasks to come up with a list of tags and primary colors for the image:
This project uses CocoaPods, so open it using the PhotoTagger.xcworkspace file.
Note:To learn more about CocoaPods, check out this tutorial by Joshua Greene, published right here on the site.
Build and run the project. You’ll see the following:
Click Select Photo and choose a photo. The background image will be replaced with the image you chose.
Open Main.storyboard and you’ll see the additional screens for displaying tags and colors have been added for you. All that remains is to upload the image and fetch the tags and colors.
The Imagga API
Imagga is an image recognition Platform-as-a-Service that provides image tagging APIs for developers and businesses to build scalable, image-intensive cloud apps. You can play around with a demo of their auto-tagging service here.
You’ll need to create a free developer account with Imagga for this Alamofire tutorial. Imagga requires an authorization header in each HTTP request so only people with an account can use their services. Go to https://imagga.com/auth/signup/hacker and fill out the form. After you create your account, check out the dashboard:
Listed down in the Authorization section is a secret token you’ll use later. You’ll need to include this information with every HTTP request as a header.
Note: Make sure you copy the whole secret token, be sure to scroll over to the right and verify you copied everything.
You’ll be using Imagga’s content endpoint to upload the photos, tagging endpoint for the image recognition and colors endpoint for color identification. You can read all about the Imagga API at http://docs.imagga.com.
REST, HTTP, JSON — What’s that?
If you’re coming to this tutorial with very little experience in using third-party services over the Internet, you might be wondering what all those acronyms mean! :]
HTTP is the application protocol, or set of rules, web sites use to transfer data from the web server to your screen. You’ve seen HTTP (or HTTPS) listed in the front of every URL you type into a web browser. You might have heard of other application protocols, such as FTP, Telnet, and SSH. HTTP defines several request methods, or verbs, the client (your web browser or app) use to indicate the desired action:
- 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 none of the actual data.
- POST: Sends data to the server, commonly used when filling a form and clicking submit.
- PUT: Sends data to the specific location provided.
- DELETE: Deletes data from the specific location provided.
REST, or REpresentational State Transfer, is a set of rules for designing consistent, easy-to-use and maintainable web APIs. REST has several architecture rules that enforce things such as not persisting states across requests, making requests cacheable, and providing uniform interfaces. This makes it easy for app developers like you to integrate the API into your app, without needing to track the state of data across requests.
JSON stands for JavaScript Object Notation. It provides a straightforward, human-readable and portable mechanism for transporting data between two systems. JSON has a limited number of data types: string, boolean, array, object/dictionary, null and number. There’s no distinction between integers and decimals.
There are a few native choices for converting your objects in memory to JSON and vice-versa: the good old JSONSerialization
class and the newly-added JSONEncoder
and JSONDecoder
classes. In addition, there are numerous third party libraries that help with handling JSON. You’ll use one of them, SwiftyJSON
in this tutorial.
The combination of HTTP, REST and JSON make up a good portion of the web services available to you as a developer. Trying to understand how every little piece works can be overwhelming. Libraries like Alamofire can help reduce the complexity of working with these services, and get you up and running faster than you could without their help.
What is Alamofire Good For?
Why do you need Alamofire at all? Apple already provides URLSession
and other classes for downloading content via HTTP, so why complicate things with another third party library?
The short answer is Alamofire is based on URLSession
, but it frees you from writing boilerplate code which makes writing networking code much easier. You can access data on the Internet with very little effort, and your code will be much cleaner and easier to read.
There are several major functions available with Alamofire:
Alamofire.upload
: Upload files with multipart, stream, file or data methods.Alamofire.download
: Download files or resume a download already in progress.Alamofire.request
: Every other HTTP request not associated with file transfers.
These Alamofire methods are global within Alamofire so you don’t have to instantiate a class to use them. There are underlying pieces to Alamofire that are 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.
Here’s an example of the same networking operation with both Apple’s URLSession
and Alamofire’s request
function:
// With URLSession
public func fetchAllRooms(completion: @escaping ([RemoteRoom]?) -> Void) {
guard let url = URL(string: "http://localhost:5984/rooms/_all_docs?include_docs=true") else {
completion(nil)
return
}
var urlRequest = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 10.0 * 1000)
urlRequest.httpMethod = "GET"
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
let task = urlSession.dataTask(with: urlRequest)
{ (data, response, error) -> Void in
guard error == nil else {
print("Error while fetching remote rooms: \(String(describing: error)")
completion(nil)
return
}
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
print("Nil data received from fetchAllRooms service")
completion(nil)
return
}
guard let rows = json?["rows"] as? [[String: Any]] else {
print("Malformed data received from fetchAllRooms service")
completion(nil)
return
}
let rooms = rows.flatMap { roomDict in return RemoteRoom(jsonData: roomDict) }
completion(rooms)
}
task.resume()
}
Versus:
// With Alamofire
func fetchAllRooms(completion: @escaping ([RemoteRoom]?) -> Void) {
guard let url = URL(string: "http://localhost:5984/rooms/_all_docs?include_docs=true") else {
completion(nil)
return
}
Alamofire.request(url,
method: .get,
parameters: ["include_docs": "true"])
.validate()
.responseJSON { response in
guard response.result.isSuccess else {
print("Error while fetching remote rooms: \(String(describing: response.result.error)")
completion(nil)
return
}
guard let value = response.result.value as? [String: Any],
let rows = value["rows"] as? [[String: Any]] else {
print("Malformed data received from fetchAllRooms service")
completion(nil)
return
}
let rooms = rows.flatMap { roomDict in return RemoteRoom(jsonData: roomDict) }
completion(rooms)
}
}
You can see the required setup for Alamofire is shorter and it’s much clearer what the function does. You deserialize the response with responseJSON(options:completionHandler:)
and calling validate()
to verify the response status code is in the default acceptable range between 200 and 299 simplifies error condition handling.
Now the theory is out of the way, it’s time to start using Alamofire.
Uploading Files
Open ViewController.swift and add the following to the top, below import SwiftyJSON
:
import Alamofire
This lets you use the functionality provided by the Alamofire module in your code, which you’ll be doing soon!
Next, go to imagePickerController(_:didFinishPickingMediaWithInfo:)
and add the following to the end, right before the call to dismiss(animated:)
:
// 1
takePictureButton.isHidden = true
progressView.progress = 0.0
progressView.isHidden = false
activityIndicatorView.startAnimating()
upload(image: image,
progressCompletion: { [weak self] percent in
// 2
self?.progressView.setProgress(percent, animated: true)
},
completion: { [weak self] tags, colors in
// 3
self?.takePictureButton.isHidden = false
self?.progressView.isHidden = true
self?.activityIndicatorView.stopAnimating()
self?.tags = tags
self?.colors = colors
// 4
self?.performSegue(withIdentifier: "ShowResults", sender: self)
})
Everything with Alamofire is asynchronous, which means you’ll update the UI in an asynchronous manner:
- Hide the upload button, and show the progress view and activity view.
- While the file uploads, you call the progress handler with an updated percent. This updates the progress indicator of the progress bar.
- The completion handler executes when the upload finishes. This sets the controls back to their original state.
- Finally the Storyboard advances to the results screen when the upload completes, successfully or not. The user interface doesn’t change based on the error condition.
Next, find upload(image:progressCompletion:completion:)
at the bottom of the file. It is currently only a method stub, so give it the following implementation:
func upload(image: UIImage,
progressCompletion: @escaping (_ percent: Float) -> Void,
completion: @escaping (_ tags: [String]?, _ colors: [PhotoColor]?) -> Void) {
// 1
guard let imageData = UIImageJPEGRepresentation(image, 0.5) else {
print("Could not get JPEG representation of UIImage")
return
}
// 2
Alamofire.upload(multipartFormData: { multipartFormData in
multipartFormData.append(imageData,
withName: "imagefile",
fileName: "image.jpg",
mimeType: "image/jpeg")
},
to: "http://api.imagga.com/v1/content",
headers: ["Authorization": "Basic xxx"],
encodingCompletion: { encodingResult in
})
}
Here’s what’s happening:
- The image that’s being uploaded needs to be converted to a
Data
instance. - Here you convert the JPEG data blob (
imageData
) into a MIME multipart request to send to the Imagga content endpoint.
Basic xxx
with the actual authorization header taken from the Imagga dashboard.Next, add the following to the encodingCompletion
closure:
switch encodingResult {
case .success(let upload, _, _):
upload.uploadProgress { progress in
progressCompletion(Float(progress.fractionCompleted))
}
upload.validate()
upload.responseJSON { response in
}
case .failure(let encodingError):
print(encodingError)
}
This chunk of code calls the Alamofire upload
function and passes in a small calculation to update the progress bar as the file uploads. It then validates the response has a status code in the default acceptable range between 200 and 299.
Note: Prior to Alamofire 4 it was not guaranteed progress callbacks were called on the main queue. Beginning with Alamofire 4, the new progress callback API is always called on the main queue.
Next, add the following code to the upload.responseJSON
closure:
// 1
guard response.result.isSuccess,
let value = response.result.value else {
print("Error while uploading file: \(String(describing: response.result.error))")
completion(nil, nil)
return
}
// 2
let firstFileID = JSON(value)["uploaded"][0]["id"].stringValue
print("Content uploaded with ID: \(firstFileID)")
//3
completion(nil, nil)
Here’s a step-by-step explanation of the above code:
- Check that the upload was successful, and the result has a value; if not, print the error and call the completion handler.
- Using SwiftyJSON, retrieve the
firstFileID
from the response. - Call the completion handler to update the UI. At this point, you don’t have any downloaded tags or colors, so simply call this with no data.
Note: Every response has a Result
enum with a value and type. Using automatic validation, the result is considered a success when it returns a valid HTTP Code between 200 and 299 and the Content Type is of a valid type specified in the Accept HTTP header field.
You can perform manual validation by adding .validate
options as shown below:
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"])
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { response in
// response handling code
}
The UI won't show an error if you hit an error during the upload; it merely returns no tags or colors to the user. This isn't the best user experience, but it's fine for this tutorial.
Build and run your project; select an image and watch the progress bar change as the file uploads. You should see a note like the following in your console when the upload completes:
Congratulations, you've successfully uploaded a file over the Interwebs!
Retrieving Data
The next step after uploading the image to Imagga is to fetch the tags Imagga produces after it analyzes the photo.
Add the following method to the ViewController
extension below upload(image:progress:completion:)
:
func downloadTags(contentID: String, completion: @escaping ([String]?) -> Void) {
// 1
Alamofire.request("http://api.imagga.com/v1/tagging",
parameters: ["content": contentID],
headers: ["Authorization": "Basic xxx"])
// 2
.responseJSON { response in
guard response.result.isSuccess,
let value = response.result.value else {
print("Error while fetching tags: \(String(describing: response.result.error))")
completion(nil)
return
}
// 3
let tags = JSON(value)["results"][0]["tags"].array?.map { json in
json["tag"].stringValue
}
// 4
completion(tags)
}
}
Here's a step-by-step explanation of the above code:
- Perform an HTTP GET request against the tagging endpoint, sending the URL parameter
content
with the ID you received after the upload. Again, be sure to replaceBasic xxx
with your actual authorization header. - Check that the response was successful, and the result has a value; if not, print the error and call the completion handler.
- Using SwiftyJSON, retrieve the raw
tags
array from the response. Iterate over each dictionary object in thetags
array, retrieving the value associated with thetag
key. - Call the completion handler passing in the
tags
received from the service.
Next, go back to upload(image:progress:completion:)
and replace the call to the completion handler in the success condition with the following:
self.downloadTags(contentID: firstFileID) { tags in
completion(tags, nil)
}
This simply sends along the tags to the completion handler.
Build and run your project; select a photo and you should see something similar to the following appear:
Pretty slick! That Imagga is one smart API. :] Next, you'll fetch the colors of the image.
Add the following method to the ViewController
extension below downloadTags(contentID:completion:)
:
func downloadColors(contentID: String, completion: @escaping ([PhotoColor]?) -> Void) {
// 1.
Alamofire.request("http://api.imagga.com/v1/colors",
parameters: ["content": contentID],
headers: ["Authorization": "Basic xxx"])
.responseJSON { response in
// 2
guard response.result.isSuccess,
let value = response.result.value else {
print("Error while fetching colors: \(String(describing: response.result.error))")
completion(nil)
return
}
// 3
let photoColors = JSON(value)["results"][0]["info"]["image_colors"].array?.map { json in
PhotoColor(red: json["r"].intValue,
green: json["g"].intValue,
blue: json["b"].intValue,
colorName: json["closest_palette_color"].stringValue)
}
// 4
completion(photoColors)
}
}
Taking each numbered comment in turn:
- Perform an HTTP GET request against the colors endpoint, sending the URL parameter
content
with the ID you received after the upload. Again, be sure to replaceBasic xxx
with your actual authorization header. - Check that the response was successful, and the result has a value; if not, print the error and call the completion handler.
- Using SwiftyJSON, retrieve the
image_colors
array from the response. Iterate over each dictionary object in theimage_colors
array, and transform it into aPhotoColor
object. This object pairs colors in the RGB format with the color name as a string. - Call the completion handler, passing in the
photoColors
from the service.
Finally, go back to upload(image:progress:completion:)
and replace the call to downloadTags(contentID:)
in the success condition with the following:
self.downloadTags(contentID: firstFileID) { tags in
self.downloadColors(contentID: firstFileID) { colors in
completion(tags, colors)
}
}
This nests the operations of uploading the image, downloading tags and downloading colors.
Build and run your project again; this time, you should see the returned color tags when you select the Colors button:
This uses the RGB colors you mapped to PhotoColor
structs to change the background color of the view. You've now successfully uploaded an image to Imagga and fetched data from two different endpoints. You've come a long way, but there's some room for improvement in how you're using Alamofire in PhotoTagger.
Improving PhotoTagger
You probably noticed some repeated code in PhotoTagger. If Imagga released v2 of their API and deprecated v1, PhotoTagger would no longer function and you'd have to update the URL in each of the three methods. Similarly, if your authorization token changed you'd be updating it all over the place.
Alamofire provides a simple method to eliminate this code duplication and provide centralized configuration. The technique involves creating a struct conforming to URLRequestConvertible
and updating your upload and request calls.
Create a new Swift file by clicking File\New\File... and selecting Swift file under iOS. Click Next, name the file ImaggaRouter.swift, select the Group PhotoTagger with the yellow folder icon and click Create.
Add the following to your new file:
import Alamofire
public enum ImaggaRouter: URLRequestConvertible {
// 1
enum Constants {
static let baseURLPath = "http://api.imagga.com/v1"
static let authenticationToken = "Basic xxx"
}
// 2
case content
case tags(String)
case colors(String)
// 3
var method: HTTPMethod {
switch self {
case .content:
return .post
case .tags, .colors:
return .get
}
}
// 4
var path: String {
switch self {
case .content:
return "/content"
case .tags:
return "/tagging"
case .colors:
return "/colors"
}
}
// 5
var parameters: [String: Any] {
switch self {
case .tags(let contentID):
return ["content": contentID]
case .colors(let contentID):
return ["content": contentID, "extract_object_colors": 0]
default:
return [:]
}
}
// 6
public func asURLRequest() throws -> URLRequest {
let url = try Constants.baseURLPath.asURL()
var request = URLRequest(url: url.appendingPathComponent(path))
request.httpMethod = method.rawValue
request.setValue(Constants.authenticationToken, forHTTPHeaderField: "Authorization")
request.timeoutInterval = TimeInterval(10 * 1000)
return try URLEncoding.default.encode(request, with: parameters)
}
}
Here's a step-by-step explanation of the above code:
- Declare constants to hold the Imagga base URL and your
Basic xxx with your actual authorization header. - Declare the enum cases. Each case corresponds to an api endpoint.
- Return the
HTTP method
for each api endpoint. - Return the
path
for each api endpoint. - Return the
parameters
for each api endpoint. - Use all of the above components to create a
URLRequest
for the requested endpoint.
Now all your boilerplate code is in single place, should you ever need to update it.
Go back to ViewController.swift and in upload(image:progress:completion:)
replace:
Alamofire.upload(
multipartFormData: { multipartFormData in
multipartFormData.append(imageData,
withName: "imagefile",
fileName: "image.jpg",
mimeType: "image/jpeg")
},
to: "http://api.imagga.com/v1/content",
headers: ["Authorization": "Basic xxx"],
with the following:
Alamofire.upload(multipartFormData: { multipartFormData in
multipartFormData.append(imageData,
withName: "imagefile",
fileName: "image.jpg",
mimeType: "image/jpeg")
},
with: ImaggaRouter.content,
Next replace the call for Alamofire.request
in downloadTags(contentID:completion:)
with:
Alamofire.request(ImaggaRouter.tags(contentID))
Finally, update the call to Alamofire.request
in downloadColors(contentID:completion:)
with:
Alamofire.request(ImaggaRouter.colors(contentID))
responseJSON
handlers in place for both of the previous edits.Build and run for the final time; everything should function just as before, which means you've refactored everything without breaking your app. However, you don't have to go through your entire source code if anything on the Imagga integration ever changes: APIs, your authorization token, parameters, etc. Awesome job!
Where To Go From Here?
You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial. Don't forget to replace your authorization token as appropriate!
This tutorial 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.
Also, you can take some time to learn more about Apple's URLSession
which Alamofire uses under the hood:
- Apple WWDC 2015 - 711 - Networking with NSURLSession
- Apple URL Session Programming Guide
- Ray Wenderlich - NSURLSession Tutorial
Please share any comments or questions about this tutorial in the forum discussion below!
The post Alamofire Tutorial: Getting Started appeared first on Ray Wenderlich.