[Swift] Using Combine + Moya + Alamofire to Handle Request and Response

Jongchan Ko
5 min readDec 8, 2023

--

1. Introduction

In the world of iOS development, networking is an essential part of almost every application. Quickly fetching data, processing it appropriately, and delivering a better user experience are key factors in the success of mobile apps. In this context, it’s necessary to understand various networking solutions and how they interact with each other. In this article, we will discuss how to build a more robust and manageable networking layer through the combination of Moya, Alamofire, and Combine.

1) Alamofire

First, “Alamofire” is an HTTP networking library written for Swift. This library offers various features including support for standard HTTP methods, serialization of data formats like JSON and XML, and monitoring of network communication status. While powerful on its own, sometimes a higher level of abstraction is needed.

Alamofire Official Documentation
“Combine” is a reactive programming framework for Swift provided by Apple. It helps manage data processing and response, and user interface updates as streams, reducing complexity and improving readability and maintainability.

2) Combine

Basic use of Combine
Now let’s explain why “Moya” comes into play. Moya is a network abstraction library that provides a higher level of abstraction on top of Alamofire. It allows developers to handle the networking layer in a more abstract way, focusing on more intuitive API calls rather than actual HTTP methods.

3) Moya

How to use Moya and its advantages
By using Moya, we can define API requests in a clearer and more manageable way without manually configuring raw HTTP requests for specific endpoints. Additionally, when used with Combine, it simplifies the management of asynchronous task flows, making error handling and data binding more convenient.

In this article, we will look at specific use cases of Moya, showing how to combine the powerful features of Alamofire with the reactive programming style of Combine. This will help in managing network calls more succinctly and intuitively, aiding in the development of robust applications that offer a better user experience.

Why Use Moya? A Translation from the Official Documentation

Reasons why a temporary network layer is often not ideal in iOS apps:

  • It makes writing new apps difficult
  • It complicates the maintenance of existing apps
  • It makes writing unit tests difficult
  • Therefore, Moya’s fundamental idea is that we want a network abstraction layer that sufficiently encapsulates direct calls to Alamofire. It should be comprehensive enough to make not only common tasks easy but also complex tasks manageable.

What if we use Alamofire to abstract the URL session, abstracting the nitty-gritty like URLs, parameters, etc.?Some amazing features of Moya include:

  • Ensuring correct API endpoint access at compile time.
  • Defining clear usage of multiple endpoints with associated enumeration values.
  • Treating test steps individually, making unit testing very easy.

Here’s an Example Code: (For detailed explanation, please check the comments in the code)

The API was obtained and used from https://apifootball.com.

We used a structure called APIManager for network access.

import Foundation
import Combine
import CombineMoya
import Moya

struct APIManager{

static var cancelable = Set<AnyCancellable>()

static func requestPremierLeague() -> AnyPublisher<[TeamStat] , ErrorModel>{
Future<[TeamStat], ErrorModel> {promise in
let apis: ApisTeam = .premierLeague
let provider = MoyaProvider<ApisTeam>()
provider.requestPublisher(apis)
.sink(receiveCompletion: { completion in
switch completion{
case .finished:
print("RECEIVE VALUE COMPLETED")
case .failure:
print("RECEIVE VALUE FAILED")
}
}, receiveValue: { response in
let result = try? JSONDecoder().decode([TeamStat].self, from: response.data)
promise(.success(result!))
})
.store(in: &cancelable)

}.eraseToAnyPublisher()
}
}


enum ApisTeam{
case premierLeague
}


extension ApisTeam: Moya.TargetType{

var baseURL: URL{
switch self{
case .premierLeague:
return URL(string: "https://apiv3.apifootball.com")!
}
}

var path: String {
switch self {
case .premierLeague:
return "/"
}
}

var method: Moya.Method {
switch self {
case .premierLeague:
return .get
}
}

var task: Moya.Task {
switch self {
case .premierLeague:

var params: [String: Any] = [:]
params["action"] = "get_standings"
params["league_id"] = 152
params["APIkey"] = "fb3816f9f0f2efe99d043b02e3ba5ca263c2b8cda14e98afd7f8f76df4d7a129"
return .requestParameters(parameters: params, encoding: URLEncoding.default)
}
}

var headers: [String : String]? {
var header :[String:String] = [:]
switch self {
default:
header["Content-Type"] = "application/json"
}
return header
}


}

