• Jan
  • Feb
  • Mar
  • Apr
  • May
  • Jun
  • Jul
  • Aug
  • Sep
  • Oct
  • Nov
  • Dec
  • Sun
  • Mon
  • Tue
  • Wed
  • Thu
  • Fri
  • Sat
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Networking 정리 4 : Alamofire

NetworkResult.swift 파일 만들기

서버 통신 결과를 처리하기 위한 파일

import Foundation

enum NetworkResult<T> {
  case success(T)					// 서버 통신 성공했을 때,
  case requestErr(T)			// 요청 에러 발생했을 때,
  case pathErr						// 경로 에러 발생했을 때,
  case serverErr					// 서버의 내부적 에러가 발생했을 때,
  case networkFail				// 네트워크 연결 실패했을 때
}

<T>?
타입 파라미터로, 지금 당장 타입을 정해놓지 않겠다는 뜻
해당 자리에는 Int, String, Bool 등 다양한 타입이 들어갈 수 있는 것

Model.swift 만들기

Quicktype 등 사용
CodingKeys, init decode function 등도 여기서 작성

Service 작성

struct GetPersonDataService {
  
}

내부에 작성

static let shared = GetPersonDataService()

싱글턴 인스턴스 생성 –> 여러 뷰컨에서 shared로 접근하면 같은 인스턴스에 접근할 수 있음

func getPersonInfo(completion: @escaping (NetworkResult<Any>) -> Void) {
  // completion 클로저를 @escaping 클로저로 정의함
  
}
  • getPersonInfo의 함수가 종료되든 말든 상관 없이,
    completion은 탈출 클로저이기 때문에 전달된다면 함수 종료 이후에 외부에서도 사용 가능
  • 여기서는 해당 네트워크 작업이 끝날 때
    completion 클로저에 네트워크 성공 여부 결과(NetworkResult에서 작성한)를 담아서 호출하게 됨.
  • 그 담은 네트워크 결과는 이후에 ViewController에서 꺼내서 처리해줄 것

아래는 함수 내용

let URL = "https://어쩌구저쩌구"
let header: HTTPHeaders = [
	"Content-Type": "application/json"
]

주소, 헤더 작성

let dataRequest = AF.request(URL,
                            method: get,
                            encoding: JSONEncoding.default,
                            headers: header)

주소, HTTPMethod, 인코딩 방식, 헤더 등 Request를 보내기 위한 정보를 묶어서
dataRequest에 저장해둔다.
““나 이렇게 통신 보낼거야!” 라고 적어둔 요청서 개념

dataRequest.responseData { dataResponse in
  
}

위에서 적어둔 요청서를 가지고 진짜 서버에 보내서 Request를 하는 중.
통신이 완료되면 클로저를 통해 dataResponse라는 이름으로 결과가 도착함.

아래는 클로저 내용

switch dataResponse.result {
  case.success:
  	
  case.failure:
}

dataResponse.result (통신 성공했는지 실패했는지 여부)로 switch 분기문을 만들어 준다.

dataResponse.result : 통신 성공 여부 (.success, .failure)
dataResponse.response?.statusCode: Response의 status code
dataResponse.value: Response의 결과 데이터

switch문 내 내용을 작성해보면

switch dataResponse.result {
  case.success:
  	guard let statusCode = dataResponse.response?.statusCode else {return}
  	guard let value = dataResponse.value else {return}
  	let networkResult = self.judgeStatus(by: statusCode, valule)
  	completion(networkResult)
  
  case.failure: completion(.pathErr)
}

1. 성공일 때
우리가 필요한 데이터는 statusCode, response 결과 데이터 두가지.
그리고 judgeStatus라는 함수에 statusCode랑 response 결과 데이터를 실어서 보낸다.

근데 이 때 성공 실패는 그냥 서버에서 값을 줬냐 안 줬냐 이기 때문에,
statusCode가 400, 500일 때에도 성공은 성공임. 통신이 되긴 한거니까.
그래서 judgeStatus로 판단하는 것

2. 실패일 때 (통신 자체가 실패 했을 때)
가차없이 completion(.pathErr) 통신 실패 값을 담아서 뷰컨으로 날려줌.
(탈출 클로저니까 클로저가 밖으로 나가는 것이 가능함!)

이제 judgeStatus 함수를 작성하자

private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult<Any> {
  switch statusCode {
    case 200: return isValidData(data: data)
    case 400: return .pathErr
    case 500: return .serverErr
    default: return .networkFail
  }
}

statuscode가
200일 때 : 성공, 데이터를 가공해서 전달해줘야 하기 때문에 isValidData라는 함수에 데이터를 넘겨 줌.
(데이터 가공은 isValidData에서 수행)
400일 때 : 뭔가 요청이 잘못되었다는 뜻, .pathErr 리턴
500일 때 : 서버 잘못, .serverErr 리턴
기타 : .networkFail로 분기처리

여기서 200 말고는 NetworkResult 값을 return하고 있는데, 요 값은 어디로 가냐면 아까 작성한 부분에서

let networkResult = self.judgeStatus(by: statusCode, valule)
  	completion(networkResult)

여기 completion에 실어서 뷰컨으로 보내주는 것 !! 뷰컨에서는 NetworkResult 값을 받아서 분기처리해주면 됨.

이제 데이터를 가공한다고 했던 isValidData 함수를 작성해보자

