본문 바로가기
개발 공부

API호출 후 데이터 가공해서 서버에 저장하기(1) (SwiftUI)

by 김 Duke 2023. 3. 31.

목차

    향수 관련 앱을 만들기 위해 향수의 정보 데이터가 필요했다.

    팀 프로젝트 때 Sephora에서 제공해주는 향수 데이터를 가져다 썼었다. 

    그 과정에서 우리 팀은 '데이터호출 / 데이터가공' 으로 역할을 나누었다.

    호출을 계속 시도하던 중 데이터가 형태가 일치하지 않는 것을 알고 가공으로 넘어가게 됐다.

    호출을 하다말아서 아쉬움이 남았고 내껄로 만들자 해서!

     그 과정을 공부해보았고 기록하고자 한다.

     

    API: RapidAPI

    데이터: Sephora

    가공: Xcode, Swift

    서버: Firebase Firestore

     

    과정


    이번 글에선 1️⃣데이터 호출에 대해 다뤄볼 것이다.

     

     

    1️⃣데이터 호출 -> 2️⃣호출한 데이터를 가지고 한번 더 호출 -> 3️⃣데이터 파싱 -> 4️⃣Firebase 올리기

     

     

     

     

    본론에 들어가기 앞서서 어떤 데이터를 쓰는지 설명이 필요할 것 같다.

    RapidAPI를 이용하여 Sephora 데이터 호출하려고 한다.

     

     

    Sephora, RapidAPI가 뭔가요 ?

    Sephora는 외국의 올리브영 같은 사이트이다.

    이 사이트에서 제공해주는 향수 데이터를 사용할 것이다.

    https://www.sephora.com/

     

     

    RapidAPI는 API를 이용하게 해주는 서비스이다.

    https://rapidapi.com/hub

     

     

    xcode에서 전처리한 이유?

    많은 향수데이터를 다루려면 자동화가 필수임

    데이터를 받아오고 가공하고 업로드까지 자동화하기 위해.

     

     

    그럼 왜 Firebase에 향수 데이터를 저장했을까? 

    - API에서 데이터 호출 시간 지연

    - 프로젝트에 맞게끔 데이터를 가공하기 위해

     

     

     

     

    데이터 호출


    본격적으로 API를 이용하여 데이터를 호출해보자.

    API란 ?

    클라이언트 - API - 데이터

    클라이언트가 데이터를 얻기위한 수단 (interface)

     

    ex)

    배달 시키는 상황을 생각해보자. 

    배민을 켜서 가게에 대한 정보를 얻고 주문까지 완료한다. 

     

    나(클라이언트) -> 인터페이스(배민) -> 데이터(가게)


     

    RapidAPI는 가입 후 과금이 필요한데 나는 공부용이라 무료 요금제를 이용했다.

     

     

    RapidAPI에서 Sephora를 검색한다.

     

     

     

    그럼 아래처럼 Sephora의 데이터를 호출할 수 있는 페이지가 나온다.

    왼쪽에서 어떤 데이터를 GET해올지 선택할 수 있는데 나는 product/list에서 향수들의 리스트를, product/detail에서 향수의 세부정보를 받아올 예정이다.

    먼저 product/list에서 향수 리스트와 몇몇 정보를 받아올 것이다.

     

    X-RapidAPI-Key 부분은 personal key이기 때문에 가려놨다.

    Sephora에서는 향수뿐만 아니라 다양한 상품들의 데이터를 제공하는데 이를 카테고리 id로 구분할 수 있다.

     

    내가 사용할 향수 카테고리의 id는 'cat60148'이다. 오른쪽의 Code snippets 부분을 보면 코드가 적혀있다.

    Code snippets의 코드는 personal key(X-RapidAPI-Key)로 URL(상수 request)의 데이터를 받아온다.

    언어에 따라 설정하면 되고 나는 Swift로 바꿔놓은 상태이다.  URL을 보면 카테고리id가 들어가있는게 볼 수 있다.

     

     

    카테고리 id를 입력하고, 파란 버튼(Test endpoint)을 눌러보면 향수 카테고리에 속한 제품들이 json 형태로 출력된다.

    살펴보면 products라는 배열에 향수의 대략적인 정보들이 Dictionary형태로 들어가있다.

    여기서 향수들의 brandName, displayName, productId, image450을 저장할 것이고,

    그 이후에는 productId를 이용하여 product/detail에서 향수의 세부정보를 뽑아올 예정이다

     

     

    우선 위에서 설명한 부분을 코드로 설명해보려한다.

    위의 code snippets를 클래스에 넣어서 함수로 만들었다.

    class APIStore {
        
        let headers = [
            "X-RapidAPI-Key": "Personal Key를 입력하세요",
            "X-RapidAPI-Host": "sephora.p.rapidapi.com"
        ]
        
        func fetchList() {
            
            let request = NSMutableURLRequest(url: NSURL(string: "https://sephora.p.rapidapi.com/products/list?categoryId=cat60148&pageSize=60&currentPage=1")! as URL,
                                              cachePolicy: .useProtocolCachePolicy,
                                              timeoutInterval: 10.0)
            request.httpMethod = "GET"
            request.allHTTPHeaderFields = headers
            
            let session = URLSession.shared
            let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
                if (error != nil) {
                    print(error)
                } else {
                    let httpResponse = response as? HTTPURLResponse
                    print(httpResponse)
                    print(data)
                }
            })
            
            dataTask.resume()
        }
    }

     

     

    dataTask의 data를 프린트해보면 옵셔널로 출력된다.

     

    요 data는 현재 json형태이다. 옵셔널 바인딩 해준 뒤, 데이터를 디코딩을 해줄 것이다.

    웹에서 보는 데이터

     

     

    위의 데이터를 우리가 보기 쉽게  만들어보면 이런 식의 json 형태의 데이터이다.

    { 
      products: [
        {	
          brandName: "CHANEL",
          displaName: "CHANCE EAU TENDRE Eau de Toilette",
          ...
        }, 
        {
          brandName: "Dior",
          displaName: "Miss Dior Blooming Bouquet",
          ...
        },
        ...
      ]
    }

     

    그래서 swift의 JSONDecoder를 사용하여 디코딩 해준다.

    디코딩을 수월하게 해줄 Model이다.

    struct FetchList: Codable {
        let products: [Product]
    }
    
    struct Product: Codable, Identifiable {
        var id: String { self.productId }
        let brandName: String
        let displayName: String
        let productId: String
        let image450: String
    }

     

     

    guard let을 이용하여 data를 바인딩해준다.

    let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
                if (error != nil) {
                    print(error)
                } else {
                    
                    guard let data = data else {return}
                    guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {return}
    
                    do {
                        let list = try JSONDecoder().decode(FetchList.self, from: data)
                        print(list.products)
                    } catch (let err) {
                        print(err)
                    }
                }
            })

     

     

    요렇게 디코딩을 해주고 list의 프로퍼티인 products를 출력해보면

    요런 식으로 향수의 정보들이 담긴 것이 보인다.(60개)

     

     

    간단하게 정리해보면 이렇다. 

    list.products = [
      Product(brandName: "CHANEL", displayName: "CHANCE EAU TENDRE Eau de Toilette", productId: "P258612", image450: "https://www.sephora.com/productimages/sku/s1237379-main-Lhero.jpg"), 
      Product(brandName: "Glossier", displayName: "Glossier You Eau de Parfum", productId: "P504364", image450: "https://www.sephora.com/productimages/sku/s2649770-main-Lhero.jpg"),
      Product(brandName: "Dior", displayName: "Miss Dior Blooming Bouquet", productId: "P385477", image450: "https://www.sephora.com/productimages/sku/s1599869-main-Lhero.jpg")
    ]

     

     

    이것을 뷰에 연결해보면 아래처럼 나온다.

     

     

    이후 내용은 다음 글에 이어서 올리겠습니다.

     

    글에 나온 코드

    ContentView

     

    //
    //  ContentView.swift
    //  API
    //
    //  Created by TAEHYOUNG KIM on 2023/03/28.
    //
    
    import SwiftUI
    
    struct ContentView: View {
        @ObservedObject var apiStore: APIStore = APIStore()
        var body: some View {
            NavigationStack {
                VStack {
                    List(apiStore.products) { product in
                        NavigationLink {
                            VStack(alignment: .leading) {
                                Text("브랜드: \(product.brandName)")
                                Text("이름: \(product.displayName)")
                                Text("Id: \(product.productId)")
                                AsyncImage(url: URL(string: product.image450)) { image in
                                    image.image?
                                        .resizable()
                                        .aspectRatio(contentMode: .fit)
                                }
                            }
                            .padding(20)
                        } label: {
                            VStack(alignment: .leading) {
                                Text(product.brandName)
                                    .font(.largeTitle)
                                Text(product.displayName)
                            }
                        }
                    }
                }
            }
            .onAppear {
                apiStore.fetchList()
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }

     

    Model

    //
    //  Model.swift
    //  API
    //
    //  Created by TAEHYOUNG KIM on 2023/03/31.
    //
    
    import Foundation
    
     struct FetchList: Codable {
        let products: [Product]
     }
    
     struct Product: Codable, Identifiable {
        var id: String { self.productId }
        let brandName: String
        let displayName: String
        let productId: String
        let image450: String
     }
    }

     

    APIstore

     

    //
    //  APIStore.swift
    //  API
    //
    //  Created by TAEHYOUNG KIM on 2023/03/28.
    //
    
    import Foundation
    
    class APIStore: ObservableObject {
        
        @Published var products: [Product] = []
        
        let headers = [
            "X-RapidAPI-Key": "Personal Key를 입력하세요",
            "X-RapidAPI-Host": "sephora.p.rapidapi.com"
        ]
        
        func fetchList() {
            
            let request = NSMutableURLRequest(url: NSURL(string: "https://sephora.p.rapidapi.com/products/list?categoryId=cat60148&pageSize=60&currentPage=1")! as URL,
                                              cachePolicy: .useProtocolCachePolicy,
                                              timeoutInterval: 10.0)
            request.httpMethod = "GET"
            request.allHTTPHeaderFields = headers
            
            let session = URLSession.shared
            
            let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
                if (error != nil) {
    //                print(error)
                } else {
                    
                    guard let data = data else {return}
                    guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {return}
    
                    do {
                        let list = try JSONDecoder().decode(FetchList.self, from: data)
                        DispatchQueue.main.async {
                            self.products = list.products
                        }
                    } catch (let err) {
                        print(err)
                    }
                }
            })
            dataTask.resume()
        }
    }

     

     

    다음 글 링크: https://yahoth.tistory.com/7

     

     

    API호출 후 데이터 가공해서 서버에 저장하기(2) (SwiftUI)

    앞글에서 API를 이용하여 1️⃣데이터 호출에 대해 다뤘다. 이번 글에선 2️⃣먼저 호출한 데이터로 다른 데이터를 호출하는 것부터 이후 3️⃣,4️⃣까지 마무리할 것이다. 1️⃣✅ 데이터 호출

    yahoth.tistory.com

     


    TOP

    Designed by 티스토리