티스토리 뷰
POSTMAN
서버가 구현한 API를 테스트 할 수 있는 플랫폼
포스트맨은 서버 데이터가 현재 어떻게 나오고 있는지 파악하기 위해서 테스트 용도로 사용!
혹은 response body 확인
-> 멀쩡하게 이미지가 출력이 되던 부분이 갑자기 출력이 안될 때
포스트맨에서 해당 URL 찍어서 현재 데이터가 어떻게 넘어오고 있는지 파악!
![](https://blog.kakaocdn.net/dn/bkFdEA/btrcA2zH0ao/PaC8XdmyjFog605Bw5u5qk/img.png)
![](https://blog.kakaocdn.net/dn/cilw4L/btrcFWFrDCm/oM8tEiBD1rTakkDWTwbC0K/img.png)
![](https://blog.kakaocdn.net/dn/bQC1gU/btrcFXjSIos/16c5oOWmKiKeyjFtknM0Dk/img.png)
![](https://blog.kakaocdn.net/dn/M876I/btrczQlPgay/kxKrxNmXpQxMWHcJaoxbWK/img.png)
Alamofire
pod init
vi Podfile
pod install
![](https://blog.kakaocdn.net/dn/deV928/btrcB3ZoINY/FuTDDDyWyuNTEnHGI7lf61/img.png)
"Allow Arbitary Loads" 해당 속성을 YES로 설정해야 빌드시에 네트워크 통신이 가능하다!
1. NetworkResult.swift
import Foundation enum NetworkResult<T> { case success(T) -> 서버 통신 성공했을 때 case requestErr(T) -> 요청 에러 발생했을 때 case pathErr -> 경로 에러 발생했을 때 case serverErr -> 서버의 내부적 에러가 발생했을 때 case networkFail -> 네트워크 연결 실패했을 때 }
서버 통신 결과를 처리하기 위해 파일을 만들어줌 !
success(T) -> 서버 통신 성공했을 때
requestErr(T) -> 요청 에러 발생했을 때
pathErr -> 경로 에러 발생했을 때
serverErr -> 서버의 내부적 에러가 발생했을 때
networkFail -> 네트워크 연결 실패했을 때
<T> 의 의미?
타입 파라미터로 지금 당장 타입을 정해 놓지 않겠다! 라는 뜻
해당 자리에는 Int, String, Bool 등 다양한 타입이 들어갈 수 있다.
2. PersonDataModel.swift
import Foundation // MARK: - PersonDataModel struct PersonDataModel: Codable { let status: Int let success: Bool let message: String let data: Person } // MARK: - Person struct Person: Codable { let name, profileMessage: String enum CodingKeys: String, CodingKey { case name case profileMessage = "profile_message" } }
* CodingKeys 와 Codable 개념 적용 !
2021.08.19 - [Sopt 28th 세미나 - iOS] - <4주차 세미나> Encode / Decode
<4주차 세미나> Encode / Decode
HTTP 프로토콜 서버와 클라이언트는 "정해진 형태"로 "요청"과 "응답"을 주고 받는다. 클라이언트 -> 서버에게 HTTP 방식으로 요청할 때에는 여러가지 방법이 존재! GET -> 데이터를 얻고 싶을 때 요청
seungchan.tistory.com
3. GetPersonDataService.swift
// GetPersonDataService.swift import Foundation import Alamofire struct GetPersonDataService { static let shared = GetPersonDataService() func getPersonInfo(completion : @escaping (NetworkResult<Any>) -> Void) { // completion 클로저를 @escaping closure로 정의합니다. let URL = "https://mocki.io/v1/e5b82f33-832c-43ae-83c8-c3e053a4ead7" let header : HTTPHeaders = ["Content-Type": "application/json"] let dataRequest = AF.request(URL, method: .get, encoding: JSONEncoding.default, headers: header) dataRequest.responseData { dataResponse in 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, value) completion(networkResult) case .failure: completion(.pathErr) } } } 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 } } 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(해독)을 한번 거칩니다. 실패하면 pathErr // 해독에 성공하면 Person data를 success에 넣어줍니다. return .success(decodedData.data) } }
위의 코드를 쪼개서 살펴보겠다 !
1. static let shared = GetPersonDataService()
싱글턴 패턴의 적용 !
static 을 활용해서 shared 라는 이름으로, GetPersonDataService 싱글턴 인스턴스를 만들었다!
이렇게 선언해놓으면 여러 뷰컨에서도 shared로 접근하면 같은 인스턴스에 접근 할 수 있다.
2. func getPersonInfo(completion : @escaping (NetworkResult<Any>) -> Void)
getPersonInfo 라는 메서드를 만들었다.
@escape 키워드를 사용해 escape closure 형태로 completion 정의하고 있다.
getPersonInfo의 함수가 종료되든 말든 상관없이, completion은 탈출 클로저여서, 전달된다면 이후 외부에서도 사용 가능하다.
여기에서는 용도를! 해당 네트워크 작업이 끝날 때 ->
completion 클로저에 (네트워크 성공 / 서버에러 / 네트워크 에러...) 와 같은 네트워크의 결과를 담아서 호출!
담은 네트워크 결과는 이후에 ViewController 에서 꺼내서 처리!
3. let URL = "https://mocki.io/v1/e5b82f33-832c-43ae-83c8-c3e053a4ead7" let header : HTTPHeaders = ["Content-Type": "application/json"]
데이터를 받아오려는 주소를 정의! 필요한 헤더를 KEY - VALUE 형태로 작성!
보통 json 형태로 받아오기 위해서 해당 header를 작성하는데 나중에 서버 붙일 때, Header 정보가 필요한지 서버 문서를 통해 확인!
4. let dataRequest = AF.request(URL, method: .get, encoding: JSONEncoding.default, headers: header)
우리는 (주소)를 가지고, (GET) 방식을 통해, (JSONEncoding) 인코딩 방식으로, (헤더) 정보와 함께 Request를 보내기 위한 정보를 묶어서 dataRequest에 저장! -> 요청서 개념
5. dataRequest.responseData { dataResponse in
위에서 적어둔 요청서 (dataRequest)를 가지고 진짜 서버에 보내서 통신 Request를 하는 중!
통신이 완료되면 클로저를 통해 dataResponse라는 이름으로 결과가 도착!
6. switch dataResponse.result {
dataResponse가 도착했으니, 그 안에는 통신에 대한 결과물이 들어있다
중요한 몇개를 알아보자면
dataResponse.result
통신 성공했는지 실패했는지 여부
dataResponse.reponse?.statusCode
Response의 statusCode
dataResponse.value
Response의 결과 데이터
Reponse의 statusCode를 조금 더 자세히 살펴보면
200대: 클라이언트의 요청을 정상적으로 수행
400대: 클라이언트의 요청이 부적절한 경우
500대: 서버에 문제가 있을 경우
7. guard let statusCode = dataResponse.response?.statusCode else {return} guard let value = dataResponse.value else {return} let networkResult = self.judgeStatus(by: statusCode, value) completion(networkResult)
만약 해당 dataReponse가 성공이라면 -> 필요한 정보는 2가지
statusCode 값과 reponse 결과 데이터!
그래서 guard let 구문을 통해 안전하게 값을 statusCode 와 value에 저장한다.
그리고 judgeStatus 라는 함수에 statusCode와 reponse 결과 데이터를 실어 보낸다.
*** 절대 헷갈리면 안되는 것은, statusCode가 400대로 떨어졌을 때도 .success로 빠진다는 것!
dataReponse.result의 실패는 타임아웃과 경우/혹은 불가능한 상태로 통신 자체에 실패한 경우를 의미
400대로 빠진다는 것 자체가 일단 서버에서 값을 주긴 준다는 뜻!
8. case .failure: completion(.pathErr)
통신 실패하면 가차없이 completion에다가 pathErr(통신 실패) 값을 담아서 뷰컨으로 날려준다.
-> 탈출 클로저니까 클로저가 밖으로 나가는 것이 가능!
9. private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult<Any> {
judgeStatus 라는 함수에서는 아까 받은 statusCode를 바탕으로 어떻게 결과값을 처리할지 정의!
10. switch statusCode { case 200: return isValidData(data: data) case 400: return .pathErr case 500: return .serverErr default: return .networkFail }
200대인 경우
-> 성공! 그러면 데이터를 가공해서 전달해줘야 하기 때문에 isValidData라는 함수로 데이터를 넘겨준다! (데이터를 여기서 하겠다는 뜻)
400대인 경우
-> 클라이언트의 요청이 부적절한 경우 / .pathErr를 리턴해준다.
500대인 경우
-> 서버에 문제가 있을 경우
기타
-> 네트워크 에러로 분기처리!
200대를 제외하고는 return값으로 NetworkResult 형을 반환한다.
여기서 반환된 값은 어디로 가냐면
networkResult로 저장이 된다.. 요 저장된 networkResult 값을 completion 클로저에 실어서 뷰컨으로 날림!
뷰컨에서는 그러면 networkResult 값을 받아서 분기를 나눠서 처리해주면 되겠다..!
11. private func isValidData(data : Data) -> NetworkResult<Any> {
isValidData 라는 함수에서는 200대로 떨어졌을 때 -> 데이터를 가공하기 위한 함수
12. let decoder = JSONDecoder() guard let decodedData = try? decoder.decode(PersonDataModel.self, from: data) else { return .pathErr} // 우선 PersonDataModel 형태로 decode(해독)을 한번 거칩니다. 실패하면 pathErr
JSON 데이터를 해독하기 위해 JSONDecoder 선언
data를 우리가 아까 만들어둔 PersonDataModel 형으로 decode 시킨다.
실패하면 .pathErr로 빼버리고
성공하면 decodedData에는 값이 담겨있다.
13. // 해독에 성공하면 Person data를 success에 넣어줍니다. return .success(decodedData.data)
성공적으로 decode까지 마치면...
success에다가 data부분을 담아서 completion 호출을 한다.
그렇게 되면 뷰컨에서 요 data를 빼서 사용이 가능!
ViewController.swift
import UIKit class SampleNetworkViewController: UIViewController { @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var messageLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() } @IBAction func getButtonClicked(_ sender: Any) { GetPersonDataService.shared.getPersonInfo { (response) in switch(response) { case .success(let personData): if let data = personData as? Person { self.nameLabel.text = data.name self.messageLabel.text = data.profileMessage } case .requestErr(let message) : print("requestERR",message) case .pathErr : print("pathERR") case .serverErr: print("serverERR") case .networkFail: print("networkFail") } } } }
위의 코드도 쪼개서 살펴볼게요..
1. GetPersonDataService.shared.getPersonInfo { (response) in switch(response)
버튼을 누르면 통신을 진행하는 코드를 작성해보겟다.
아까 만들어둔 GetPersonDataService 구조체에서 shared라는 공용 인스턴스에 접근(싱글턴 패턴)
그리고 만들어둔 getPersonInfo 를 사용 !
아까 계속 completion 클로저에다가 NetworkResult형 enum 값을 넣어줬었는데
이제 그 값을 이용해서 분기처리를 하겠다.
서버 통신 성공 / 네트워크 오류 / 경고 오류 등 (success / networkFail / pathErr)
이미 모든 처리는 GetPersonDataService에서 다 처리를 해줬기 때문에
ViewController에서는 그 결과값에 따라서 분기처리만 하면 된다.
2. case .success(let personData): if let data = personData as? Person { self.nameLabel.text = data.name self.messageLabel.text = data.profileMessage }
성공했을 경우 <T>형으로 데이터를 하나 받아올 수 있다.
<T> 형은 Generic하게 아무 타입이 가능하기 때문에 클로저에서 넘어오는 데이터를 let personData라고 정의하겠다!
우린 personData가 Person 형이라는 것을 알고
그래서 if - let 구문을 통해 옵셔널 바인딩을 해주고, 정상적으로 값을 data에 담아두게 된다.
3. case .requestErr(let message) : print("requestERR",message) case .pathErr : print("pathERR") case .serverErr: print("serverERR") case .networkFail: print("networkFail")
실패했을 때, 분기처리는 다음 case 들에서 진행..!
'Sopt 28th 세미나 - iOS' 카테고리의 다른 글
[Swift] Alamofire (POST) (0) | 2021.08.20 |
---|---|
[Swift] Alamofire (GET) 요약 (0) | 2021.08.20 |
[Swift] Escaping Closure & Singleton Pattern (0) | 2021.08.20 |
[Swift] Encode / Decode (0) | 2021.08.19 |
[Swift] Animation (0) | 2021.08.16 |