ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Server-Driven UI
    iOS 2025. 3. 7. 18:08

    안녕하세요. 그린입니다 🍏

    이번 포스팅에서는 Server-Driven UI에 대해 톺아보겠습니다 🙋🏻


    Server-Driven UI?

    서버 드리븐 UI라는건 앱의 UI를 서버에서 동적으로 정의하고, 클라이언트는 이걸 해석해서 화면을 구성하는 방식을 말해요 😃

    즉, 화면을 구성하는 컴포넌트 요소들을 JSON 등의 형식으로 서버에서 내려주면 클라이언트가 이를 해석해서 UI를 동적으로 렌더링합니다.

    결국 지향하는 목표는 앱 배포 없이 UI 변경이 자유롭고, A/B 테스트 등에 용이하다는 특징이 있죠!

     

    그럼 서버에서 어떻게 내려주는건지 한번 살펴볼께요.

     


    JSON 기반 UI 정의

    서버는 UI 요소들을 JSON 형태로 정의해서 클라이언트에 전달해줍니다.

     

    아래 서버 리스폰스 예를 볼까요?

     

    {
      "title": "메인 화면",
      "views": [
        { "type": "text", "text": "GREEN" },
        { "type": "button", "text": "Click Here", "action": "navigate", "destination": "/details" }
      ]
    }

     

    이런식으로 내려온다면, 클라이언트가 이 JSON을 해석해서 UI 컴포넌트로 변환해 렌더링을 해주는것이 핵심입니다.

     

    그럼 이 서버 드리븐 UI의 장점과 단점은 무엇이 있는지부터 살펴보고 예시를 구현해볼께요.

     


    Server-Driven UI의 장점 & 단점


    장점

    1️⃣ 배포 없이 UI 변경 가능

    - 앱 업데이트를 하지 않아도 서버에서 UI에 대한 리스폰스만 변경하여 내려준다면 바로 UI가 변경되어 나타나기에 운영 및 유지보수에 용이합니다.

    2️⃣ A/B 테스트 역량 향상

    - 사용자별로 A/B 테스트를 통해 실험을 할때 UI를 달리 제공해주기가 쉬워 비지니스적인 부분을 위해 빠르게 실험해볼 수 있습니다.

    3️⃣ 다양한 플랫폼에서 동일 UI 제공

    - iOS, Android, Web 등 여러 플랫폼에서 이 데이터를 기반으로 UI를 구성하기에 동일한 UI 구조를 서버 드리븐이 아닐때보다 잘 유지할 수 있습니다.

     


    단점

    1️⃣ 클라이언트 코드 복잡성 증가

    - JSON을 해석하고 동적으로 UI를 생성하는 로직이 들어가면서 앱 구조가 다소 복잡해질 수는 있습니다.

    2️⃣ 애니메이션 관련

    - 복잡한 UI/UX 구성 시 서버에서 내려주는 데이터로는 한계가 있어 어느정도는 그 뷰 컴포넌트에서 처리해야 되는 부분도 있습니다.

    사실 단점이라기보다는 서버 드리븐으로 UI 레이아웃 및 컴포넌트의 기본 구성 정도를 구현하는것이고 나머지 애니메이션이나 인터렉션 부분은 클라이언트에서 직접 추가하는게 맞다고 생각해요.

    3️⃣ 디버깅의 어려움

    - 서버에서 내려주는 데이터에 문제가 생긴다면 앱의 UI가 당연히 깨질 수 있어, 서버를 의존하지 않고는 디버깅이 힘들 수 있습니다.

     

    그럼 실제로 SwiftUI에서 간단하게 어떤 개발 방식인지 구현해볼까요?

     


    SwiftUI에서 Server-Driven UI 구현


    Model

    우선 데이터 모델을 정의해봅시다.

     

    import Foundation
    
    struct UIComponent: Decodable, Identifiable {
        let id = UUID()
        let type: String
        let text: String?
        let action: String?
        let destination: String?
    }
    
    struct UIScreenData: Decodable {
        let title: String
        let views: [UIComponent]
    }

     

    내려오는 데이터는 위에서 나타낸 JSON response라고 가정합니다.

     


    ViewModel

    그 다음 뷰모델 객체를 구현해봅니다.

     

    import Foundation
    import Combine
    
    class GreenViewModel: ObservableObject {
        @Published var screenData: UIScreenData?
    
        func fetchUI() {
            guard let url = URL(string: "https://green.blabla.com") else { return }
            
            URLSession.shared.dataTask(with: url) { data, _, error in
                guard let data = data else { return }
                do {
                    let decodedData = try JSONDecoder().decode(UIScreenData.self, from: data)
                    DispatchQueue.main.async {
                        self.screenData = decodedData
                    }
                } catch {
                    print("JSON Parsing Error: \(error)")
                }
            }.resume()
        }
    }

     

    간단하게 데이터를 받아와서 screenData 퍼블리셔 프로퍼티에 바인딩하는 그림입니다.

    즉, 데이터를 받아 입히면 자동으로 UI가 업데이트 되죠!

     


    View

    이제 핵심인 UI를 구현해볼까요?

     

    import SwiftUI
    
    struct GreenView: View {
        @StateObject private var viewModel = GreenViewModel()
    
        var body: some View {
            VStack {
                if let screenData = viewModel.screenData {
                    Text(screenData.title)
                        .font(.largeTitle)
                        .padding()
    
                    ForEach(screenData.views) { component in
                        createView(from: component)
                    }
                } else {
                    ProgressView("Loading...")
                }
            }
            .onAppear {
                viewModel.fetchUI()
            }
            .padding()
        }
    
        @ViewBuilder
        private func createView(from component: UIComponent) -> some View {
            switch component.type {
            case "text":
                if let text = component.text {
                    Text(text)
                        .font(.body)
                        .padding()
                }
            case "button":
                if let text = component.text {
                    Button(action: {
                        handleAction(component)
                    }) {
                        Text(text)
                            .font(.headline)
                            .padding()
                            .background(Color.blue)
                            .foregroundColor(.white)
                            .cornerRadius(8)
                    }
                }
            default:
                EmptyView()
            }
        }
    
        private func handleAction(_ component: UIComponent) {
            if let action = component.action, action == "navigate", let destination = component.destination {
                print("Navigating to \(destination)")
            }
        }
    }

     

    요렇게 구현해볼 수 있습니다.

    뷰모델의 프로퍼티를 가지고 UI를 구성해주죠.

     

    ForEach에서 views 필드로 구분되어 있어 반복을 돌면서 createView를 호출해요.

    그럼, createView에서는 해당 뷰 필드 타입에 따라 분기되어 해당하는 뷰를 데이터로 구현할 수 있습니다.

     

    그럼 이제 서버에서 데이터 순서가 변경되거나 뷰 타입이 변경되어도 앱의 코드를 고칠 필요가 없어집니다.

    다만, 새로운 뷰 타입이 추가되거나 할때는 당연히 필요하죠!

     

    여기서는 아주 간단하게 View에 넣어서 구현을 최소화해서 소개했지만, 자유롭게 Factory 패턴을 이용하거나 별도 Manager를 이용하는 등 프로젝트에 더 맞게 구현을 확장해볼 수 도 있습니다.

     

    또한, 만약 지금 Stack으로 쌓이는 구조인데, 중간 빈 여백이 들어가야 하거나 하는것도 서버에서 뷰 타입을 지정해서 내려주면 그에 맞게 처리도 할 수 있죠.

     

    즉, 서버와 얼마나 규약을 잘 정의하는가에 따라 퀄리티가 달라질거라 생각해요 🤔

     


    Server-Driven UI 적용 시 고려할 부분

    1️⃣ JSON 설계 유연성

    - 새로운 UI 요소를 쉽게 추가할 수 있도록 구조를 잘 설계해야 합니다.

    2️⃣ 캐싱 및 성능 최적화

    - API 요청을 최소화하고, UI를 빠르게 업데이트할 수 있는 로직의 구현이 필요합니다.

    3️⃣ 네트워크 오류 시

    - 실제로 서버 장애가 발생하면 아예 UI를 제공해주지 않기에 대응할 수 있는 UI를 제공하여 앱이 정상적으로 동작할 수 있도록 유연함을 가져가야 합니다.

     


    마무리

    아주 간단하게 이번 포스팅에서는 Server-Driven UI가 무엇인지를 살펴봤는데요.

    핵심은 앱 배포 없이 서버에 의존하는것에 있습니다.

    목적에 맞다면 이 방법을 적용해보는건 어떨까요?

    'iOS' 카테고리의 다른 글

    iOS에서 서버 과부하 감지 및 API 호출 최적화  (0) 2025.03.15
    Factory Pattern  (0) 2025.03.03
    카카오톡 공유하기 (메시지 템플릿)  (36) 2024.12.05
    Bring your app to Siri (feat. WWDC 2024)  (7) 2024.10.07
    Genmoji (feat. WWDC 2024)  (4) 2024.10.04
Designed by Tistory.