Alamofire
asynchronous
completionHandler
JSON request
Swift programming

AlamoFire asynchronous completionHandler for JSON request

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Alamofire requests are asynchronous by default, which means the network call starts now and the result arrives later through a completion handler. In modern Swift code, the most important decision is not whether the request is asynchronous, but how you want to decode the JSON and how you want to surface success and failure back to the rest of the app.

The Basic Pattern

With Alamofire, a request typically looks like this:

swift
1import Alamofire
2
3AF.request("https://api.example.com/users/1")
4    .validate()
5    .responseData { response in
6        switch response.result {
7        case .success(let data):
8            print("Received \(data.count) bytes")
9        case .failure(let error):
10            print("Request failed: \(error)")
11        }
12    }

The closure passed to responseData is the completion handler. It runs after the request finishes and gives you a Result that is either:

  • '.success with the payload'
  • '.failure with an AFError'

That means any code depending on the server response must live inside the completion path or be called from it.

Prefer Decodable for Typed JSON

For modern Alamofire code, strongly typed decoding is usually better than grabbing raw JSON dictionaries. A Decodable model makes the response contract clearer and avoids repeated casting.

swift
1import Alamofire
2
3struct User: Decodable {
4    let id: Int
5    let name: String
6    let email: String
7}
8
9func fetchUser(completion: @escaping (Result<User, AFError>) -> Void) {
10    AF.request("https://api.example.com/users/1")
11        .validate()
12        .responseDecodable(of: User.self) { response in
13            completion(response.result)
14        }
15}

Using the function:

swift
1fetchUser { result in
2    switch result {
3    case .success(let user):
4        print("Loaded user: \(user.name)")
5    case .failure(let error):
6        print("Load failed: \(error)")
7    }
8}

This keeps the networking layer small and lets higher-level code react through one clean completion handler.

If You Really Need Raw JSON

Sometimes you do not have a stable response model yet, or the payload is too dynamic for Decodable to be the right first step. In that case, request raw data and decode it yourself.

swift
1import Alamofire
2
3AF.request("https://api.example.com/settings")
4    .validate()
5    .responseData { response in
6        switch response.result {
7        case .success(let data):
8            do {
9                let json = try JSONSerialization.jsonObject(with: data)
10                print(json)
11            } catch {
12                print("Invalid JSON: \(error)")
13            }
14        case .failure(let error):
15            print("Network error: \(error)")
16        }
17    }

This is more flexible, but it also pushes parsing errors and type checking back onto your code.

Wrap Alamofire in Your Own API

One of the cleanest ways to use completion handlers is to hide Alamofire behind a small service object. That keeps controllers and views from turning into long networking blocks.

swift
1import Alamofire
2
3final class UserService {
4    func fetchUsers(completion: @escaping (Result<[User], AFError>) -> Void) {
5        AF.request("https://api.example.com/users")
6            .validate()
7            .responseDecodable(of: [User].self) { response in
8                completion(response.result)
9            }
10    }
11}

Now a view controller can stay focused on UI behavior:

swift
1let service = UserService()
2
3service.fetchUsers { result in
4    switch result {
5    case .success(let users):
6        print("Loaded \(users.count) users")
7    case .failure(let error):
8        print("Failed: \(error.localizedDescription)")
9    }
10}

This is easier to test and easier to refactor later than scattering raw request code throughout the app.

Validation and Error Handling Matter

Without validate(), a request with an HTTP 404 or 500 can still arrive at the success side if the transport itself succeeded. For most JSON APIs, you want status validation first so the completion handler reflects application-level success more accurately.

You can also inspect the status code when needed:

swift
1AF.request("https://api.example.com/users/999")
2    .responseData { response in
3        let statusCode = response.response?.statusCode
4        print("Status: \(String(describing: statusCode))")
5    }

That is useful when error behavior depends on specific server responses such as unauthorized or not found.

Completion Handlers and the Main Thread

The network work itself is asynchronous, but the UI still needs to be updated on the main thread. If your completion handler updates labels, table views, or view models that assume main-thread usage, make that explicit.

Alamofire already integrates cleanly with app code, but you should still be deliberate about where UI work happens and where parsing or business logic lives.

Common Pitfalls

  • Trying to return the JSON value directly from the request function instead of using the completion handler.
  • Using old raw-JSON patterns everywhere when Decodable would make the response safer and clearer.
  • Skipping validate() and then treating HTTP error responses as successful requests.
  • Writing large controller methods full of networking and parsing logic instead of wrapping Alamofire in a service layer.
  • Mixing UI updates and background parsing logic without being clear about the thread that owns the UI.

Summary

  • Alamofire JSON requests are asynchronous, so dependent logic must flow through a completion handler.
  • 'responseDecodable is usually the cleanest modern choice for JSON APIs with stable schemas.'
  • Use responseData and manual decoding only when you genuinely need raw or highly dynamic JSON.
  • Wrap requests in small service methods so the rest of the app sees a clean Result-based API.
  • Add validation and explicit error handling so the completion path reflects real API success and failure.

Course illustration
Course illustration

All Rights Reserved.