이번 주차는 위시리스트 앱을 만드는 과제를 만들어 보고있다.
네트워크 통신 공부도 함께 진행하면서 URL Session에 대해 공부하는 시간도 가졌다.
가장 첫 단계인 API를 통해 데이터를 받아오는 내용을 정리해보려 한다.
URLSession 이해하기
URL Session은 네트워크 데이터를 가져오거나 보내는 작업을 수행한다.
주요 특징으로는 비동기적으로 네트워크 요청을 처리하므로, 네트워크 작업이 백그라운드에서 수행될 수 있다.
URL Session을 사용하여 데이터를 업로드하거나 다운로드 할 수 있고, , JSON, 이미지, 파일 등 다양한 데이터 형식을 처리할 수 있다.
이번 챕터의 과제인 위시리스트 앱에서는 JSON 데이터를 다운로드하여 사용한다.
데이터 모델링
우선 데이터 구조를 작성하기 전에 JSON Dummy API 를 살펴보자!
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"
]
}
위의 API에서 받아올 데이터 형식과 동일한 프로퍼티 이름을 사용해서 데이터 객체를 아래와 같이 구성했다.
struct RemoteProduct: Decodable {
let id: Int
let title: String
let description: String
let price: Double
let thumbnail: URL
}
- 💡Decodable이란?
Decodable 프로토콜은 데이터를 객체로 디코딩할 때 사용된다.
즉, 외부 데이터(JSON)를 Swift의 데이터 모델로 변환하는데에 필요한 프로토콜이다!
URLSession 을 통해 데이터 가져오기
URLSession을 통해 RemoteProduct를 가져오는 과정을 알아보자.
1) NetworkingManager 클래스
class NetworkingManager {
let url = "https://dummyjson.com/products/"
}
우선 네트워크를 관리하는 네트워킹 매니저 클래스를 생성했다.
URLSession을 사용해 원격 제품 데이터를 가져오는 역할을 한다.
2) URL 인스턴스 생성 & completion
func fetchRemoteProduct(completion: @escaping (Result<RemoteProduct, Error>) -> Void) {
let session = URLSession.shared
let productID = Int.random(in: 1 ... 100)
사용할 URLSession 인스턴스를 생성한다.
가져올 제품 ID는 1부터 100 사이의 숫자 중 랜덤으로 생성했다.
- 💡completion이란?
completion 클로저는 비동기적으로 제품 정보를 가져온 후에 호출되는 핸들러로 호출된 시점에 다른 작업을 수행할 수 있다!
3) URL 생성
if let url = URL(string: "\(url)\(productID)")
앞에서 생성한 url정보와 제품 ID를 이용하여 실제 제품 정보를 가져올 URL을 생성한다.
4) URLSessionDataTask를 사용하여 비동기적으로 데이터 요청
let task = session.dataTask(with: url) { (data, response, error) in
URLSession의 dataTask 메서드를 사용하여 비동기적으로 데이터를 요청한다.
요청 결과는 클로저로 전달된다.
if let error = error {
print("Error: \(error)")
completion(.failure(error))
}
데이터를 요청하는 과정에서 에러가 발생하면 에러를 출력하고 completion 핸들러에 실패를 전달한다.
else if let data = data {
do {
let product = try JSONDecoder().decode(RemoteProduct.self, from: data)
completion(.success(product))
print("Decode Product: \(product)")
} catch {
completion(.failure(error))
print("Decode Error: \(error)")
}
}
데이터가 정상적으로 수신되면 JSONDecoder를 사용하여 데이터를 RemoteProduct 구조체로 디코딩한다.
성공적으로 디코딩되면 디코딩된 제품 정보를 completion 핸들러에 성공적으로 전달한다.
디코딩 과정에서 에러가 발생하면 에러를 출력하고 completion 핸들러에 실패를 전달한다.
5) 네트워크 요청 시작
task.resume()
URLSession의 dataTask 메서드를 호출하여 네트워크 요청을 시작한다.
NetworkingManager 전체 코드
class NetworkingManager {
let url = "https://dummyjson.com/products/"
func fetchRemoteProduct(completion: @escaping (Result<RemoteProduct, Error>) -> Void) {
// URLSession 인스턴스 생성
let session = URLSession.shared
let productID = Int.random(in: 1 ... 100)
// URL 생성하여 RemoteProduct 가져오기
if let url = URL(string: "https://dummyjson.com/products/\(productID)") {
// URLSessionDataTask 사용하여 비동기적으로 데이터 요청
let task = session.dataTask(with: url) { (data, response, error) in
if let error = error {
print("Error: \(error)")
completion(.failure(error))
} else if let data = data {
do {
let product = try JSONDecoder().decode(RemoteProduct.self, from: data)
completion(.success(product))
print("Decode Product: \(product)")
} catch {
completion(.failure(error))
print("Decode Error: \(error)")
}
}
}
// 네트워크 요청 시작
task.resume()
}
}
}
👉 위의 과정을 통해 URLSession으로 제품 데이터를 가져올 수 있다.
제품 정보 화면에 표시
원격 서버에서 데이터가 잘 받아졌으니 제품 정보를 화면에 표시해보자.
우선 받아온 데이터를 화면에 보여줄 컴포넌트들을 연결해준다.
1) 제품 정보 가져오기
networkingManager.fetchRemoteProduct { result in
switch result {
case .success(let product):
print("제품 정보 받아오기 성공")
}
case .failure(let error):
print("Error fetching product: \(error)")
}
}
앞에서 작성한 NetworkingManager의 fetchRemoteProduct를 호출한다.
fetchRemoteProduct 메서드는 원격 제품 정보를 가져오는 비동기 작업을 수행하며 결과를 클로저 형태로 반환한다.
result에는 제품 정보를 가져오는 작업의 결과가 포함된다.
2) UI 요소들과 데이터를 연결하여 표시
DispatchQueue.main.async {
self.imageView.loadImage(url: product.thumbnail)
self.titleLabel.text = product.title
self.descriptionLabel.text = product.description
self.priceLabel.text = product.price.formatAsCurrency()
}
UI 업데이트는 메인 스레드에서 이루어져야 하기에 따라서 DispatchQueue.main.async 블록 내에서
UI 요소들과 제품 정보를 연결하여 업데이트해주자.
- 💡 DispatchQueue.main.async이란?
- DispatchQueue.main: 메인 스레드의 DispatchQueue를 나타낸다.
- async: 주어진 클로저를 비동기적으로 실행한다.
따라서 DispatchQueue.main.async는 메인 스레드에서 비동기적으로 작업을 실행하도록 보장하여 UI 업데이트와 같은 작업이 스무딩하게 처리될 수 있도록 한다!
fetchRemoteProduct() 전체 코드
func fetchRemoteProduct() {
networkingManager.fetchRemoteProduct { result in
switch result {
case .success(let product):
// UI 요소들과 데이터를 연결하여 표시
DispatchQueue.main.async {
self.imageView.loadImage(url: product.thumbnail)
self.titleLabel.text = product.title
self.descriptionLabel.text = product.description
self.priceLabel.text = product.price.formatAsCurrency()
}
case .failure(let error):
print("Error fetching product: \(error)")
}
}
}
전송받은 url을 이용하여 UIImageView에 띄우기
전달 받은 url을 이용하여 사진을 띄워보자.
UIImageView extension 작성
extension UIImageView {
func loadImage(url: URL) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let data = data,
let image = UIImage(data: data)
else {
print("Failed to load image from \(url): \(String(describing: error))")
return
}
DispatchQueue.main.async {
self.image = image
}
}.resume()
}
}
이제 메서드를 적용해주면 이미지가 로드되는 걸 확인할 수 있다!
결과 화면
앗 그리구 이번 과제부터는 커밋메세지도 신경 써서 작성해봤는데 깔끔해서 변경 기록을 알아보기에 좋은 것 같다~!
'TIL✏️' 카테고리의 다른 글
[iOS] WishList App - ScrollView 적용, Pull to Refresh (4) | 2024.04.18 |
---|---|
[iOS] WishList App - CoreData 사용하기 (2) | 2024.04.17 |
[iOS] 키오스크 앱 프로젝트 - 주문 내역 화면 (1) | 2024.04.03 |
[iOS] 키오스크 앱 프로젝트 - 스토리보드 초기 뷰 컨트롤러 설정 (1) | 2024.04.02 |
[iOS] 키오스크 앱 프로젝트 - GitHub 초기 작업 (1) | 2024.04.02 |