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호~에 뻘하게 터졌네요;; ㅋㅋ