2024.01.04 - [iOS/SWIFT] - [Swift] 네트워크 통신(1) - URL과 REST API
🩵 URLSession
데이터를 다운로드하거나 업로드하고, 웹 서버와의 통신을 위한 HTTP 요청을 생성하고 처리하는 데 활용됩니다.
- URLSession은 비동기 방식으로 작동하며
- 네트워크 작업이 완료되면 클로저를 사용하여 결과를 처리합니다.
✔️ GET
import Foundation
// URLSession 인스턴스 생성
let session = URLSession.shared
// URL 생성
if let url = URL(string: "https://api.example.com/data") {
// URLSessionDataTask를 사용하여 비동기적으로 데이터 요청
let task = session.dataTask(with: url) { (data, response, error) in
if let error = error {
print("Error: \(error)")
} else if let data = data {
// 데이터를 받아온 후 처리하는 로직을 작성
print("Received data: \(data)")
}
}
// 네트워크 요청 시작
task.resume()
}
- URLSession의 인스턴스를 생성하는데, shared 속성을 사용하여 기본 세션을 공유합니다.
- 기네트워크 요청과 응답을 처리하는 데 사용하기 위한 앱 전체에서 사용할 수 있는 공유 세션을 가져오는 것
let session = URLSession.shared
- URL 객체를 생성하는 부분입니다.
- 여기서는 주어진 문자열을 사용하여 URL을 만들고, 옵셔널 바인딩을 통해 안전하게 처리하고 있습니다.
if let url = URL(string: "https://api.example.com/data")
- URLSession의 dataTask 메서드를 사용하여 비동기적으로 데이터를 요청하는 작업을 생성합니다.
- 클로저 내부에서는 네트워크 응답에 대한 처리를 수행하고 있습니다.
let task = session.dataTask(with: url) { (data, response, error) in
.....
}
- 네트워크 요청이 시작되는 부분입니다.
- resume 메서드를 호출함으로써 요청이 서버로 전송되고, 클로저 내부의 코드가 비동기적으로 실행됩니다.
task.resume()
🩵 Decodable 프로토콜
JSON과 같은 외부 데이터를 Swift의 데이터 모델, 객체로 디코딩할 수 있도록 해주는 프로토콜입니다.
Decodable을 준수하는 객체는 외부 데이터를 해석하고 그 데이터를 객체의 프로퍼티로 매핑할 수 있어야 합니다.
네트워크에서 데이터를 받아올 때, 그 데이터를 앱 내의 데이터 모델로 변환하는 과정을 의미합니다.
Decodable을 준수하는 객체의 프로퍼티는 JSON 데이터의 키에 해당하는 이름을 가져야하며,
해당 프로퍼티의 타입은 JSON 데이터에서 예상되는 타입과 일치해야 합니다.
이를 통해 JSON 데이터의 각 필드가 Swift 객체의 프로퍼티로 매핑되어 사용될 수 있게 됩니다.
struct User: Decodable {
let id: Int
let name: String
public enum CodingKeys: String, CodingKey {
case id = "key"
case name
}
// Decoding - 디코딩
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
}
}
외부 데이터에서는 key로 사용되는 값을 우리가 만들 객체에서는 id라는 이름의 프로퍼티로 사용할 수 있게 연결시켜주는 부분이 추가되어 있습니다.
name 프로퍼티에 해당하는 JSON키는 그대로 name에 연결시켰습니다.
이런 것을 매핑이라고 합니다.
만약 이 매핑이 필요하지 않고, 프로퍼티 이름과 JSON 키의 이름이 동일하다면 별도의 CodingKeys를 선언할 필요가 없습니다.
🩵 JSON → Model 변환하기
URLSession으로 받은 Data 타입을 JSONDecoder 클래스를 사용하여 데이터를 모델 객체로 디코딩합니다.
do {
let user = try JSONDecoder().decode(User.self, from: data)
print("디코딩된 사용자: \(user)")
} catch {
print("디코딩 에러: \(error)")
}
- JSONDecoder().decode(User.self, from: data)
- SONDecoder 인스턴스를 생성하고, 해당 인스턴스를 사용하여 User 타입으로 디코딩합니다.
- decode 메서드는 디코딩이 성공하면 해당 타입의 객체를 반환합니다.
- let user = try JSONDecoder().decode(User.self, from: data)
- 디코딩 작업은 예외가 발생할 수 있으므로 try 키워드를 사용합니다.
- 디코딩이 성공하면 디코딩된 User 객체가 user 상수에 할당됩니다.
- print("디코딩된 사용자: \(user)")
- 디코딩이 성공한 경우, 디코딩된 사용자 객체(2번의 user 상수)를 출력합니다.
- catch
- 디코딩 과정에서 에러가 발생하면 catch 블록이 실행되어 해당 에러를 출력합니다.
- 이를 통해 디코딩 실패 시 에러에 대한 디버깅이 가능합니다.
🖋️ URLSession 을 통해 REST API 통신하기
✔️ API 정보
[GET] https://dummyjson.com/products/{ID}
- ID의 범위: 1 ~ 100
- Response JSON
{
"id": 1,
"title": "iPhone 9",
"description": "An apple mobile which is nothing like apple",
"price": 549,
"discountPercentage": 12.96,
"rating": 4.69,
"stock": 94,
"brand": "Apple",
"category": "smartphones",
"thumbnail": "https://i.dummyjson.com/data/products/1/thumbnail.jpg",
"images": [
"https://i.dummyjson.com/data/products/1/1.jpg",
"https://i.dummyjson.com/data/products/1/2.jpg",
"https://i.dummyjson.com/data/products/1/3.jpg",
"https://i.dummyjson.com/data/products/1/4.jpg",
"https://i.dummyjson.com/data/products/1/thumbnail.jpg"
]
}
✔️ 스토리보드
✔️ 구현 화면
✔️ 실습 코드
Decodable 프로토콜
- CodingKeys 열거형
- CodingKeys 열거형은 각 프로퍼티의 이름과 실제 JSON 데이터의 키를 매핑하는 역할을 합니다.
- 이를 통해 Swift에서 다른 이름의 프로퍼티를 사용하고 JSON에서 원하는 이름으로 디코딩할 수 있습니다.
- init(from decoder:) 초기화 메소드
- 이 메소드는 Decodable 프로토콜을 따르는 객체를 생성하기 위해 필요한 디코딩 로직이 포함되어 있습니다.
- 각 프로퍼티를 디코딩하기 위해 container를 사용하고, CodingKeys를 통해 매핑된 키를 지정하여 디코딩합니다.
- container
- Decoder에서 제공하는 container를 통해 디코딩(외부 데이터에서 내부 데이터로의 변환 작업)을 수행합니다.
- container는 키-값 쌍을 가지고 있고, 이를 통해 JSON 데이터의 각 프로퍼티에 접근합니다.
- 그리고 해당 값을 내부 데이터 모델의 프로퍼티에 매핑할 수 있습니다.
Int, String, Double, [String] 등 각 프로퍼티에 해당하는 데이터 타입으로 디코딩을 수행하고 있습니다.
프로퍼티의 타입과 JSON 데이터의 형식이 일치해야 올바르게 디코딩됩니다.
struct Product: Decodable {
// MARK: 프로퍼티 정의
let key: Int // 원래는 id 인데 이름을 key 로 바꿔봄 (1)
let productTitle: String // 원래는 title 인데 이름을 productTitle 로 바꿔봄 (1)
let description: String
let price: Int
let discountPercentage: Double
let rating: Double
let stock: Int
let brand: String
let category: String
let thumbnail: String
let images: [String]
// MARK: CodingKeys 열거형
// 프로퍼티의 이름과 실제 JSON 데이터의 키를 매핑하는 역할
// 이를 통해 Swift에서 다른 이름의 프로퍼티를 사용하고 JSON에서 원하는 이름으로 디코딩할 수 있음
public enum CodingKeys: String, CodingKey {
// "id" 키를 사용하여 key 프로퍼티 디코딩
case key = "id" // 원래는 id 인데 이름을 key 로 바꿔봄 (2)
// "title" 키를 사용하여 productTitle 프로퍼티 디코딩
case productTitle = "title" // 원래는 title 인데 이름을 productTitle 로 바꿔봄 (2)
case description
case price
case discountPercentage
case rating
case stock
case brand
case category
case thumbnail
case images
}
// MARK: init(from decoder:) 초기화 메소드
// Decodable 프로토콜을 따르는 객체를 생성하기 위해 init(from:) 초기화 메소드 구현
// 각 프로퍼티를 디코딩하기 위해 container를 사용하고, CodingKeys를 통해 매핑된 키를 지정하여 디코딩
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
key = try container.decode(Int.self, forKey: .key) // 원래는 id 인데 이름을 key 로 바꿔봄 (3)
productTitle = try container.decode(String.self, forKey: .productTitle) // 원래는 title 인데 이름을 productTitle 로 바꿔봄 (3)
description = try container.decode(String.self, forKey: .description)
price = try container.decode(Int.self, forKey: .price)
discountPercentage = try container.decode(Double.self, forKey: .discountPercentage)
rating = try container.decode(Double.self, forKey: .rating)
stock = try container.decode(Int.self, forKey: .stock)
brand = try container.decode(String.self, forKey: .brand)
category = try container.decode(String.self, forKey: .category)
thumbnail = try container.decode(String.self, forKey: .thumbnail)
images = try container.decode([String].self, forKey: .images)
}
}
JSON → Model 변환하기
- URL 생성 및 URLSession 인스턴스 생성:
- URL(string: "https://dummyjson.com/products/\(productID)")를 통해 지정된 productID를 이용하여 요청할 URL을 생성합니다.
- URLSession.shared를 사용하여 앱 전체에서 공유되는 기본 URLSession 인스턴스를 가져옵니다.
- URLSessionDataTask를 사용하여 비동기적으로 데이터 요청
- URLSession.shared.dataTask(with: url)를 호출하여 비동기적으로 데이터를 요청하는 URLSessionDataTask를 생성합니다.
- 클로저 내에서 데이터 요청이나 다른 비동기 작업이 완료된 후에 UI를 업데이트하는 목적으로 DispatchQueue.main.async를 사용하여 UI 작업을 수행합니다.
- 데이터 처리 및 디코딩
- data, response, error 파라미터를 사용하여 네트워크 요청 결과를 처리합니다.
- 만약 에러가 발생하면 에러를 출력하고 함수 실행을 종료합니다.
- 성공적으로 데이터를 받아오면 JSONDecoder를 사용하여 Product 모델로 디코딩합니다.
func decodeing() {
if let url = URL(string: "https://dummyjson.com/products/\(productID)") {
// 1. URLSession 인스턴스 생성
// 2. URLSessionDataTask를 사용하여 비동기적으로 데이터 요청
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
// 네트워크 요청이나 다른 비동기 작업이 완료된 후에 UI를 업데이트하는 목적
DispatchQueue.main.async {
self.hideSkeletonView()
}
if let error = error {
print("Error: \(error)")
} else if let data = data {
// 데이터를 받아오고 처리하는 로직 작성
do {
// URLSession으로 받은 Data 타입을
// JSONDecoder 클래스를 사용하여 데이터를 모델 객체로 디코딩
let product = try JSONDecoder().decode(Product.self, from: data)
// 네트워크 요청이 완료되고 상품 정보가 성공적으로 디코딩되면 호출되는 부분
// 실제 데이터로 UI를 업데이트
DispatchQueue.main.async {
self.updateUIWithProduct(product)
}
print("Decoded Product:")
print("ID: \(product.key)") // 원래는 id 인데 이름을 key 로 바꿔봄 (4)
print("Title: \(product.productTitle)") // 원래는 title 인데 이름을 productTitle 로 바꿔봄 (4)
print("Description: \(product.description)")
print("Price: \(product.price)")
print("Discount Percentage: \(product.discountPercentage)")
print("Rating: \(product.rating)")
print("Stock: \(product.stock)")
print("Brand: \(product.brand)")
print("Category: \(product.category)")
print("Thumbnail: \(product.thumbnail) \(type(of: product.thumbnail))")
print("Images: \(product.images)")
} catch {
print("Decode Error: \(error)")
}
}
}
task.resume()
}
}
'iOS > Swift' 카테고리의 다른 글
[Swift] filter() vs first() - 차이점, 시간복잡도, 디버깅 (1) | 2024.01.29 |
---|---|
[Swift] 아키텍처 Architecture(1) - MVC 패턴 (1) | 2024.01.27 |
[Swift] 네트워크 통신(1) - URL과 REST API (1) | 2024.01.04 |
[Swift] Delegate Pattern에서 AnyObject를 사용하는 이유는? (0) | 2024.01.02 |
[Swift] UIStackView 사용, UILabel.font 속성 연속 사용, @objc 어노테이션 (0) | 2023.12.27 |