
iOS에서 네트워킹을 구현하는 가장 기본적인 방법은 URLSession을 사용하는 것입니다.
URLSession은 로우레벨의 코드를 작성할 수 있고, 다른 프레임워크를 사용할 필요가 없다는 장점이 있지만, 사용이 복잡하고 코드의 가독성이 좋지 않아서 Foundation Networking을 기반으로한 인터페이스를 제공해 네트워킹 작업을 단순화 해주는 라이브러리인Alamofire를 많이 사용합니다.
Alamofire는 iOS 앱을 개발할 때 가장 보편적으로 많이 사용되는 방법이지만, 유지보수와 유닛 테스트가 힘들다는 단점이 있습니다.
그래서 등장한 Moya는 URLSession을 추상화한 Alamofire를 다시 추상화한 프레임워크로 Network Layer를 템플릿화 해서 재사용성을 높히고, 개발자가 request, response에만 신경을 쓰도록 해줍니다.
Moya의 사용법을 알아보고 실제 사용을 해보기 위해 샘플 프로젝트를 하나 만들어 테스트 해보겠습니다.

Moya는 Swift Package Manager, CocoaPods, Carthage를 사용하여 인스톨할 수 있기 때문에 본인에게 가장 익숙하고 편한 방법으로 설치하면 됩니다.
Moya는 네트워킹 프레임워크이기 때문에, 테스트를 하기 위해서는 Http 통신을 지원하는 api가 꼭 필요합니다.
이번 포스트에서는
Chuck Norris once ordered a Big Mac at Burger King, and got one.
와 같은 척 노리스 밈(조크)를 모아둔 ICNDb.com에서 랜덤한 조크를 리턴해주는 api를 사용하겠습니다.

템플릿 Moya에서 제공해주는 템플릿을 따라 코드를 작성해보겠습니다.
새로운 파일을 하나 만들고 JokeAPI.swift
enum을 하나 선언해서 사용될 target들을 작성합니다.
// JokeAPI.swift
import Moya
enum JokeAPI {
case randomJokes(_ firstName: String? = nil, _ lastName: String? = nil)
}
이 예제에서는 이름을 파라미터로 받아 조크의 이름을 만들어주는 target을 하나만 사용하겠습니다.
TargetType 구현Moya는 MoyaProvider<TargetType>으로 request를 수행하기 때문에, 위에서 정의한 API가 TargetType 프로토콜을 구현해야합니다.
extension을 만들어 TargetType 프로토콜을 채택하면, 아래와 같은 프로퍼티들을 구현 해야합니다.
extension JokeAPI: TargetType {
var baseURL: URL {
return URL(string: "https://api.icndb.com")!
}
var path: String {
switch self {
case .randomJokes(_, _):
return "/jokes/random"
}
}
var method: Moya.Method {
switch self {
case .randomJokes(_, _):
return .get
}
}
var sampleData: Data {
switch self {
case .randomJokes(let firstName, let lastName):
let firstName = firstName
let lastName = lastName
return Data(
"""
{
"type": "success",
"value": {
"id": 107,
"joke": "\(firstName)\(lastName) can retrieve anything from /dev/null."
}
}
""".utf8
)
}
}
var task: Task {
switch self {
case .randomJokes(let firstName, let lastName):
let params: [String: Any] = [
"firstName": firstName,
"lastName": lastName
]
return .requestParameters(parameters: params, encoding: URLEncoding.queryString)
}
}
var headers: [String : String]? {
return ["Content-type": "application/json"]
}
}
위처럼 switch문을 통해 여러 target에 맞는 프로퍼티들을 정의해줄 수 있습니다.
이제 위에서 정의한 TargetType을 이용해 request를 보내는 코드를 작성하겠습니다.
response 시 받는 JSON의 형태는 아래와 같습니다.
{
"type": "success",
"value": {
"id": 268,
"joke": "Time waits for no man. Unless that man is John Doe."
}
}
이와 대응되는 Decodable struct를 정의하고:
struct Joke: Decodable {
var type: String
var value: Value
struct Value: Decodable {
var id: Int
var joke: String
}
}
이제 request를 보내고 받은 response를 text view에 표시해보도록 하겠습니다.
class ViewController: UIViewController {
@IBOutlet var jokeTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let provider = MoyaProvider<JokeAPI>()
provider.request(.randomJokes("GilDong", "Hong")) { (result) in
switch result {
case let .success(response):
let result = try? response.map(Joke.self)
self.jokeTextView.text = result?.value.joke
case let .failure(error):
print(error.localizedDescription)
}
}
}
}
이처럼 네트워크 요청을 Moya를 통해 처리하면 enum을 활용해 안전한 방식으로 캡슐화된 요청을 할 수 있다는 장점이 있고, 코드를 직관적이고 쉽게 작성할 수 있다는 장점이 있습니다.
페이지 들어왔다가 moya호~에 뻘하게 터졌네요;; ㅋㅋ