Upgrade to Pro — share decks privately, control downloads, hide ads and more …

pixivのリアーキテクチャにおける
The Composable Architecter活用

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for tatsubee tatsubee
October 24, 2024
170

pixivのリアーキテクチャにおける
The Composable Architecter活用

現在、pixivのiOSアプリでは、The Composable Architecture (TCA) を用いたリアーキテクチャを進めています。TCAはSwiftUIとの組み合わせで使用されることが多い印象がありますが、UIKitでも問題なく活用できる上、最近ではUIKitに対するサポートも強化されています。

本発表では、UIKitを主に使用しているpixivアプリが、TCAを導入したことによって得られた恩恵や、UIKit×TCA実装の知見を紹介します!

Avatar for tatsubee

tatsubee

October 24, 2024
Tweet

More Decks by tatsubee

Transcript

  1. ViewControllerͰঢ়ଶΛ؂ࢹ͢Δ observe(_:)ͷ࢖͍ํ final class FeatureViewController: UIViewController { let store: StoreOf<Feature>

    let label = UILabel() … override func viewDidLoad() { super.viewDidLoad() observe { [weak self] in guard let self else { return } self.label.text = self.store.text } } }
  2. ViewControllerͰঢ়ଶΛ؂ࢹ͢Δ observe(_:)ͷ஫ҙ఺ • observeͷ෼ׂ final class FeatureViewController: UIViewController { let

    titleLabel = UILabel() let counterLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() observe { [weak self] in guard let self else { return } self.titleLabel1.text = self.store.text1 self.counterLabel2.text = “\(self.store.count)” } } }
  3. ViewControllerͰঢ়ଶΛ؂ࢹ͢Δ observe(_:)ͷ஫ҙ఺ • observeͷ෼ׂ final class FeatureViewController: UIViewController { let

    titleLabel = UILabel() let counterLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() observe { [weak self] in guard let self else { return } self.titleLabel1.text = self.store.text1 } observe { [weak self] in guard let self else { return } self.counterLabel2.text = “\(self.store.count)” } } }
  4. ViewControllerͰঢ়ଶΛ؂ࢹ͢Δ observe(_:)ͷ஫ҙ఺ • Task final class FeatureViewController: UIViewController { let

    label = UILabel() override func viewDidLoad() { super.viewDidLoad() observe { [weak self] in guard let self else { return } Task { try? await Task.sleep() self.label.text = self.store.text } } } }
  5. ViewControllerͰঢ়ଶΛ؂ࢹ͢Δ observe(_:)ͷ஫ҙ఺ • Task final class FeatureViewController: UIViewController { let

    label = UILabel() override func viewDidLoad() { super.viewDidLoad() observe { [weak self] in guard let self else { return } Task { try? await Task.sleep() self.label.text = self.store.text } } } } มߋΛ௥੻Ͱ͖ͳ͍
  6. ViewControllerͰঢ়ଶΛ؂ࢹ͢Δ observe(_:)ͷ஫ҙ఺ • Task final class FeatureViewController: UIViewController { let

    label = UILabel() override func viewDidLoad() { super.viewDidLoad() observe { [weak self] in guard let self else { return } let text = self.store.text Task { try? await Task.sleep() self.label.text = text } } } }
  7. υϝΠϯΛࡉ෼Խ͢Δ SwiftUIͷྫ @Reducer struct ListFeature: Reducer { @ObservableState struct State:

    Hashable { … } enum Action { case onAppeared case rowTapped case rowLongTapped } var body: some ReducerOf<Self> { … } }
  8. υϝΠϯΛࡉ෼Խ͢Δ SwiftUIͷྫ @Reducer struct ListFeature: Reducer { @ObservableState struct State:

    Hashable { … } enum Action { case onAppeared } var body: some ReducerOf<Self> { … } } @Reducer struct RowFeature: Reducer { @ObservableState struct State: Hashable, Identifiable { … } enum Action { case rowTapped case rowLongTapped } var body: some ReducerOf<Self> { … } }
  9. υϝΠϯΛࡉ෼Խ͢Δ SwiftUIͷྫ @Reducer struct ListFeature: Reducer { @ObservableState struct State:

    Hashable { … } enum Action { case onAppeared } var body: some ReducerOf<Self> { … } }
  10. υϝΠϯΛࡉ෼Խ͢Δ SwiftUIͷྫ @Reducer struct ListFeature: Reducer { @ObservableState struct State:

    Hashable { … var rows: IdentifiedArrayOf<RowFeature.State> = [] } enum Action { case onAppeared case rows(IdentifiedActionOf<RowFeature>) } var body: some ReducerOf<Self> { Reduce { state, action in … } } }
  11. υϝΠϯΛࡉ෼Խ͢Δ SwiftUIͷྫ @Reducer struct ListFeature: Reducer { @ObservableState struct State:

    Hashable { … var rows: IdentifiedArrayOf<RowFeature.State> = [] } enum Action { case onAppeared case rows(IdentifiedActionOf<RowFeature>) } var body: some ReducerOf<Self> { Reduce { state, action in … } .forEach(\.rows, action: \.rows) { RowFeature() } } }
  12. υϝΠϯΛࡉ෼Խ͢Δ SwiftUIͷྫ struct ListView: View { let store: StoreOf<ListFeature> =

    .init( initialState: ListFeature.State() ) { ListFeature() } var body: some View { ForEach(store.scope(state: \.rows, action: \.rows)) { RowView(store: $0) } } }
  13. υϝΠϯΛࡉ෼Խ͢Δ UIKitͰ࢖͍ಘΔύλʔϯ • UITableView final class ListViewController: UITableViewController { let

    store: StoreOf<ListFeature> = .init(initialState: ListFeature.State()) { ListFeature() } override func viewDidLoad() { … } // MARK: UITableViewDataSource override func numberOfSections(in tableView: UITableView) override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell }
  14. υϝΠϯΛࡉ෼Խ͢Δ UITableView final class ListViewController: UITableViewController { let store: StoreOf<ListFeature>

    = .init(initialState: ListFeature.State()) { ListFeature() } var observations: [IndexPath: ObserveToken] = [:] override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { observations[indexPath]?.cancel() observations[indexPath] = observe { [weak self] in guard let self else { return } cell.textLabel?.text = "\(store.rows[indexPath.row].id)" } return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let id = store.rows[indexPath.row].id if let store = store.scope(state: \.rows[id: id], action: \.rows[id: id]) { navigationController?.pushViewController(RowDetailViewController(store: store), animated: true) } } }
  15. uikit-navigation ࢖͍ํ: Tree-based final class FeatureViewController: UIViewController { let store:

    StoreOf<Feature> override func viewDidLoad() { super.viewDidLoad() observe { … } } }
  16. uikit-navigation ࢖͍ํ: Tree-based final class FeatureViewController: UIViewController { @UIBindable var

    store: StoreOf<Feature> override func viewDidLoad() { super.viewDidLoad() observe { … } } }
  17. uikit-navigation ࢖͍ํ: Tree-based final class FeatureViewController: UIViewController { @UIBindable var

    store: StoreOf<Feature> override func viewDidLoad() { super.viewDidLoad() observe { … } present(item: $store.scope( state: \.child, action: \.child )) { store in ChildViewController(store: store) } } }
  18. uikit-navigation ࢖͍ํ: Stack-based class AppController: NavigationStackController { private var store:

    StoreOf<AppFeature>! convenience init(store: StoreOf<AppFeature>) { @UIBindable var store = store self.init(path: $store.scope(state: \.path, action: \.path)) { ListViewController() } destination: { store in switch store.case { case let .detail(store): DetailViewController(store: store) case let .editItem(store): EditViewController(store: store) } } } }
  19. TCA × UIKit × pixiv • ⭕ • TCAͷಋೖ •

    ViewControllerͰঢ়ଶΛ؂ࢹ • swift-navigation(uikit-navigation)Λ׆༻ͨ͠state-drivenͳը໘ભҠ • ˚ • υϝΠϯͷࡉ෼Խ • swift-navigationͱTCAͷ౷߹
  20. TCA × UIKit × pixiv • ࡉ෼Խɾswift-navigationͱTCAͷ౷߹͕·ͩͰ͖͍ͯͳ͍ཧ༝ • ࢼߦࡨޡதʂ •

    ը໘ભҠઌʹReducerΛಋೖͰ͖͍ͯͳ͍ • Tree-based͸ಛʹ໰୊ͳͦ͞͏ • swift-navigationͷΈͰstate-drivenͳը໘ભҠ͕࣮ݱͰ͖ΔͨΊ • Stack-basedΛ΍Γ͍ͨ৔߹ͩͱͪΐͬͱ೉͍͔͠΋ʁ • StackΛ؅ཧ͢Δ࢓૊ΈΛࣗ෼Ͱ࡞Βͳ͍ͱ͍͚ͳ͍ʁ