[swift]RandomUser.me API

👉🏻 아래는 RandomUser.me의 API를 사용해서 유저를 앱에서 랜덤하게 호출하는 내용입니다.
Below is how to randomly call a user in your app using RandomUser.me’s API.

👉🏻 서버를 따로 구성하지않고 api를 사용하여 앱을 만들어 볼 수 있습니다.
You can create an app using the API without configuring a separate server.

👉🏻RandomUser.me Api

✔️ 브라우저에서 https://randomuser.me/api 이렇게 실행시 사용 할 수 있습니다.
You can use it by running https://randomuser.me/api in your browser.

✔️ 데이터를 새롭게 요청시 랜덤으로 아래의 api정보가 바뀝니다.
When you request new data, the API information below changes randomly.

⭐️ 코드 설명은 주석을 참조하시면됩니다. 추가 설명이 필요하면 따로 포스팅하겠습니다.
Please refer to the comments for code explanations. If further explanation is needed, I will post it separately.

{
  "results": [
    {
      "gender": "female",
      "name": {
        "title": "Mrs",
        "first": "Lisa",
        "last": "Fowler"
      },
      "location": {
        "street": {
          "number": 2580,
          "name": "Oaks Cross"
        },
        "city": "Armagh",
        "state": "Highlands and Islands",
        "country": "United Kingdom",
        "postcode": "HH16 2NA",
        "coordinates": {
          "latitude": "-10.3689",
          "longitude": "101.2611"
        },
        "timezone": {
          "offset": "+5:00",
          "description": "Ekaterinburg, Islamabad, Karachi, Tashkent"
        }
      },
      "email": "lisa.fowler@example.com",
      "login": {
        "uuid": "a46a4808-b2fd-452b-b923-212f144a5349",
        "username": "browndog164",
        "password": "bones",
        "salt": "k8ZwiUoK",
        "md5": "bc71f9e4f2a4960d2e309be7b51c1f31",
        "sha1": "ff2dde7c2e1d208e8ad05e06f5324d2d849fed0a",
        "sha256": "ee02e23c14550ae433bf8052c1d7d23fb7cd8b4fd42cf10a0900ef3e6c9c4605"
      },
      "dob": {
        "date": "1954-07-09T14:01:43.714Z",
        "age": 71
      },
      "registered": {
        "date": "2022-02-02T03:34:03.950Z",
        "age": 3
      },
      "phone": "017687 96364",
      "cell": "07149 397609",
      "id": {
        "name": "NINO",
        "value": "LP 72 77 54 S"
      },
      "picture": {
        "large": "https://randomuser.me/api/portraits/women/8.jpg",
        "medium": "https://randomuser.me/api/portraits/med/women/8.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/women/8.jpg"
      },
      "nat": "GB"
    }
  ],
  "info": {
    "seed": "42e207ecbc7085df",
    "results": 1,
    "page": 1,
    "version": "1.4"
  }
}

👉🏻 코드 / Code

✔️ ContentView.swift


import SwiftUI

struct ContentView: View {
    
    // SwiftUI는 이 ViewModel 안의 @Published 프로퍼티(user, errorMessage)가 바뀌면
    // 자동으로 뷰를 다시 그리도록 함
    // SwiftUI automatically redraws the view when the @Published properties (user, errorMessage) in this ViewModel change.
    @StateObject private var viewModel = RandomUserViewModel()
    
