Skip to content

choijunyeong-company/CoreFlow-C7T

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

92 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CoreFlow

Swift Platforms Swift Package Manager

MCP Servers (Context7, etc.): For structured documentation guide, see llms.txt

How to use

Xcode ν…œν”Œλ¦Ώμ„ μ„€μΉ˜ν•˜λ©΄ CoreFlow μ»΄ν¬λ„ŒνŠΈλ₯Ό λΉ λ₯΄κ²Œ 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.

  1. μ €μž₯μ†Œλ₯Ό λ‹€μš΄λ‘œλ“œν•˜κ³  압좕을 ν•΄μ œν•©λ‹ˆλ‹€.

  2. tooling λ””λ ‰ν† λ¦¬λ‘œ μ΄λ™ν•©λ‹ˆλ‹€.

cd CoreFlow/tooling
  1. tooling 디렉토리 λ‚΄λΆ€ μ„€μΉ˜ 슀크립트λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€.
./install-xcode-template.sh

μ„€μΉ˜ ν›„ Xcodeμ—μ„œ File β†’ New β†’ File... (⌘N)을 μ„ νƒν•˜λ©΄ CoreFlow ν…œν”Œλ¦Ώμ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

ν•΄λ‹Ή 라이브러리λ₯Ό λ§Œλ“  이유

ν•΄λ‹Ή λΌμ΄λΈŒλŸ¬λ¦¬λŠ” iOS μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 개발 μ‹œ μ•„ν‚€ν…μ²˜ κ²°μ •μ˜ λ³΅μž‘λ„λ₯Ό μ΅œμ†Œν™”ν•˜κΈ° μœ„ν•΄ λ§Œλ“€μ–΄μ‘ŒμŠ΅λ‹ˆλ‹€. 보톡 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 개발 μ‹œ λ‹€μŒκ³Ό 같은 μ•„ν‚€ν…μ²˜λ“€μ΄ κ³ λ €λ©λ‹ˆλ‹€.

MVVM, MVC, MVVM-C, RIBs, TCA ..

각각의 μ•„ν‚€ν…μ²˜μ˜ λ‹¨μ λ§Œμ„ μ„œμˆ ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

MVVM, MVC

큰 생각 없이 μ‹œμž‘ν•˜κΈ° 쒋은 νŒ¨ν„΄λ“€μž…λ‹ˆλ‹€. ν•˜μ§€λ§Œ ν”„λ‘œμ νŠΈμ˜ 규λͺ¨κ°€ 컀질수둝 μ±…μž„μ΄ κ³Όμ€‘λœ 객체듀이 μ¦κ°€ν•˜κ²Œ λ˜μ–΄ λ³΅μž‘ν•œ μ½”λ“œλ² μ΄μŠ€κ°€ λ§Œλ“€μ–΄μ§€κ³  ν…ŒμŠ€νŠΈλ„ μ–΄λ ΅μŠ΅λ‹ˆλ‹€.

Coordinator

μ½”λ””λ„€μ΄ν„°λŠ” 일반적으둜 ViewController의 λ‚΄λΉ„κ²Œμ΄μ…˜ μ±…μž„μ„ λ‹΄λ‹Ήν•˜κ³ , κΈ°λŠ₯ 계측을 κ΅¬μ„±ν•˜λŠ” 역할을 μˆ˜ν–‰ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ κ·Έ ꡬ쑰가 μ •ν˜•ν™”λ˜μ–΄ μžˆμ§€ λͺ»ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ νŠΉμ • Coordinator 계측에 μ‘΄μž¬ν•˜λŠ” 객체에 μ ‘κ·Όν•˜κ³  싢은 경우 λ‹€μ–‘ν•œ 방식이 κ°€λŠ₯ν•©λ‹ˆλ‹€. 직접 ν•΄λ‹Ή 객체λ₯Ό μ°Έμ‘°ν•  수 있고, Coordinator 계측을 μ΄λ™ν•˜μ—¬ μ ‘κ·Όν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ μžμœ λ‘œμ›€μ€ λ•Œλ‘œλŠ” ν˜‘μ—… μ‹œ λ‚œμ²˜ν•œ 흐름을 λ§Œλ“€κ³  μœ μ§€λ³΄μˆ˜ν•˜κΈ° μ–΄λ €μš΄ μ½”λ“œλ₯Ό λ‚³μŠ΅λ‹ˆλ‹€.