private func isValidData(data: Data) -> NetworkResult<Any> {
  let decoder = JSONDecoder()
  
  guard let decodedData = try? decoder.decode(PersonDataModel.self, from: data) else {
    return .pathErr
  }
  // PersonDataModel 형태로 decode를 한 번 해줌. 만약 decode에 실패하면 .pathErr 반환
  
  // 해독에 성공하면 PersonData를 success에 넣어줌.
  return .success(decodedData.data)
}
  • JSON 데이터를 해독하기 위해 JSONDecoder를 하나 선언
  • data를 우리가 만들어둔 Model 형으로 decode를 시도
  • 실패하면 pathErr, 성공하면 decodedData에 값을 담아줌
  • decode에 성공하면 success에다가 data 부분을 담아서 completion을 호출함.
    (그렇게 되면 뷰컨에서 이 data를 빼서 사용 가능)

뷰컨에서 서버 통신 진행하기

GetPersonDataService.shared.getPersonInfo { (response) in 
	switch(response) {
    case .success(let personData):
    	if let data = personData as? Person {
        // 변수에 값 할당, UI 관련 작업 등 수행
        // self.nameLabel.text = data.name
      }
    case .requestErr(let message):
    	print("requestErr", message)
    case .pathErr:
    	print("pathErr")
    case .serverErr:
    	print("serverErr")
    case .networkFail:
    	print("networkFail")
  }
}
  • 아까 만들어둔 GetPersonDataService 구조체에서 shared 라는 공용 인스턴스에 접근 (싱글턴 패턴)

  • 그리고 만들어둔 getPersonInfo를 사용함

    func getPersonInfo(completion: @escaping (NetworkResult<Any>) -> Void) {
      // completion 클로저를 @escaping 클로저로 정의함
        
    }
    

    아까 계속 completion 클로저에다가

    case.failure: completion(.pathErr) //라던가
      
    let networkResult = self.judgeStatus(by: statusCode, value)
    completion(networkResult) // 이런 식으로
    

    NetworkResult형 Enum값을 넘겨줬는데, 이제 그 값을 이용해서 분기처리를 해 주면 됨!
    이미 모든 오류에 대한 처리는 Service에서 해줬으니, VC에서는 결과값에 따라서 분기처리만 하면 됨

  • case .success(let personData):
        if let data = personData as? Person {
    

    성공했을 경우에는 <T>형으로 데이터를 받아올 수 있게 해줬는데, (NetworkResult에서)
    <T> 형은 Generic하게 아무 타입이 가능하기 때문에
    우선 클로저에서 넘어오는 데이터를 let personData라고 정의해 둔 후,
    Person형이니까 거기에 if-let 구문을 통해서 옵셔널 바인딩 해 줌.

POST를 할 때 GET과의 차이점

1. body 보내기

GET과는 다르게, POST는 body에 데이터를 실어서 요청할 수 있다.

Service struct 내부에 makeParameter 함수를 만들어준다.

private func makeParameter(email: String, password: String) -> Parameters {
  return ["email": email,
         "password": password]
}

request에 parameters로 makeParameter()를 전달해준다.

let dataRequest = AF.request(URL,
                            method: .post,
                            parameters: makeParameter(email: email, password: password),
                            encoding: JSONEncoding.default,
                            headers: header)

그리고 통신 성공, status code가 200인지 보기 위해 message만 전달하려면

private judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult<Any> {
  let decoder = JSONDecoder()
  
  guard let decodeData = try? decoder.decode(LoginDataModel.self, from: data) else {
    return .pathErr
  }
  
  switch statusCode {
    case 200: return .success(decodeData.message)
    case 400: return .requestErr(decodeData.message)
    case 500: return .serverErr
    default: return .networkFail
  }
}

성공, 실패시에도 메세지를 담아서 반환

추가 : Alert에 cancel, ok Action closure 넘기기

extension UIViewController {
  func makeRequestAlert(title: String,
                       message: String,
                       okAction: ((UIAlertAction) -> Void)?,
                       cancelAction: ((UIAlertAction) -> Void)? = nil,
                       completion: (() -> Void)? = nil) {
    
    let generator = UIImpactFeedbackGenerator(style: .medium)
    generator.impactOccurred()
    
    let alertViewController = UIAlertController(title: title,
                                               message: message,
                                               preferredStyle: .alert)
    
    let okAction = UIAlertAction(title: "확인", style: .default, handler: okAction)
    alertViewController.addAction(okAction)
    
    let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: cancelAction)
    alertViewController.addAction(cancelAction)
    
    self.present(alertViewControlelr, animated: true, completion: completion)
  }
  
  func makeAlert(title: String,
                message: String,
                okAction: ((UIAlertAction) -> Void)? = nil,
                completion: (() -> Void)? = nil) {
    
    let generator = UIImpactFeedbackGenerator(style: .medium)
    generator.impactOccurred()
    
    let alertViewController = UIAlertController(title: title,
                                               message: message,
                                               preferredStyle: .alert)
    
     let okAction = UIAlertAction(title: "확인", style: .default, handler: okAction)
    alertViewController.addAction(okAction)
    
    self.present(alertViewControlelr, animated: true, completion: completion)
  }
}

이렇게 Extension으로 만들어두면 아무 뷰컨에서나 makeAlert 메서드를 호출할 수 있음.
VC에서 호출할 땐

self.makeAlert(title: "알림", message: "로그인 성공")

self.makeRequestAlert(title: "알림",
                     message: "로그인을 하시겠습니까?",
                     okAction: { _ in
                       // ok버튼 눌렀을때 하고싶은 동작
                          self.loginAction() // 통신 처리 메서드 등...
                     })