    var body: some View {
        VStack(spacing: 20) {
            
            // 프로필 / Profile name
            if let user = viewModel.user {
                // 프로필 네임 / profile name
                Text("\(user.name.first) \(user.name.last)")
                    .font(.title)
                
                // 프로필 이미지 URL이미지 비동기 로딩
                // Profile image URL image asynchronous loading
                AsyncImage(url: URL(string: user.picture.large)) { image in
                    image
                        .resizable()
                        .scaledToFit() // 비율유지하며 맞춤 / Custom while maintaining proportions
                        .frame(width: 180, height: 180)
                        .clipShape(Circle())
                } placeholder: {
                    ProgressView() // 로딩중에는 ProgressView표시 / Display ProgressView while loading
                }
            }
            
            // 에러 메세지 / error message
            if let error = viewModel.errorMessage {
                Text("Error: \(error)")
                    .foregroundColor(.red)
            }
            
            // 버튼 / button
            Button(action: {
                viewModel.fetchRandomUser()
            }) {
                Text("랜덤 유저 불러오기/Load random users")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(12)
            }
        }
        .padding()
    }
}


#Preview {
    ContentView()
}

✔️ RandomUserModel.swift

import Foundation

// Codable을 쓰면 JSON ↔ Struct 변환이 자동
// If you use Codable, JSON ↔ Struct conversion is automatic.
struct RandomUserResponse: Codable {
    let results: [RandomUser] // "results": [
}

struct RandomUser: Codable {
    let name: Name            // "results": [ { "name": {
    let picture: Picture
    
    struct Name: Codable {
        let first: String     // "results": [ { "name": { "first": "Lisa","last": "Fowler"
        let last: String
    }
    
    struct Picture: Codable {
        let large: String
    }
}

✔️ RandomUserSwiftApp

import SwiftUI

@main
struct RandomUserSwiftApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

✔️ RandomUserViewModel

import Foundation
import Combine // add

class RandomUserViewModel: ObservableObject {
    @Published var user: RandomUser?
    @Published var errorMessage: String?
    
    private var cancellables = Set<AnyCancellable>()
    
    func fetchRandomUser() {
        guard let url = URL(string: "https://randomuser.me/api/") else { return }
        
        /*
         Combine의 Publisher 흐름
         URLSession.shared.dataTaskPublisher(for: url)
         기본적으로 백그라운드 스레드에서 실행됨
         네트워크 응답, 데이터 디코딩 등은 비동기 작업
         이 상태에서 UI를 바로 업데이트하면 문제가 생길 수 있음
         
         Combine's Publisher Flow
         URLSession.shared.dataTaskPublisher(for: url)
         By default, runs on a background thread.
         Network responses, data decoding, etc. are asynchronous tasks.
         Updating the UI immediately in this state can cause problems.
         */
        
        URLSession.shared.dataTaskPublisher(for: url)
            //Publisher가 (data: Data, response: URLResponse) 형태인데 그중 data만 사용하겠다는 뜻
            //Publisher is in the form of (data: Data, response: URLResponse), which means that only data will be used.
            .map { $0.data }
            // JSON → Swift Struct 변환.
            // Convert JSON → Swift Struct.
            .decode(type: RandomUserResponse.self, decoder: JSONDecoder())
            // UI 업데이트는 반드시 메인 스레드에서 해야 하기 때문에 이 시점(sink) 이후 모든 downstream 작업을 메인 스레드에서 실행.(백그랄운드 스레드에서 실행시 크래시 발생할 수 있음)
            // Since UI updates must be done on the main thread, all downstream work after this point (sink) is executed on the main thread. (Crash may occur if executed on a background thread.)
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { completion in // 구독시작,에러처리 / Subscription start, error handling
                switch completion {
                case .finished:
                    break
                case .failure(let error):
                    self.errorMessage = error.localizedDescription
                }
            }, receiveValue: { response in
                // <-- 여기서 실제 @Published user 프로퍼티가 바뀜(UI갱신 시키는 위치)
                // <-- Here, the actual @Published user property changes (where the UI is updated)
                self.user = response.results.first
            })
            // 구독 유지하기 위해 저장 , inout키워드 in은 파라메터이름(레이블),.store함수가 inout키워드를 사용함
            // To maintain subscription, store, inout keyword in is parameter name (label), .store function uses inout keyword
            .store(in: &cancellables)
    }
}

👉🏻 스크린샷/ScreenShot

Leave a Reply