RIBs

κ°€μž₯ μ •ν˜•ν™”λœ νŒ¨ν„΄μ΄λΌκ³  말할 수 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ λ„ˆλ¬΄ λ¬΄κ²μŠ΅λ‹ˆλ‹€. μ˜μ‘΄κ΄€κ³„λ₯Ό μ΅œμ†Œν™”ν•˜κΈ° μœ„ν•΄ λ§Žμ€ 좔상화가 μ‘΄μž¬ν•©λ‹ˆλ‹€. RIB λ‚΄λΆ€μ—μ„œλ„ μΆ”μƒν™”λœ 객체듀 κ°„ μ†Œν†΅μ„ μœ„ν•œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ ν›„ μ†Œν†΅μ„ μ‹œμž‘ν•  수 μžˆλ‹€λŠ” μ μ—μ„œ λ³΅μž‘λ„μ™€ κ³΅μˆ˜κ°€ ν½λ‹ˆλ‹€. 그둜 μΈν•œ λŸ¬λ‹μ»€λΈŒ μ—­μ‹œ λ¬΄μ‹œν•˜μ§€ λͺ»ν•  μˆ˜μ€€μž…λ‹ˆλ‹€. RIBsλŠ” RxSwift둜 κ΅¬ν˜„λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. κ²€μ¦λœ 라이브러리λ₯Ό 기반으둜 ν•˜μ§€λ§Œ μž₯기적인 지원을 보μž₯ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ•ˆμ •μ μΈ ꡬ쑰와 κ°•λ ₯ν•œ 좔상화가 μ‘΄μž¬ν•˜μ§€λ§Œ, κ³΅μˆ˜κ°€ 많이 λ“œλŠ” μ•„ν‚€ν…μ²˜μž…λ‹ˆλ‹€.

TCA

λ¦¬λ“€μ„œ μ»΄ν¬μ§€μ…˜ λ“± κΈ°μ‘΄ iOS κ°œλ°œμ—μ„œλŠ” μ œκ³΅ν•˜μ§€ μ•Šμ•˜λ˜ μƒˆλ‘œμš΄ νŒ¨λŸ¬λ‹€μž„μ„ μ œκ³΅ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ ν•΄λ‹Ή 라이브러리 κ°œλ°œμžμ—κ²Œ μ§€λ‚˜μΉ˜κ²Œ μ˜μ‘΄μ μ΄λΌλŠ” 점이 λ¬Έμ œμž…λ‹ˆλ‹€. μˆ˜λ§Žμ€ λ§€ν¬λ‘œμ™€ 좔상화 등은 μ‚¬μš©μžκ°€ λ‚΄λΆ€ λ™μž‘μ„ μ΄ν•΄ν•˜κΈ° μ–΄λ ΅κ²Œ ν•©λ‹ˆλ‹€. ν–₯ν›„ 지원 및 λ³€λ™μ‚¬ν•­μœΌλ‘œ μΈν•œ μœ„ν—˜μ΄ 큰 라이브러리라고 μƒκ°ν•©λ‹ˆλ‹€.

단점 ν•΄κ²°

