Alamofire is an HTTP networking library written in Swift.
Alamofire는 Swift기반의 HTTP 네트워킹 라이브러리로 iOS, macOS 개발에서 정말 유용하게 쓰인다.
주요 특징은 아래와 같이 나타난다.
사실 굳이 Alamofire를 쓰지 않더라도 iOS에서 기본적으로 제공하는 URLSession를 통해 네트워킹이 가능하다. 그럼에도 Alamofire를 많은 사람들이 이용하는 이유는 코드의 간소화, 가독성
측면에서 도움을 주고 여러 기능
을 직접 구축하지 않아도 쉽게 사용할 수 있다는 점일 것이다.
물론 Alamofire 역시 URLSession에 기반한 라이브러리이기 때문에 크게 이질적이지 않아 사용에도 문제가 없다.
참고 : https://ichi.pro/ko/alamofire-vs-urlsession-swiftui-neteuwoking-bigyo-218576183751608
지금까지 Alamofire를 공부하고 이용했던 것들을 정리해보면서 이에 대한 개념과 활용성에 대해서 조금 더 고민해보자.
func getEvents(completion: @escaping (NetworkResult<Any>) -> Void){
let url : String = APIConstants.promiseCheckEvents
requestGetData(url: url, httpmethod: .get,header: header, decodeType: ResponseCalendarEvents.self) {(completionData) in
completion(completionData)
}
}
func getPromiseList(_ selectedDate: String, completion: @escaping (NetworkResult<Any>) -> Void){
let url : String = APIConstants.promiseList + selectedDate
requestGetData(url: url, httpmethod: .get, header: header, decodeType: ResponsePromiseList.self) {(completionData) in
completion(completionData)
}
}
func judgeStatus<T : Codable>(by statusCode : Int, _ data : Data, _ decodeType : T.Type) -> NetworkResult<Any>{
let decoder = JSONDecoder()
guard let decodedData = try? decoder.decode(decodeType, from: data) else{return .serverErr}
switch statusCode {
case 200: return .success(decodedData)
case 400: return .requestErr(NetworkInfo.NO_DATA)
case 500: return .serverErr
default: return .networkFail
}
}
func requestGetData<T : Codable> (url : String, httpmethod : HTTPMethod, header : HTTPHeaders, decodeType : T.Type, completion: @escaping (NetworkResult<Any>) -> Void){
let dataRequest = AF.request(url,
method: httpmethod,
encoding: JSONEncoding.default,
headers: header).validate(statusCode: 200...500)
dataRequest.responseData { response in
switch response.result{
case .success :
guard let statusCode = response.response?.statusCode else { return }
guard let value = response.value else{return}
let networkResult = self.judgeStatus(by : statusCode, value, decodeType)
completion(networkResult)
case .failure:
completion(.serverErr)
}
}
}
일반적으로 singleton
형태로 객체를 만들고 위의 네트워크 통신을 맡도록 하는 구조이다.
우선 반복적인 코드를 줄이기 위해 statusCode를 판단하는 부분과 dataRequest 부분을 따로 함수를 구현했다.
이로 인해 추가적인 request가 많아질수록 효과적으로 코드를 줄일 수 있다.
이렇게 정리했을 때의 장점은 각 기능별로 분리가 되어있어 코드를 보는 측면에서 좋다고 느꼈다.
여기까지가 내가 지금까지 활용해왔던 Alamofire 부분이다.
여기서 추가적으로 RequestInterceptor, EventMonitor, URLRequestConvertible
를 더 알아보자.
우선 Interceptor는 서버에 요청을 보낼 때, 중간에 가로채서 어떤 작업을 한 뒤 서버로 보내는 역할을 한다.
서버로 Request를 보내기 전이니 공통 파라미터, 헤더, 토큰을 추가로 넣어줄 때 사용하면 편하게 사용할거라 생각한다.
또한 이를 위해선 func adapt() , func retry() 를 구현해줘야 한다. Interceptor에 따른 작동 순서는 다음과 같다.
아래 코드 예시를 첨부한다.
class BaseInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var request = urlRequest
request.addValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
//공통 파라메터 추가
var dictionary = [String:String]()
dictionary.updateValue(~~~, forKey: "~~~")
do{
request = try URLEncodedFormParameterEncoder().encode(dictionary, into: request)
}catch{
print(error)
}
completion(.success(request))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
guard let statusCode = request.response?.statusCode else{
completion(.doNotRetry)
return
}
//statusCode에 따라 재시도 조건 삽입
//completion(.doNotRetry)
//NotificationCenter.default.post(name: NSNotification.Name(rawValue: NOTIFICATION.API.AUTH_FAIL), object: nil, userInfo: data)
}
}
다음으로는 EventMonitor이다. EventMonitor는 내부 이벤트를 보다 명확하게 볼 수 있게 해주는 로깅
에 강점을 갖는다.
예시는 아래와 같으며 로그를 찍어보며 단계를 자세히 보는게 도움이 될거같다. 이밖에도 더 많은 이벤트에 따른 함수를 가지고 있다.
final class MyLogger : EventMonitor{
func requestDidFinish(_ request: Request) {//request 끝났는지}
func requestDidCancel(_ request: Request) {//request 취소됐는지}
func requestIsRetrying(_ request: Request) {//request 재시도됐는지}
func requestDidResume(_ request: Request) {
print("reqeustDidResume") //request 잘 갔는지
}
func request(_ request: DataRequest, didParseResponse response: DataResponse<Data?, AFError>) {
print("didParseResponse") //response를 잘 파싱했는지
}
}
다음은 URLRequestConvertible이다. enum
타입을 통해 router 경로를 지정해주고 네트워크 스택을 구축
해나가는 곳이다.
물론 사진, 유저를 검색한다고 했을 때 이 둘을 search라는 스택 하나, 그 다음 각각 photo, user 로 네트워크 스택을 쌓게 해줄 수도 있지만 HTTPMethod .get, .post에 따른 구분도 가능하게 해주며 스택이 깊을수록 경우 중복도 적어지고 enum으로 parameter, path한데 묶어서 볼 수 있으니 가독성 측면에서도 좋을거같다.
이렇게 구축한 경로를 func asURLRequest() throws -> URLRequest를 통해 request를 통해 반환해준다.
enum SearchRouter: URLRequestConvertible {
case SearchPhotos(terms: String)
case SearchUsers(terms: String)
var baseURL: URL {
return URL(string: API.BASE_URL + "search")!
}
var method: HTTPMethod {
switch self {
case .SearchPhotos: return .get
case .SearchUsers: return .get
}
}
var path: String {
switch self {
case .SearchPhotos: return "/photos"
case .SearchUsers: return "/users"
}
}
var parameters :[String:String]{
switch self {
case let .SearchPhotos(terms): return ["query": terms]
case let .SearchUsers(terms): return ["query": terms]
}
}
func asURLRequest() throws -> URLRequest {
let url = baseURL.appendingPathComponent(path)
var request = URLRequest(url: url)
request.method = method
request = try URLEncodedFormParameterEncoder().encode(parameters, into: request)
switch self {
case let .SearchPhoto(parameters):
request = try URLEncodedFormParameterEncoder().encode(parameters, into: request)
case let .SearchUser(parameters):
request = try JSONParameterEncoder().encode(parameters, into: request)
}
return request
}
}
위에서 새롭게 배운 인터셉터, 로거, 라우터
를 이제 등록을 시켜줘야한다. 제일 처음 만든 싱글톤 객체(ex. AlamofireManager)에 세션을 설정해주고 등록해주면 된다.
로거와 인터셉터는 필요에 따라 dictionary 형태로 여러 개 등록이 가능하고 라우터는 비슷한 네트워크를 구축하는 것들끼리 라우터를 만들어서 request할 때 호출해주면 된다.
//인터셉터
let interceptors = Interceptor(interceptors:[BaseInterceptor()])
//로거 설정
let monitors = [MyLogger()]
//세션 설정
var session : Session
private init(){
session = Session(interceptor: interceptors, eventMonitors: monitors)
}
func getPhotos(searchTerm userInput : String, completion : @escaping(Result<[Photo], MyError>) -> Void){
self.session
.request(SearchRouter.SearchPhotos(terms: userInput))
.validate(statusCode: 200..<401)
.responseJSON { (response) in
//response 처리
}
}
정리가 잘 되어있네요. 개발을 공부하는 분들에게 많은 도움이 될 것 같아요 😊