struct TeamStat: Codable {
let countryName: String
let leagueId: String
let leagueName: String
let teamId: String
let teamName: String
let overallPromotion: String
let overallLeaguePosition: String
let overallLeaguePayed: String
let overallLeagueW: String
let overallLeagueD: String
let overallLeagueL: String
let overallLeagueGF: String
let overallLeagueGA: String
let overallLeaguePTS: String
let homeLeaguePosition: String
let homePromotion: String
let homeLeaguePayed: String
let homeLeagueW: String
let homeLeagueD: String
let homeLeagueL: String
let homeLeagueGF: String
let homeLeagueGA: String
let homeLeaguePTS: String
let awayLeaguePosition: String
let awayPromotion: String
let awayLeaguePayed: String
let awayLeagueW: String
let awayLeagueD: String
let awayLeagueL: String
let awayLeagueGF: String
let awayLeagueGA: String
let awayLeaguePTS: String
let leagueRound: String
let teamBadge: URL
let fkStageKey: String
let stageName: String

enum CodingKeys: String, CodingKey {
case countryName = "country_name"
case leagueId = "league_id"
case leagueName = "league_name"
case teamId = "team_id"
case teamName = "team_name"
case overallPromotion = "overall_promotion"
case overallLeaguePosition = "overall_league_position"
case overallLeaguePayed = "overall_league_payed"
case overallLeagueW = "overall_league_W"
case overallLeagueD = "overall_league_D"
case overallLeagueL = "overall_league_L"
case overallLeagueGF = "overall_league_GF"
case overallLeagueGA = "overall_league_GA"
case overallLeaguePTS = "overall_league_PTS"
case homeLeaguePosition = "home_league_position"
case homePromotion = "home_promotion"
case homeLeaguePayed = "home_league_payed"
case homeLeagueW = "home_league_W"
case homeLeagueD = "home_league_D"
case homeLeagueL = "home_league_L"
case homeLeagueGF = "home_league_GF"
case homeLeagueGA = "home_league_GA"
case homeLeaguePTS = "home_league_PTS"
case awayLeaguePosition = "away_league_position"
case awayPromotion = "away_promotion"
case awayLeaguePayed = "away_league_payed"
case awayLeagueW = "away_league_W"
case awayLeagueD = "away_league_D"
case awayLeagueL = "away_league_L"
case awayLeagueGF = "away_league_GF"
case awayLeagueGA = "away_league_GA"
case awayLeaguePTS = "away_league_PTS"
case leagueRound = "league_round"
case teamBadge = "team_badge"
case fkStageKey = "fk_stage_key"
case stageName = "stage_name"
}
}

struct ErrorModel: Codable, Error {
var code : String = ""

var msg : String? = ""
}

We created a ViewModel for SwiftUI’s view model.

import Foundation
import Combine

class ViewModel: ObservableObject{

var cancelable = Set<AnyCancellable>()

@Published var team : [Team] = []
func requestPremierLeagueData(){
APIManager.requestPremierLeague()
.sink(receiveCompletion: { result in

}, receiveValue: { values in
for value in values {
let thisTeam = Team(teamName: value.teamName, stadings: value.overallLeaguePosition)
self.team.append(thisTeam)
}
})
.store(in: &cancelable)
}
}
struct Team : Identifiable{
let id = UUID()
let teamName:String
let stadings:String
}

We implemented the screen in ContentView.

import SwiftUI

struct ContentView: View {
@StateObject var vm = ViewModel()

var body: some View {
VStack {

ForEach(vm.team, id: \.id, content: {team in
Text("\(team.stadings) - \(team.teamName) ")
.padding(.horizontal, 20)
})
}
.onAppear{
vm.requestPremierLeagueData()
}
.padding()
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Implementation Screen

Conclusion

Using Moya and Combine probably improved code readability and made modifications easier.

P.S. Additionally, when you register Moya in SPM (Swift Package Manager), it includes Alamofire, so you don’t need to add Alamofire separately.

--

--