CoreFlowλŠ” μœ„μ—μ„œ μ„œμˆ λœ λ¬Έμ œλ“€μ„ ν•΄κ²°ν•˜κ³ μž λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€.

  • μ •ν˜•ν™”λœ νŒ¨ν„΄κ³Ό ꡬ쑰λ₯Ό μ œκ³΅ν•˜λ©° μ΄ν•΄ν•˜κΈ° μ‰½μŠ΅λ‹ˆλ‹€.
  • κ³Όλ„ν•œ 좔상화λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 객체 κ°„ μ˜μ‘΄μ„± κ΄€λ¦¬λŠ” μ ‘κ·Ό μ§€μ •μž μ‚¬μš©μ„ μ§€ν–₯ν•©λ‹ˆλ‹€.
  • Combineκ³Ό Swift Concurrency 기반으둜 κ΅¬ν˜„ν•˜μ—¬ μž₯기적인 μ•ˆμ •μ„±μ΄ λ†’μŠ΅λ‹ˆλ‹€.
  • RIBsμ—μ„œ μ œκ³΅ν•˜λŠ” LeakDetector, Workflow κΈ°λŠ₯을 μ§€μ›ν•©λ‹ˆλ‹€.

Features

CoreFlowλŠ” μ„Έ κ°€μ§€ μ»΄ν¬λ„ŒνŠΈ(Flow, Core, Screen)둜 κ΅¬μ„±λ©λ‹ˆλ‹€. 이 μ„Έ κ°€μ§€ μš”μ†Œλ‘œ κ΅¬μ„±λœ 집합을 CoreFlow라고 λͺ…μΉ­ν•©λ‹ˆλ‹€.

μ•„λž˜λΆ€ν„° 각 κ΅¬μ„±μš”μ†Œμ— λŒ€ν•œ κ°„λž΅ν•œ μ„€λͺ…이 μžˆμŠ΅λ‹ˆλ‹€. 더 μžμ„Έν•œ λͺ…μ„ΈλŠ” docs 디렉터리 λ‚΄λΆ€ λ¬Έμ„œλ₯Ό μ°Έκ³ ν•΄μ£Όμ„Έμš”.

Flow

Flow κ°μ²΄λŠ” Core와 Screen 두 객체λ₯Ό μ°Έμ‘°ν•˜λ©° 트리 κ΅¬μ„±μ˜ 쀑심이 λ©λ‹ˆλ‹€. λ‹€λ₯Έ Flowλ₯Ό μ§μ ‘μ μœΌλ‘œ μ°Έμ‘°ν•˜λŠ” 주체둜, λ‚΄λΉ„κ²Œμ΄μ…˜ 및 μΈν„°λž™μ…˜μ„ μ—°κ²°ν•©λ‹ˆλ‹€.

Core

CoreFlow의 핡심적인 둜직이 μœ„μΉ˜ν•©λ‹ˆλ‹€. 이와 λ”λΆˆμ–΄ CoreFlow에 슀크린이 μ‘΄μž¬ν•˜λŠ” 경우 Reactor의 역할도 μˆ˜ν–‰ν•©λ‹ˆλ‹€. Reactor νŒ¨ν„΄μ€ 단방ν–₯ μ•„ν‚€ν…μ²˜λ₯Ό 기반으둜 ν•˜λŠ” λ””μžμΈ νŒ¨ν„΄μœΌλ‘œ, 잘 μ•Œλ €μ§„ ν”„λ ˆμž„μ›Œν¬μΈ ReactorKitκ³Ό TCAλ₯Ό μ°Έκ³ ν•˜μ—¬ μ œμž‘ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

Screen

Screen은 UIViewController라고 μƒκ°ν•˜μ‹œλ©΄ λ©λ‹ˆλ‹€. Core의 μƒνƒœλ₯Ό κ΅¬λ…ν•˜κ³  있으며, 뷰의 μ•‘μ…˜μ„ Core에 전달할 수 μžˆμŠ΅λ‹ˆλ‹€.

Procedure (Workflow)

