[iOS] Alamofire Network Layer 구성하기

이남준·2022년 1월 16일
0

Alamofire

Alamofire는 Swift로 작성된 HTTP Networking 라이브러리로, iOS 앱에 네트워킹을 구현할 때, Moya와 함께 가장 많이 선택되는 오픈소스입니다.

Alamofire를 사용해서 HTTP Request를 보내고 싶으면,

AF.request("https://www.somewhere.com")
AF.request("https://www.somewhere.com", method: .get)

이런식으로 간단하게, 요청을 보낼 URL과 Method 등만 이용해서 요청을 보낼 수 있고,

AF.request("https://www.somewhere.com", method: .get)
	.responseData { response in 
  	switch response.result {
      case .success:
      case .failure(let error):
    }              
  }

response 처리 위처럼 편하게 해줄 수 있는 장점이 있습니다.

하지만, 앱에서 다양한 Request를 사용하고, 여러 Method, Parameter가 있다면 네트워크 요청시 마다 위처럼 request, response를 하드코딩으로 작성한다면 가독성이 떨어지고, 중복된 작업을 매번 해줘야하기 때문에 Request, Response, 인증 등을 담담해주는 Network Layer를 구성하는 것이 효율적입니다.

Network Layer

Request

먼저 비슷한 API들을 카테고리화하기 위해서 enum 타입으로 end point를 정의하겠습니다.

enum에 포함된 요소들은 Request를 위한 method, header, path, parameter를 모두 가집니다.

enum MapAPI {
  case getCurrentLoaction()
  case findLocation(x: String, y: String)
}

먼저 필요한 요청들을 enum으로 정의하고, URL Request를 구성할 수 있는 URLRequestConvertible프로토콜을 채택하고 구현합니다.

extension MapAPI: URLRequestConvertible {
    
  	// End point base URL
		var baseURL: URL {
        return URL(string: "http://www.awsomemaps.com")!
    }
    
  	// 요청별 method 정의
    var method: HTTPMethod {
        switch self {
          	case .getCurrentLoaction:
          			return .get
          	case .findLocation(_, _):
          			return .get
        }
    }
    
  	// 요청별 path 정의
    var path: String {
        switch self {
        case .getCurrentLoaction:
            return "/current"
        case .findLocation(_, _):
            return "/find"
        }
    }
    
    var header: HTTPHeaders? {
        return nil
    }
    
  	// 요청별 parameter 정의
    var parameters: Parameters? {
        switch self {
        		case .getCurrentLoaction:
          			return nil
          	case .findLocation(let x, let y):
          			return ["xPosition": x, "yPosition": y]
        }
    }
    
  	// 정의한 요소들로 request 생성
    func asURLRequest() throws -> URLRequest {
        let url = baseURL
        
        var urlRequest = URLRequest(url: url.appendingPathComponent(path))
        urlRequest.httpMethod = method.rawValue
        urlRequest.allHTTPHeaderFields = header
        
        let encoding = URLEncoding.default
        return try encoding.encode(urlRequest, with: parameters)
    }
}

이렇게 필요한 Request들을 정의했으니, 실제 Request를 보내는 Client를 정의하겠습니다.

Client

class MapService {
    static func request<T: Codable> (_ urlConvertible: URLRequestConvertible) -> Observable<T> {
          return Observable<T>.create { observer in
              let request = AF.request(urlConvertible).responseJSONDecodable { (response: DataResponse<T>) in
                  switch response.result {
                  case .success(let value):
                      observer.onNext(value)
                      observer.onCompleted()
                  case .failure(let error):
                      switch response.response?.statusCode {
                      case 403:
                          observer.onError(ApiError.forbidden)
                      case 404:
                          observer.onError(ApiError.notFound)
                      case 409:
                          observer.onError(ApiError.conflict)
                      case 500:
                          observer.onError(ApiError.internalServerError)
                      default:
                          observer.onError(error)
                      }
                  }
              }

              return Disposables.create {
                  request.cancel()
              }
          }
      }
}

Request별 함수를 정의하기 전에, 먼저 Generic 타입을 파라미터로 받는 함수를 먼저 정의했습니다.

각 Request별 함수튼 위 Generic request함수를 이용해서 구현하겠습니다.

static func getCurrentLocation() -> Observable<Location?> {
  	return request(MapAPI.getCurrentLocation)
}

static func findLocation(x: String, y: String) -> Observable<Location?> {
  	return request(MapAPI.findLocation(x: x, y: y))
}

이렇게 각 Request를 정의하고, 요청을 보낼 수 있는 함수를 정의했습니다.

MapService.getCurrentLocation()
		.subscribe(onNext: { location in
      	print(location)
    }, onError: { error in 
    		print(error)            
    })
		.disposed(by: disposeBag)

정의 해둔 Request의 사용은 위처럼 MapService의 함수를 콜하고, subscribe만 해주면 됩니다.

Alamofire를 이용해서 간단한 수준의 Network Layer를 구현해봤습니다.

인증, 로그, 세션 등을 모두 처리하도록 Network Layer를 구성할 수도 있지만, 필요에 따라서는 위의 수준에서 구현하는 것도 괜찮은 것 같습니다.

profile
iOS 개발자의 기록

0개의 댓글