iOS 앱 개발에서 API 통신은 필수적인 요소이다. API를 통해 서버와 데이터를 주고 받으며, 이를 통해 다양한 기능을 구현할 수 있다. 그러나 여러 API 요청을 효과적으로 관리하고 유지보수하기 위해서는 체계적인 접근이 필요하다. 이때 유용한 방법 중 하나가 라우터 패턴을 사용하는 것이다.
라우터 패턴을 사용하면 각 API 요청을 구조화하여 관리할 수 있다. URL, HTTP Method, Parameter 등을 중앙에서 정의하고 관리할 수 있기 때문에 코드의 일관성과 가독성이 높아진다. 또한, 라우터를 통해 요청을 캡슐화함으로써 네트워크 요청 관련 코드를 재사용할 수 있고, 변경 사항을 쉽게 적용할 수 있다.
이 글에서는 Alamofire를 사용하여 라우터 패턴을 적용하는 방법에 대해 다뤄보겠다. Alamofire는 Swift에서 네트워크 요청을 간편하게 처리할 수 있는 라이브러리로, 라우터 패턴과 결합하면 더 효율적이고 유지보수하기 쉬운 네트워크 통신 코드를 작성할 수 있다.
URLRequestConvertible
URLRequestConvertible는 Alamofire 라이브러리에서 사용하는 프로토콜로, URLRequest 객체를 생성하는 데 사용된다. 이 프로토콜을 채택하면 네트워크 요청을 정의하고, 이를 URLRequest 객체로 변환할 수 있다. 이는 API 요청을 구조화하고 관리하는 데 매우 유용하다.
URLRequestConvertible 프로토콜은 Alamofire가 네트워크 요청을 처리할 수 있도록, 요청을 URLRequest 객체로 변환하는 역할을 한다. 이 프로토콜은 asURLRequest() 메소드를 요구한다.
public protocol URLRequestConvertible {
func asURLRequest() throws -> URLRequest
}
이 프로토콜을 채택한 객체는 asURLRequest() 메소드를 구현해야 하며, 이 메소드는 네트워크 요청을 표현하는 URLRequest 객체를 반환한다.
❓왜 URLRequestConvertible를 사용하는가?
1. 코드의 일관성 : 네트워크 요청을 정의하는 코드가 한 곳에 모이기 때문에 코드의 일관성을 유지할 수 있다.
2. 재사용성 : 공통된 요청 로직을 캡슐화하여 여러 곳에서 재사용할 수 있다.
3. 유지보수성 : 요청의 URL, HTTP Method, Header, Parameter 등을 쉽게 변경할 수 있다.
라우터 패턴
라우터 패턴은 네트워크 요청을 관리하는 데 사용되는 디자인 패턴으로, 각 API 요청의 URL, HTTP Method, Parameter, Header 등을 중앙에서 정의하고 관리할 수 있게 한다. 이를 통해 네트워크 요청 코드를 구조화하고 일관성 있게 유지할 수 있다. 라우터 패턴은 주로 RESTful API와 같은 네트워크 요청을 처리하는 데 유용하다.
라우터 패턴의 핵심 요소
1. 라우터 정의 : 각 API의 요청을 enum이나 struct로 정의하여, 요청의 URL, HTTPMethod, Parameter 등을 중앙에서 관리한다.
2. URLRequestConvertible : Alamofire와 같은 라이브러리에서는 URLRequestConvertible 프로토콜을 사용하여 각 API 요청을 URLRequest로 변환한다.
3. 중앙집중식 관리 : 모든 네트워크 요청을 한 곳에서 관리함으로써 코드의 일관성과 가독성을 높인다.
적용하기
간단한 예제를 위해 네트워크 파일 구조를 설계했다.
struct User: Codable {
let id: Int
let name: String
let username: String
let email: String
}
서버로부터의 응답을 처리할 모델을 정의한다.
import Alamofire
enum UserRouter {
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 UserRouter: URLRequestConvertible {
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: HTTPMethod {
switch self {
case .getUsers, .getUser:
return .get
case .createUser:
return .post
case .updateUser:
return .put
case .deleteUser:
return .delete
}
}
var parameters: Parameters? {
switch self {
case .getUsers, .getUser, .deleteUser:
return nil
case .createUser(let name, let job, let username, let email):
return ["name": name, "job": job, "username": username, "email": email]
case .updateUser(_, let name, let job, let username, let email):
return ["name": name, "job": job, "username": username, "email": email]
}
}
func asURLRequest() throws -> URLRequest {
let url = try baseURL.asURL()
var urlRequest = URLRequest(url: url.appendingPathComponent(path))
urlRequest.method = method
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
if let parameters = parameters {
urlRequest = try JSONEncoding.default.encode(urlRequest, with: parameters)
}
return urlRequest
}
}
라우터를 정의하는 첫 번째 단계는 URLRequestConvertible 프로토콜을 준수하는 열거형을 만드는 것이다. 이 enum은 각 API 요청을 정의한다.
import Alamofire
class UserService {
static let shared = UserService()
private init() { }
func getUsers(completion: @escaping (Result<[User], Error>) -> Void) {
AF.request(UserRouter.getUsers).responseDecodable(of: [User].self) { response in
switch response.result {
case .success(let users):
completion(.success(users))
case .failure(let error):
completion(.failure(error))
}
}
}
func getUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
AF.request(UserRouter.getUser(id: id)).responseDecodable(of: User.self) { response in
switch response.result {
case .success(let user):
completion(.success(user))
case .failure(let error):
completion(.failure(error))
}
}
}
func createUser(name: String, job: String, username: String, email: String, completion: @escaping (Result<User, Error>) -> Void) {
AF.request(UserRouter.createUser(name: name, job: job, username: username, email: email)).responseDecodable(of: User.self) { response in
switch response.result {
case .success(let user):
completion(.success(user))
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) {
AF.request(UserRouter.updateUser(id: id, name: name, job: job, username: username, email: email)).responseDecodable(of: User.self) { response in
switch response.result {
case .success(let user):
completion(.success(user))
case .failure(let error):
completion(.failure(error))
}
}
}
func deleteUser(id: Int, completion: @escaping (Result<Void, Error>) -> Void) {
AF.request(UserRouter.deleteUser(id: id)).response { response in
switch response.result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
}
이제 라우터를 사용하여 API 요청을 수행하는 서비스를 구현한다.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UserService.shared.getUsers { result in
switch result {
case .success(let users):
print(users)
case .failure(let error):
print(error)
}
}
UserService.shared.getUser(id: 1) { result in
switch result {
case .success(let user):
print(user)
case .failure(let error):
print(error)
}
}
UserService.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)
}
}
UserService.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)
}
}
UserService.shared.deleteUser(id: 1) { result in
switch result {
case .success(let user):
print(user)
case .failure(let error):
print(error)
}
}
}
}
이제 UserService를 사용하여 API 요청을 수행할 수 있다.
'iOS > iOS' 카테고리의 다른 글
[iOS] FSCalendar를 사용해보자. (0) | 2024.08.08 |
---|---|
[iOS] Moya가 모야? (0) | 2024.06.27 |
[iOS] Xcode에서 앱 이름 변경하기 (0) | 2024.01.27 |
[iOS] 동기(Sync), 비동기(Async) (1) | 2024.01.26 |
[iOS] 프로세스(Process), 쓰레드(Thread) (0) | 2024.01.26 |