ProcedureλŠ” RIBs의 Workflow와 μœ μ‚¬ν•œ κΈ°λŠ₯으둜, μ•±μ˜ 흐름을 λ‹¨κ³„λ³„λ‘œ μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ν•΄λ‹Ή κΈ°λŠ₯을 μ‚¬μš©ν•˜μ—¬ μ•±μ˜ λŸ°μΉ­ν”Œλ‘œμš° 및 λ”₯링크 뢄석을 ν†΅ν•œ 흐름을 μ†μ‰½κ²Œ λ§Œλ“€μ–΄λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€. Combine 기반의 체이닝 APIλ₯Ό μ œκ³΅ν•˜μ—¬ λ³΅μž‘ν•œ 비동기 흐름을 μ„ μ–Έμ μœΌλ‘œ ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ‚¬μš© 방법

  1. Procedureλ₯Ό 상속받아 μ›Œν¬ν”Œλ‘œμš°λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€.
  2. onStep을 μ²΄μ΄λ‹ν•˜μ—¬ 각 단계λ₯Ό μ—°κ²°ν•©λ‹ˆλ‹€.
  3. commit()으둜 μ›Œν¬ν”Œλ‘œμš°λ₯Ό ν™•μ •ν•©λ‹ˆλ‹€.
  4. start()둜 μ‹€ν–‰ν•©λ‹ˆλ‹€.
final class DefaultProcedure: Procedure<RootStepAction> {
    override init() {
        super.init()
        onStep { rootStepAction in
            rootStepAction.waitForOnboarding()
        }
        .onStep { rootStepAction, _ in
            rootStepAction.waitForLogin()
        }
        .onStep { rootStepAction, user in
            rootStepAction.presentMain(user: user)
        }
        .commit()
    }
}

Step ν”„λ‘œν† μ½œ μ •μ˜

각 λ‹¨κ³„μ—μ„œ μ‹€ν–‰ν•  μ•‘μ…˜μ„ ν”„λ‘œν† μ½œλ‘œ μ •μ˜ν•©λ‹ˆλ‹€. 각 λ©”μ„œλ“œλŠ” AnyPublisher<(NextAction, Value), Never>λ₯Ό λ°˜ν™˜ν•˜μ—¬ λ‹€μŒ λ‹¨κ³„λ‘œ 값을 μ „λ‹¬ν•©λ‹ˆλ‹€.

protocol RootStepAction {
    func waitForOnboarding() -> AnyPublisher<(RootStepAction, Void), Never>
    func waitForLogin() -> AnyPublisher<(RootStepAction, User), Never>
    func presentMain(user: User) -> AnyPublisher<(MainStepAction, Void), Never>
}

StepAction ν”„λ‘œν† μ½œμ€ ν•΄λ‹Ή 흐름을 κ΄€μž₯ν•˜λŠ” Coreκ°€ μ±„νƒν•˜μ—¬ κ΅¬ν˜„ν•©λ‹ˆλ‹€. CoreλŠ” 이미 routerλ₯Ό 톡해 ν™”λ©΄ μ „ν™˜μ„ μ œμ–΄ν•˜κ³ , μžμ‹ Flow의 Listenerλ₯Ό κ΅¬ν˜„ν•˜μ—¬ μ™„λ£Œ μ‹œμ μ„ μ•Œ 수 μžˆμœΌλ―€λ‘œ StepAction의 역할을 μˆ˜ν–‰ν•˜κΈ°μ— κ°€μž₯ μ ν•©ν•©λ‹ˆλ‹€(ꢌμž₯).

extension RootCore: RootStepAction {
    func waitForOnboarding() -> AnyPublisher<(RootStepAction, Void), Never> {
        router?.routeToOnboarding()
        return onboardingFinished
            .compactMap(\.self)
            .map { (self, ()) }
            .eraseToAnyPublisher()
    }

    func waitForLogin() -> AnyPublisher<(RootStepAction, User), Never> {
        router?.routeToLogin()
        return loginFinished
            .compactMap(\.self)
            .map { user in (self, user) }
            .eraseToAnyPublisher()
    }
}

Coreμ—μ„œ Procedure μ‹€ν–‰

