저번 포스트에서는 Alamofire의 URLRequestConvertible 프로토콜을 통해 라우터 패턴을 구현하는 방법에 대해 알아보았다. Alamofire는 강력하고 유연한 네트워크 라이브러리지만, 라우터 패턴을 보다 구조화되고 명확하게 구현하고자 한다면 보다 더 높은 수준의 추상화가 필요하다.
Moya
Moya는 Swift로 API 통신을 할 때 사용하는 라이브러리로, Alamofire 위에 구축된 네트워크 추상화 라이브러리이다. Moya는 네트워크 요청을 구조화하고 관리하기 쉽게 만들어주는 라우터 패턴을 구현할 때 유용하다.
✅ Moya의 장점
1. Moya는 Alamofire보다 높은 수준의 추상화를 제공한다. API 엔드포인트를 열거형으로 정의하여 타입 안전성을 높인다.
2. API 관련 코드를 한 곳에서 관리할 수 있어 코드 구조화가 용이하다.
3. 모의 응답을 쉽게 만들 수 있어 단위 테스트가 간편하다.
4. Login, Auth 등의 기능을 플러그인으로 쉽게 추가할 수 있다.
5. RxSwift와, ReactiveSwift 등과 쉽게 통합된다.
TargetType
TargetType 프로토콜은 Moya의 핵심 프로토콜로, API 엔드포인트 속성을 정의한다. 이 프로토콜을 채택하면 각 엔드포인트에 대해 필요한 정보를 제공해야 한다.
✅ TargetType 프로토콜의 주요 메소드
1. basseURL : API의 기본 URL
2. path : 엔드포인트의 경로
3. method: HTTP 메소드 (GET, POST, PUT, DELETE)
4. task : 요청에 대한 구체적인 작업(파라미터, 인코딩 등)
5. headers : HTTP 헤더
6. sampleData : 테스트 용도로 사용될 샘플 데이터
MoyaProvider
MoyaProvider는 실제 네트워크 요청을 수행하는 클래스이다. TargetType 프로토콜을 준수하는 열거형을 제네릭 타입으로 받아 초기화된다. MoyaProvider를 통해 API 요청을 쉽게 수행하고, 응답을 처리할 수 있다.
✅ TargetType 프로토콜을 준수하는 열거형과 URLRequestConvertible 프로토콜을 준수하는 열거형의 차이점 (Moya VS Alamofire)
1. 추상화 수준
TargetType : 더 높은 수준의 추상화를 제공한다. API 엔드포인트를 더 선언적이고 읽기 쉬운 방식으로 정의할 수 있다.
URLRequestConvertible: 좀 더 낮은 수준의 추상화로, URLRequest를 직접 구성해야 한다.
2. 필수 구현 속성/메서드
TargetType : baseURL, path, method, task, headers 등의 속성을 구현 한다.
URLRequestConvertible : asURLRequest() 메소드만 구현하면 된다.
3. 요청 파라미터 처리
TargetType : task 속성을 통해 다양한 요청 타입(plain, data, parameters 등)을 쉽게 처리할 수 있다.
URLRequestConvertible : 파라미터 처리를 직접 구현해야 한다.
4. 유연성
TargetType : Moya 프레임워크의 기능들(예: 테스트, 플러그인)을 쉽게 활용할 수 있다.
URLRequestConvertible : Alamofire의 기능을 직접 사용해야 하므로 상대적으로 유연성이 낮다.
5. 테스트 용이성
TargetType : Moya의 내장 테스트 기능을 활용하여 쉽게 모의 응답을 만들 수 있다.
URLRequestConvertible : 테스트를 위해서는 별도의 설정이 필요하다.
6. 코드 구조
TargetType : 각 속성(baseURL, path, method 등)이 명확히 분리되어 있어 코드 구조가 더 명확하다.
URLRequestConvertible : 모든 로직이 asURLRequest() 메서드 안에 집중되어 있을 수 있다.
7. 오류 처리
TargetType : Moya의 내장 오류 처리 메커니즘을 활용할 수 있다.
URLRequestConvertible : 오류 처리를 직접 구현해야 한다.
8. 확장성
TargetType : Moya의 플러그인 시스템을 통해 쉽게 기능을 확장할 수 있다.
URLRequestConvertible : 기능 확장을 위해서는 추가적인 코드 작성이 필요하다.
적용하기
저번 게시글을 참고해서 Alamofire에서 Moya로 리팩토링 해보겠다.
Model과 Mockup API는 동일하다.
import Moya
enum MoyaUserRouter {
case getUsers
case getUser(id: Int)
case createUser(name: String, job: String, username: String, email: String)
case updateUser(id: Int, name: String, job: String, username: String, email: String)
case deleteUser(id: Int)
}
extension MoyaUserRouter: TargetType {
var baseURL: URL {
guard let url = URL(string: "https://jsonplaceholder.typicode.com") else {
fatalError("baseURL Error")
}
return url
}
var path: String {
switch self {
case .getUsers:
return "/users"
case .getUser(let id):
return "/users/\(id)"
case .createUser:
return "/users"
case .updateUser(let id, _, _, _, _):
return "/users/\(id)"
case .deleteUser(let id):
return "/users/\(id)"
}
}
var method: Moya.Method {
switch self {
case .getUsers, .getUser:
return .get
case .createUser:
return .post
case .updateUser:
return .put
case .deleteUser:
return .delete
}
}
var task: Task {
switch self {
case .getUsers, .getUser, .deleteUser:
return .requestPlain
case .createUser(let name, let job, let username, let email):
return .requestParameters(parameters: ["name": name, "job": job, "username": username, "email": email], encoding: JSONEncoding.default)
case .updateUser(_, let name, let job, let username, let email):
return .requestParameters(parameters: ["name": name, "job": job, "username": username, "email": email], encoding: JSONEncoding.default)
}
}
var headers: [String : String]? {
return ["Content-Type": "application/json"]
}
}
Moya를 적용하기 위해서는 TargetType 프로토콜을 준수하는 열거형을 만드는 것이다. URLRequestConvertible과 달리 URLRequest를 직접 구현할 필요가 없다.
import Moya
class MoyaUserService {
static let shared = MoyaUserService()
private init() { }
private let provider = MoyaProvider<MoyaUserRouter>()
func getUsers(completion: @escaping (Result<[User], Error>) -> Void) {
provider.request(.getUsers) { result in
switch result {
case .success(let data):
do {
let users = try JSONDecoder().decode([User].self, from: data.data)
completion(.success(users))
} catch let error {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
func getUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
provider.request(.getUser(id: id)) { result in
switch result {
case .success(let data):
do {
let users = try JSONDecoder().decode(User.self, from: data.data)
completion(.success(users))
} catch let error {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
func createUser(name: String, job: String, username: String, email: String, completion: @escaping (Result<User, Error>) -> Void) {
provider.request(.createUser(name: name, job: job, username: username, email: email)) { result in
switch result {
case .success(let data):
do {
let users = try JSONDecoder().decode(User.self, from: data.data)
completion(.success(users))
} catch let error {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
func updateUser(id: Int, name: String, job: String, username: String, email:String, completion: @escaping (Result<User, Error>) -> Void) {
provider.request(.updateUser(id: id, name: name, job: job, username: username, email: email)) { result in
switch result {
case .success(let data):
do {
let users = try JSONDecoder().decode(User.self, from: data.data)
completion(.success(users))
} catch let error {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
func deleteUser(id: Int, completion: @escaping (Result<Void, Error>) -> Void) {
provider.request(.deleteUser(id: id)) { result in
switch result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
}
MoyaProvider를 사용하여 요청을 수행하는 서비스를 구현한다.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
MoyaUserService.shared.getUsers { result in
switch result {
case .success(let users):
print(users)
case .failure(let error):
print(error)
}
}
MoyaUserService.shared.getUser(id: 1) { result in
switch result {
case .success(let user):
print(user)
case .failure(let error):
print(error)
}
}
MoyaUserService.shared.createUser(name: "Seongil", job: "Football Player", username: "Yeo", email: "yeo@seong.il") { result in
switch result {
case .success(let user):
print(user)
case .failure(let error):
print(error)
}
}
MoyaUserService.shared.updateUser(id: 1, name: "Seongil", job: "iOS App Developer", username: "Yeo", email: "yeo@seong.il") { result in
switch result {
case .success(let user):
print(user)
case .failure(let error):
print(error)
}
}
MoyaUserService.shared.deleteUser(id: 1) { result in
switch result {
case .success(let user):
print(user)
case .failure(let error):
print(error)
}
}
}
}
이제 MoyaUserService를 사용하여 API 요청을 수행할 수 있다.
나의 생각
✅ Alamofire로 구현한 것을 Moya로 리팩토링 해보면서 느낀점
1. 코드 구조의 명확성
Moya의 TargetType 프로토콜을 사용하면 API 엔드포인트의 각 속성(baseURL, path, method, task 등)이 더 명확하게 구분된다. 또, Alamofire의 URLRequestConvertible에서는 asURLRequest() 메소드 내에서 모든 것을 처리해야 했지만, Moya에서는 각 속성이 분리되어 있어 코드 가독성이 향샹됐다.
2. 요청 파라미터 처리
Moya의 Task 열거형을 사용하면 요청 파라미터를 더 쉽게 처리할 수 있다. 특히 .requestParameters Case를 사용하여 파라미터를 명확하게 정의할 수 있다. 반면에 Alamofire에서는 parameters 속성을 별도로 정의하고 이를 URLRequest에 인코딩해야 했다.
3. 네트워크 요청 실행
Moya는 MoyaProvider를 사용하여 네트워크 요청을 실행한다. 이는 더 높은 수준의 추상화를 제공한다. 반면에 Alamofire에서는 AF.request를 직접 사용해야 했다.
결론적으로, Moya를 사용하면 네트워크 계층의 코드가 더 깔끔하고 유지보수하기 쉬워진다고 생각한다. 특히 대규모 프로젝트나 복잡한 API 구조를 가진 앱에서 Moya의 장점이 더욱 두드러질 것 같다.
'iOS > iOS' 카테고리의 다른 글
[iOS] FSCalendar를 사용해보자. (0) | 2024.08.08 |
---|---|
[iOS] 라우터 패턴과 Alamofire를 이용해서 API 통신을 해보자. (0) | 2024.06.26 |
[iOS] Xcode에서 앱 이름 변경하기 (0) | 2024.01.27 |
[iOS] 동기(Sync), 비동기(Async) (1) | 2024.01.26 |
[iOS] 프로세스(Process), 쓰레드(Thread) (0) | 2024.01.26 |