솝트 4주차 세미나 복습 정리를 위한 글입니다.
다소 두서없는 글임이 예상되오니 그냥 봐주세오.
[ Alamofire를 통해 서버와 통신 해보기 - GET ]
📚 데이터를 가져오는 GET 방식에서는 서버에서 일방적으로 데이터를 요청해서 받아오는 식입니다.
📚 이때 파일 별로 나눠서 단계 별로 생각해서 정리해보자면
1. NetworkResult.swift
: 네트워크 결과를 나누기 위해서 enum 형으로 선언해줍니다.
2. XXXDataModel.swift
: JSON 데이터를 담아내기 위해 데이터 구조체를 만들고 Codable 프로토콜을 채택합니다.
3. XXXXService.swift
: 실질적인 네트워크를 처리해주는 부분으로 escaping closure를 통해서 결과값을 뷰컨에 전달합니다.
4. XXXXViewController.swift
: 싱글턴 패턴을 통해 공용 인스턴스에 접근해서 통신하는 메서드를 소출합니다.
이때 escaping closure 값을 받아서 분기처리 후 데이터를 가공해줍니다.
1️⃣ 서버 통신 결과 처리를 위해 NetworkResult.swift 파일을 만들어 주세요.
여기서 열거체 이름 옆 <T>는 타입 파라미터로 지금 당장은 타입을 정해놓지 않겠다는 뜻으로
Int, String, Bool 등 다양한 타입이 들어갈 수 있다는 뜻입니다.
import Foundation
enum NetworkResult<T> {
case success(T)
case requestErr(T)
case pathErr
case serverErr
case networkFail
}
2️⃣ 스보에 버튼을 눌렀을 때 서버에서 주는 데이터를 표시하기 위한 label 2개와 button 1개를 올리고
해당하는 뷰컨트롤러 파일을 만들어서 @IBOutlet, @IBAction을 연결해주세요.
3️⃣ 저는 포스트맨에서 이 데이터를 서버에서 받아와서 넣어주려고 합니다.
현재 데이터가 JSON 형식으로 구성되어 있기 때문에 Decode를 통해 Swift의 데이터 모델로 변환해주어야 합니다.
그렇기 위해서
데이터 모델 구조체 파일을 하나 만들고 Codable 프로토콜을 채택해줍니다.
Codable은 Decodable과 Encodable 프로토콜이 합쳐진 프로토콜입니다.
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를 써준 이유는
스위프트에서는 "profile_message"처럼 언더바를 써주지 않기 때문입니다.
그리고 [ let data : Person ]에는 두 번째 구조체인 Person이 들어갑니다.
4️⃣ 이제 통신하는 부분을 만들겁니다.
GetPersonDataService.swift 파일을 만들어 주세요.
5️⃣ 그 다음으로 뷰컨인 SampleNetworkingViewController.swift에 코드를 작성해줍니다.
데이터를 받아와서 보여주는 화면이죠.
결과화면
🦋 GetPersonDataService.swift 코드
import Foundation
import Alamofire
struct GetPersonDataService {
// 싱글턴 패턴 - static 키워드를 통해 shared 라는 프로퍼티에 싱글턴 인스턴스 저장하여 생성
// 이를 통해서 여러 VC에서도 shared로 접근하면 같은 인스턴스에 접근할 수 있는 형태
static let shared = GetPersonDataService()
// getPersonInfo 함수 생성
// completion 클로저를 @escaping closure로 정의
// -> getPersonInfo 함수가 종료되든 말든 상관없이 completion은 탈출 클로저이기 때문에, 전달된다면 이후에 외부에서도 사용가능
// ** 해당 completion 클로저에는 네트워크의 결과를 담아서 호출하게 되고, VC에서 꺼내서 처리할 예정
func getPersonInfo(completion : @escaping (NetworkResult<Any>) -> Void) {
// 데이터를 받아오려는 주소를 정의
// 필요한 헤더를 Key - Value 형태로 작성
// 보통 json 형태로 받아오기 위해서는 해당 header를 작성함
let URL = "https://mocki.io/v1/e5b82f33-832c-43ae-83c8-c3e053a4ead7"
let header : HTTPHeaders = ["Content-Type": "application/json"]
// ** "나 요렇게 통신 요청 보낼거야!!" 라고 적어둔 요청서라고 보면 됨
// URL 주소를 가지고, GET 방식을 통해, JSONEncoding 인코딩 방식으로, 헤더 정보와 함께
// Request를 보내기 위한 정보를 묶어서 dataRequest에 저장해둡니다.
let dataRequest = AF.request(URL,
method: .get,
encoding: JSONEncoding.default,
headers: header)
// 위에서 적어둔 요청설르 가지고 진짜 서버에 보내서 통신 Request를 하는 중
// 통신이 완료되면 클로저를 통해서 dataRequest라는 이름으로 결과가 도착
dataRequest.responseData { dataResponse in
// dataResponse가 도착했으니, 그 안에는 통신에 대한 결과물이 있다
// dataResponse.result는 통신 성공했는지 / 실패했는지 여부
switch dataResponse.result {
// dataResponse가 성공이면 statusCode와 response(결과데이터)를 guard let 구문을 통해서 저장해 둡니다.
case .success:
// dataResponse.statusCode는 Response의 statusCode - 200/400/500
guard let statusCode = dataResponse.response?.statusCode else {return}
// dataResponse.value는 Response의 결과 데이터
guard let value = dataResponse.value else { return }
// judgeStatus라는 함수에 statusCode와 response(결과데이터)를 실어서 보냅니다.
let networkResult = self.judgeStatus(by: statusCode, value)
completion(networkResult)
// 통신 실패의 경우, completion에 pathErr값을 담아서 뷰컨으로 날려줍니다.
// 타임아웃 / 통신 불가능의 상태로 통신 자체에 실패한 경우입니다.
case .failure: completion(.pathErr)
}
}
}
// 아까 받은 statusCode를 바탕으로 어떻게 결과값을 처리할 건지 정의합니다.
private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult<Any> {
switch statusCode {
case 200: return isValidData(data: data) // 성공 -> 데이터를 가공해서 전달해줘야 하기 때문에 isValidData라는 함수로 데이터 넘격줌
case 400: return .pathErr // -> 요청이 잘못됨
case 500: return .serverErr // -> 서버 에러
default: return .networkFail // -> 네트워크 에러로 분기 처리할 예정
}
}
// 200대로 떨어졌을 때 데이터를 가공하기 위한 함수
private func isValidData(data: Data) -> NetworkResult<Any> {
// JSON 데이터를 해독하기 위해 JSONDecoder()를 하나 선언
let decoder = JSONDecoder()
// data를 우리가 만들어둔 PersonDataModel 형으로 decode 해준다.
// 실패하면 pathErr로 빼고, 성공하면 decodedData에 값을 뺍니다.
guard let decodedData = try? decoder.decode(PersonDataModel.self, from: data) else { return .pathErr }
// 성공적으로 decode를 마치면 success에다가 data 부분을 담아서 completion을 호출합니다.
return .success(decodedData.data)
}
}
🦋 SampleNetworkingViewController.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 메소드를 사용합니다.
GetPersonDataService.shared.getPersonInfo { (response) in
// NetworkResult형 enum값을 이용해서 분기처리를 합니다.
switch(response) {
// 성공할 경우에는 <T>형으로 데이터를 받아올 수 있다고 했기 때문에 Generic하게 아무 타입이나 가능하기 때문에
// 클로저에서 넘어오는 데이터를 let personData라고 정의합니다.
case .success(let personData):
// personData를 Person형이라고 옵셔널 바인딩 해주고, 정상적으로 값을 data에 담아둡니다.
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("serveErr")
case .networkFail:
print("networkFail")
}
}
}
}
'⭐️ 개발 > iOS & Swift' 카테고리의 다른 글
[iOS] Then / DuctTape 라이브러리 사용기 (0) | 2021.06.07 |
---|---|
[Swift] Escaping Closure : 탈출 클로저란? (0) | 2021.05.14 |
[Swift] 싱글턴 패턴 : Singleton Pattern이란? (0) | 2021.05.14 |
[iOS] 데이터 직접 전달 방식(3) - Delegation을 통해 전달 (1) | 2021.04.30 |
[iOS] 데이터 직접 전달 방식(2) - Segue prepare 메소드를 통해 전달 (1) | 2021.04.29 |