KINTO Tech Blog
iOS

[iOS] From UIKit + Combine to a Tailor-Made SwiftUI Architecture

Cover Image for [iOS] From UIKit + Combine to a Tailor-Made SwiftUI Architecture

This article is the entry for day 8 in the KINTO Technologies Advent Calendar 2024 🎅🎄

Introduction

Hello. My name is Nakamoto, and I work in the Mobile App Development Group. I usually work from Osaka and collaborate with team members in Tokyo on iOS development for the KINTO Unlimited app .

This article deep dives into the process of enhancing the architecture of the KINTO Unlimited iOS. The app's architecture evolved gradually, moving through three different phases, and ultimately transitioning to its current structure. I will adress its design aspect and the challenges encountered at each stage below.

The 1st Generation Architecture

  • Adopted the VIPER architechture
    • Implemented all screens using UIKit + xib/storyboard
    • Used Combine to update views
    • Chose an architecture with a proven track record within the company due to the short timeline for the first release

Design of the 1st Gen

  • ViewController

    • Notify ViewModel of an event
    • Subscribe to outputs triggered by events from the ViewModel.
    • Update the View based on the subscription results and invoke the Router to handle screen transitions.
  • ViewModel

    • Use Combine to update the state reactively.

Transform an event Publisher to output the View state through a Publisher.

  • Interactor

    • Perform requests to API and internal DB
  • Router

    • Perform transitions to other screens
  • UIView

    • Layout using code/xib/storyboard

Issue with the 1st Gen

  • Layouts built with UIKit come with high development costs and are challenging to modify, particularly when xib/storyboard is used. Transitioning to SwiftUI would significantly enhance the process!

The 2nd Generation Architecture

  • Transitioning from UIKit to SwiftUI
    • Replaced UIKit layouts with SwiftUI to enhance development efficiency.
      • Integrated a SwiftUI View into a ViewController using UIHostingController.
    • UIPerform screen transitions using UIKit as usual.
      • At that time, SwiftUI's screen transition API was unstable, so we decided to continue using UIKit.
    • Focus on switching to SwiftUI
      • Making too many changes at once could raise concerns about potential degradation of functional specifications.

Design of the 2nd Gen

  • ViewController

    • Implement HostingControllerInjectable protocol and add SwiftUI View.
    • Subscribe to the ViewModel's output and update the ScreenModel (ObservableObject) accordingly.
    • Subscribe to the ViewModel output and the ScreenModel Publisher, then utilize the Router to handle screen transitions.
  • ScreenModel

    • An ObservableObject that manages the state of the View.
  • ViewModel/ Interactor/ Router

    • Same features as the 1st Generation

Issue with the 2nd Gen

  • State management is split between the ViewModel and ScreenModel, leading to fragmented logic and increased development and maintenance costs.

  • Issues from the 1st generation

    • Using Combine for reactive state changes raises concerns about maintainability and, due to the extensive codebase, can result in reduced readability.
    • Having a single ViewModel for each screen can result in it becoming excessively large on multi-functional screens.

Therefore, transitioning away from Combine and ViewModel would be a highly beneficial improvement!

The 3rd Generation Architecture

  • Switched from a Combine-driven ViewModel to a ViewStore-based architecture that centralizes state management
    • Implemented a structure that directly updates the ObservableObject with event results, eliminating the need for AnyPublisher.
    • Utilized async/await to achieve reactive state changes without relying on Combine.
    • State management logic can be modularized by dividing it into functions.

Design of the 3rd Gen

  • ViewStore

    • State

      • An ObservableObject that manages the state of the View and is used within a SwiftUI View.
    • Action

      • Use an enum to replicate the functionality of the INPUT in the transform method of a traditional ViewModel.
    • ActionHandler

      • A handler that accepts an Action as an argument and updates the State accordingly.
      • Implement using async/await
  • ViewController

    • Subscribe to routerSubject and utilize the Router to handle screen transitions.
  • Interactor / Router

    • Same as 2nd Generation

Splitting ActionHandler

On multi-functional screens, separating the ActionHandler and State can significantly improve code readability and maintainability.

  • Binding the actionPublisher of one State to another State allows actions to be propagated from one View to another.

Conclusion

We have been pursuing this initiative for over a year, alongside ongoing feature development. Now, nearly all of the source code has been transitioned to the 3rd Generation Architecture. As a result, the code has become more readable and maintainable, paving the way for smoother future development. We are excited to continue making improvements!

Facebook

関連記事 | Related Posts

We are hiring!

【iOSエンジニア】モバイルアプリ開発G/東京

モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。

【iOSエンジニア】モバイルアプリ開発G/大阪

モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。

イベント情報

P3NFEST Bug Bounty 2025 Winter 【KINTOテクノロジーズ協賛】