final class RootCore: Core<RootAction, RootState, RootFlow> {
    override func reduce(state: inout RootState, action: RootAction) -> Effect<RootAction> {
        switch action {
        case .viewDidLoad:
            DefaultProcedure()
                .start(self)  // RootStepAction을 μ€€μˆ˜ν•˜λŠ” selfλ₯Ό 전달
                .store(in: &store)
            return .none
        }
    }
}

이 νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λ©΄ Onboarding β†’ Login β†’ Mainκ³Ό 같은 μ•±μ˜ 전체 흐름을 λͺ…ν™•ν•˜κ²Œ μ •μ˜ν•˜κ³  관리할 수 μžˆμŠ΅λ‹ˆλ‹€.

Testing

Reactor 역할을 μˆ˜ν–‰ν•˜λŠ” Core의 경우 Action을 기반으둜 κ²°μ •λœ μ΅œμ’… μƒνƒœλ₯Ό 검증할 수 μžˆμŠ΅λ‹ˆλ‹€.

ν•˜λ‚˜μ˜ Action을 μ „μ†‘ν•˜λ”λΌλ„ Core λ‚΄λΆ€μ—μ„œ Effectκ°€ μƒˆλ‘œμš΄ Action을 λ°œν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ μ΅œμ’… μƒνƒœκ°€ κ²°μ •λ˜κΈ°κΉŒμ§€ λŒ€κΈ°κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

Action을 μ „μ†‘ν•œ 이후 exhaust()을 ν˜ΈμΆœν•˜μ—¬ μ΅œμ’… μƒνƒœ 결정을 기닀릴 수 μžˆμŠ΅λ‹ˆλ‹€. ν•΄λ‹Ή ν•¨μˆ˜κ°€ λ¦¬ν„΄λœ 이후 currentState둜 μ΅œμ’… μƒνƒœμ— μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€.

import Testing
@testable import YourApp
import CoreFlow

@MainActor
struct CoreTests {
    @Test
    func testActionFlow() async throws {
        // 1. Core μΈμŠ€ν„΄μŠ€ 생성
        let sut = SutCore(initialState: .init(step: 0))

        // 2. ν…ŒμŠ€νŠΈ λͺ¨λ“œ ν™œμ„±ν™”
        sut.enableTestMode()

        // 3. Action 전솑
        sut.send(.step1)

        // 4. λͺ¨λ“  Effectκ°€ μ™„λ£Œλ  λ•ŒκΉŒμ§€ λŒ€κΈ°
        try await sut.exhaust(timeout: 5)

        // 5. μ΅œμ’… μƒνƒœ 검증
        #expect(sut.currentState.step == 4)
    }
}

ν…ŒμŠ€νŠΈ λŒ€μƒ Core μ˜ˆμ‹œ

public final class SutCore: Core<SutAction, SutState> {
    public override func reduce(state: inout SutState, action: SutAction) -> Effect<SutAction> {
        switch action {
        case .step1:
            state.step = 1
            return .run { send in
                await send(.step2)  // Effectκ°€ μƒˆλ‘œμš΄ Action λ°œν–‰
            }
        case .step2:
            state.step = 2
            return .run { send in
                await send(.step3)
            }
        case .step3:
            state.step = 3
            return .run { send in
                await send(.step4)
            }
        case .step4:
            state.step = 4
            return .none  // μ΅œμ’… μƒνƒœ
        }
    }
}

μœ„ μ˜ˆμ‹œμ—μ„œ .step1 μ•‘μ…˜μ„ μ „μ†‘ν•˜λ©΄ Effect 체인을 톡해 .step2 β†’ .step3 β†’ .step4κΉŒμ§€ 순차적으둜 μ‹€ν–‰λ©λ‹ˆλ‹€. exhaust()은 λͺ¨λ“  Effectκ°€ μ™„λ£Œλ˜μ–΄ 더 이상 μ§„ν–‰ 쀑인 μž‘μ—…μ΄ 없을 λ•Œ λ¦¬ν„΄λ©λ‹ˆλ‹€.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors