Bibi's DevLog 🤓🍎
[Swift] JSON API와 네트워크 통신하기 - URLSession, JSONConverter 본문
📱🍎 iOS/Code Templates
[Swift] JSON API와 네트워크 통신하기 - URLSession, JSONConverter
비비 bibi 2022. 4. 29. 00:27220426
[Swift] JSON API와 네트워크 통신하기
1. HTTPManager (URLManager) 만들기
- HTTP 요청을 보내고 그 결과를 받는 역할.
URLSession
URL
- completionHandler : '완료 처리기'
HTTPManager
import Foundation
import os
// HTTP 요청을 보내고, 그 결과를 받는 역할
final class HTTPManager {
static func requestGET(url: String, complete: @escaping (Data) -> ()) {
// complete @escaping : 클로저가 바로 실행되지 않고, 조건에 해당될 때 클로저가 실행됨
guard let validURL = URL(string: url) else { return }
var urlRequest = URLRequest(url: validURL) // URL에 보내는 URLRequest 생성
urlRequest.httpMethod = HTTPMethod.get.description
URLSession.shared.dataTask(with: urlRequest) { data, urlResponse, error in
// 비동기!! -> 서버에서 요청이 처리된 다음 실행되는 부분.
// dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
// completion handler의 내용은 모두 옵셔널로 넘어온다.
guard let data = data else { return }
guard let response = urlResponse as? HTTPURLResponse,
(200..<300).contains(response.statusCode) else { // response를 HTTPURLResponse로 바꿨을 때 statusCode가 200번대(성공)이면 계속 진행
if let response = urlResponse as? HTTPURLResponse {
os_log("%@", "\(response.statusCode)") // 아니라면 statusCode 출력
}
return
}
complete(data) // complete에 담겨 온 디코더 클로저에 data를 넘김
}.resume() // 해당 task를 실행함
}
//Post - encode된 Data를 매개변수로 받아옴
static func requestPOST(url: String, encodingData: Data, complete: @escaping (Data) -> ()) {
guard let validURL = URL(string: url) else { return }
var urlRequest = URLRequest(url: validURL)
urlRequest.httpMethod = HTTPMethod.post.description
urlRequest.httpBody = encodingData // GET과 다르게 보낼 데이터를 httpBody에 넣어준다
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("\(encodingData.count)", forHTTPHeaderField: "Content-Length")
URLSession.shared.dataTask(with: urlRequest) { data, urlResponse, error in
guard let data = data else { return }
guard let response = urlResponse as? HTTPURLResponse, (200..<300).contains(response.statusCode) else {
if let response = urlResponse as? HTTPURLResponse {
os_log("%@", "\(response.statusCode)")
}
return
}
complete(data)
}.resume()
}
//Patch - Post와 비슷함
static func requestPATCH(url: String, encodingData: Data, complete: @escaping (Data) -> ()) {
guard let validURL = URL(string: url) else { return }
var urlRequest = URLRequest(url: validURL)
urlRequest.httpMethod = HTTPMethod.patch.description
urlRequest.httpBody = encodingData
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("\(encodingData.count)", forHTTPHeaderField: "Content-Length")
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
guard let data = data else { return }
guard let response = response as? HTTPURLResponse, (200..<300).contains(response.statusCode) else {
if let response = response as? HTTPURLResponse{
os_log("%@", "\(response.statusCode)")
}
return
}
complete(data)
}.resume()
}
// Delete
static func requestDELETE(url: String, encodingData: Data, complete: @escaping (Data) -> ()) {
guard let validURL = URL(string: url) else { return }
var urlRequest = URLRequest(url: validURL)
urlRequest.httpMethod = HTTPMethod.delete.description
urlRequest.httpBody = encodingData
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
guard let data = data else { return }
guard let response = response as? HTTPURLResponse, (200..<300).contains(response.statusCode) else { return }
complete(data)
}.resume()
}
}
HTTPMethod
import Foundation
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case patch = "PATCH"
case delete = "DELETE"
var description: String {
switch self {
case .get:
return "GET"
case .post:
return "POST"
case .patch:
return "PATCH"
case .delete:
return "DELETE"
}
}
}
2. JSONConverter 만들기
- JSON을 swift객체로 변환하거나 (decoding),
- swift객체를 JSON으로 변환하는 (encoding) 역할.
- 객체 T는 Codable 프로토콜을 채택해야 한다.
import Foundation
// JSON decoding(JSON->swift객체)과 encoding(swift객체->JSON) 담당
final class JSONConverter {
static func decodeJsonArray<T: Codable>(data: Data) -> [T]? {
// JSON 배열 디코더 : JSON Array를 [T] 타입으로 변환
do {
let result = try JSONDecoder().decode([T].self, from: data)
return result
} catch { // 에러 발생 시
guard let error = error as? DecodingError else { return nil }
// error : catch 내부에서 암시적으로 사용되는 에러를 나타내는 파라미터
switch error { // 에러 유형에 따른 에러메시지 출력 가능
case .dataCorrupted(let context):
print(context.codingPath, context.debugDescription, context.underlyingError ?? "", separator: "\n")
return nil
default :
return nil
}
}
}
static func decodeJson<T: Codable>(data: Data) -> T? {
// JSON 객체 디코더 : JSON 객체를 T타입으로 변환
do {
let result = try JSONDecoder().decode(T.self, from: data) // T타입인 것 빼고 위 메서드와 동일
return result
} catch {
guard let error = error as? DecodingError else { return nil }
switch error {
case .dataCorrupted(let context):
print(context.codingPath, context.debugDescription, context.underlyingError ?? "", separator: "\n")
return nil
default :
return nil
}
}
}
static func encodeJson<T: Codable>(param: T) -> Data? {
// JSON 인코더 : T타입의 객체를 JSON으로 변환
do {
let result = try JSONEncoder().encode(param)
return result
} catch {
return nil
}
}
}
3. JSON응답 데이터에 맞는 구조체 만들기
- 프로젝트마다 새로 설계 필요
- JSON response 형식과 동일해야 함
Codable
해야 함- API key와 동일하지 않은 프로퍼티명이 있다면,
CodingKey
를 import한 enum을 선언해 해결해 주어야 함 - 유용한 사이트 : https://app.quicktype.io/
import Foundation
// MARK: - HomeData
struct HomeData: Codable {
let displayName: String
let yourRecommand: Recommand
let mainEvent: MainEvent
let nowRecommand: Recommand
enum CodingKeys: String, CodingKey {
case displayName = "display-name"
case yourRecommand = "your-recommand"
case mainEvent = "main-event"
case nowRecommand = "now-recommand"
}
}
// MARK: - MainEvent
struct MainEvent: Codable {
let imageUploadPath: String
let mobTHUM: String
enum CodingKeys: String, CodingKey {
case imageUploadPath = "img_UPLOAD_PATH"
case mobTHUM = "mob_THUM"
}
}
// MARK: - Recommand
struct Recommand: Codable {
let products: [String]
}
4-1. 네트워크 요청을 보내고 결과를 받을 Manager 클래스 만들기
- 1.에서 만든 HTTPManager와 2.에서 만든 JSONConverter를 사용해 HTTP 요청을 보내고, 그 결과를 담아 관리할 클래스 생성
import Foundation
// HTTPManager와 JSONConverter를 사용해 HTTP요청을 보내고, 그 결과를 관리하는 클래스
final class NetworkManager {
public static let publicNetworkManager = NetworkManager()
static let identifier = "NetworkManager"
static let homeDataNotification = "HomeDataNotificationName"
func getHomeData(completion: @escaping (HomeData?) -> Void) {
HTTPManager.requestGET(url: "서버API주소") { data in
// get요청의 CompletionHandler로 JSON Decoder를 보냄 : 응답 정보를 Swift객체로 변환하기 위해
// 응답 정보가 단일객체이면 decodeJson()을, 배열이면 decodeJsonArray()를 사용
guard let data: HomeData = JSONConverter.decodeJson(data: data) else {
return
}
completion(data)
}
}
}
4-2. 이미지URL을 통해 요청을 보내고 이미지를 다운로드받는 ImageCacheManager 추가 (선택)
- URL을 통해 이미지를 다운로드해야 하는 경우 구현한다
- https://bibi6666667.tistory.com/375 참고
5. 관련 ViewController에서 extension을 통해 API 호출 시점과 이후 처리 구현
'📱🍎 iOS > Code Templates' 카테고리의 다른 글
[Swift] Linked List - Singly Linked List 구현 (0) | 2022.12.15 |
---|---|
[Swift] URL에서 비동기로 이미지 다운로드 및 캐싱하기 - NSCache (0) | 2022.05.18 |
코드로 UIView 만들기 기본 템플릿 (0) | 2022.05.11 |
UIFont Extension - custom font (0) | 2022.05.11 |
UIColor extension (0) | 2022.05.11 |