서버 통신 구현 흐름 (with Alamofire)

이세진·2022년 9월 20일
0

iOS

목록 보기
38/46
post-custom-banner

Alamofire를 이용하여 서버 통신을 위한 기초 코드를 작성해보자!

제일 먼저 CocoaPods 혹은 Swift Package Manager를 이용하여 Alamofire를 설치한다.

  • 단계 요약
    1. APIConstants 구조체 생성
    2. NetworkResult 열거형 생성
    3. Model 생성
    4. Service 클래스(혹은 구조체) 생성
    5. ViewController에서 서버 통신 코드 호출

1. APIConstants 구조체 생성

  • 통신할 API 주소들을 모아 놓은 구조체를 생성한다.
import Foundation

struct APIConstants {
    //MARK: - Base URL
    static let baseURL = "http://3.34.192.134:8000"

    //MARK: - Feature URL
    // 게시글 전체를 가져오기 위한 URL
    static let getPostsURL = baseURL + "/post"
		// 댓글 작성 URL
    static let postCommentURL = baseURL + "/review/post"
}
  • 이렇게 API 주소들을 static을 이용하여 선언해주면 다른 곳에서 해당 주소를 사용할 때 직접 타이핑 할 필요없이 APIConstants.getPostsURL 처럼 사용할 수 있다.

2. NetworkResult 열거형 생성

  • NetworkResult라는 enum을 생성한다.
  • 서버 통신을 통해 얻은 결과를 해당 enum타입으로 만들어서 ViewController에 전달하기 위함이다.
  • 곧바로 데이터를 넘겨주지 않고 enum으로 만드는 이유는 다양한 서버 통신 결과 (성공or실패)를 구분하여 전달할 수 있기 때문이다.
import Foundation

enum NetworkResult<T> {
  case success(T) //서버 통신 성공했을 때
  case requestErr(T) // 요청 에러 발생했을 때
  case pathErr(T) // 경로 에러 발생했을 때
  case serverErr // 서버의 내부 에러가 발생했을 때
  case networkFail // 네트워크 연결 실패했을 때
}
  • 제네릭을 사용하여 Json을 디코딩한 결과 모델을 enum에 담아서 전달한다.

3. Model 생성

  • 서버에 request를 보낸 후 받을 Response의 형태에 맞는 Model을 생성해야한다. (API 명세서를 확인하거나 Postman에서 직접 해당 API 주소로 요청을 보내고 얻은 JSON Response를 통해 확인한다.)
  • Response 예시
    {
        "status": 201,
        "success": true,
        "message": "회원가입 성공",
        "data": {
            "id": 133
        }
    }
  • Response가 위와 같다면 다음과 같은 구조로 Model을 생성할 수 있다.
    import Foundation
    
    struct BaseResponse<T: Codable>: Codable {
        let status: Int
        let success: Bool
        let message: String
        let data: T?
    }
    
    struct SignUpData: Codable {
        let id: Int
    }
    • 일반적으로 대부분의 API에서는 status, success, message는 공통으로 Response에 포함시키기 때문에 BaseResponse와 같은 구조체를 생성하여 재사용하는 것이 효율적이다. data는 API 종류에 따라 바뀌기 때문에 제네릭으로 선언해준다.
  • Response Model 생성을 도와주는 사이트 Convert JSON to Swift, C#, TypeScript, Objective-C, Go, Java, C++ and more * quicktype

4. Service 클래스(혹은 구조체) 생성

  • 본격적으로 서버에 요청을 보내 결과를 받아보기 위한 Service 코드를 작성한다.
  • API 명세서를 확인하여 Request Body가 있다면 이를 Parameters로 만들어준다.
  • UserService.swift
    import Foundation
    import Alamofire
    
    struct UserService {
        static let shared = UserService()
        
        private init() {}
    		
    		// 회원가입
        func signUp(name: String, email: String, password: String, completion: @escaping (NetworkResult<Any>) -> Void) {
            let url = APIConstants.signUpURL
            let header: HTTPHeaders = ["Content-Type" : "application/json"]
            let body: Parameters = [
                "name": name,
                "email": email,
                "password": password
            ]
            
            // Request 생성
    				// get통신에서는 encoding에 URLEncoding.default 사용
            let dataRequest = AF.request(url, method: .post, parameters: body, encoding: JSONEncoding.default, headers: header)
            
            // Request 시작
            dataRequest.responseData { response in
                switch response.result {
                // 성공 시 상태코드와 데이터(value) 수신
                case .success:
                    guard let statusCode = response.response?.statusCode else { return }
                    guard let value = response.value else { return }
                    
                    let networkResult = NetworkHelper.parseJSON(by: statusCode, data: value, type: SignUpData.self)
                    completion(networkResult)
                    
                // 실패 시 networkFail(통신 실패)신호 전달
                case .failure:
                    completion(.networkFail)
                }
            }   
        }
    }
  • NetworkHelper.swift
    import Foundation
    
    struct NetworkHelper {
        private init() {}
        
        // 상태 코드와 데이터, decoding type을 가지고 통신의 결과를 핸들링하는 함수
        static func parseJSON<T: Codable> (by statusCode: Int, data: Data, type: T.Type) -> NetworkResult<Any> {
            let decoder = JSONDecoder()
    
            guard let decodedData = try? decoder.decode(BaseResponse<T>.self, from: data) else { return .pathErr }
            
            switch statusCode {
            case 200..<300: return .success(decodedData)
            case 400..<500: return .requestErr(decodedData)
            case 500..<600: return .serverErr
            default: return .networkFail
            }
        }
    }

5. ViewController에서 서버 통신 코드 호출

  • API 호출이 필요한 ViewController에서 앞서 만든 Service 구조체의 함수를 호출한다.
//MARK: - UserService
extension LoginConfirmController {
    func signUp() {
        guard let name = name else { return }
        let email = name
        guard let password = password else { return }
        
        UserService.shared.signUp(name: name, email: email, password: password) { respose in
            switch respose {
            case .success(_):
                self.alert(withTitle: "회원가입 성공", message: "Instagram에 오신 것을 환영합니다.") { _ in
                    self.dismiss(animated: true)
                }
            case .requestErr(_):
                self.alert(withTitle: "회원 가입 실패", message: "이미 등록된 유저가 존재합니다.") { _ in
                    self.dismiss(animated: true)
                }
            case .pathErr:
                self.alert(withTitle: "회원가입 실패", message: "Can not decode...")
            case .serverErr:
                print("serverError")
            case .networkFail:
                print("networkFail")
            }
        }
    }
}
  • signUp이라는 함수를 별도로 VC에 생성하고 해당 함수 내부에서 UserSerivce.shared.signUp(…)을 호출하는 방식을 사용하였다.
  • 위의 예시에서는 .success 일 때 얻은 data를 VC에 활용하지 않았기 때문에 통신 성공 여부만 확인하고 있다.
  • 만약, 서버 통신을 통해 받은 data를 화면에 보여줘야 한다면
    case .success(let data):
    	guard let testData = data as? SomeDataModel else { return }
    	dataArray = testData.anyDataList
    위처럼 data를 원하는 모델로 캐스팅하고 VC에 생성해 놓은 어레이에 담아주면 된다. 해당 어레이에 담긴 값을 UI로 뿌려주면 서버 통신 완료!
profile
나중은 결코 오지 않는다.
post-custom-banner

0개의 댓글