fromm 앱에 λͺ¨λ“ˆν™” λ„μž…ν•˜κΈ°

haYeon
  • #λͺ¨λ“ˆν™”
  • #Modularization
  • #swift
  • #ios
  • #fromm

λ“€μ–΄κ°€λ©°



μ•ˆλ…•ν•˜μ„Έμš”. μ €λŠ” ν˜„μž¬ β€˜fromm’ μ•±μ—μ„œ iOS κ°œλ°œμ„ ν•˜κ³  μžˆλŠ” μ΄ν•˜μ—°μž…λ‹ˆλ‹€.

ν˜„μž¬ fromm 앱은 Clean Architecture와 MVVM νŒ¨ν„΄μ„ 기반으둜 λ§Œλ“€μ–΄μ Έ μžˆμŠ΅λ‹ˆλ‹€.

졜근 μ„œλΉ„μŠ€ 규λͺ¨κ°€ ν™•μž₯ λ˜λ©΄μ„œ λ¦¬νŒ©ν† λ§μ„ μ§„ν–‰ν•˜κ²Œ λ˜μ—ˆκ³ , κΈ°μ‘΄ 개발 κ³Όμ •μ—μ„œ κ²ͺμ—ˆλ˜ λͺ‡κ°€μ§€ 어렀움을 ν•΄κ²°ν•˜κΈ° μœ„ν•΄ TCA Architecture와 λͺ¨λ“ˆν™”λ₯Ό λ„μž…ν•˜κΈ°λ‘œ κ²°μ •ν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ 이번 κΈ€μ—μ„œλŠ” λͺ¨λ“ˆν™”λ₯Ό ν•˜λŠ” κ³Όμ •μ—μ„œ 느꼈던 점을 ν•¨κ»˜ κ³΅μœ ν•˜λ €κ³  ν•©λ‹ˆλ‹€.

AS-IS 뢄석을 ν†΅ν•œ 이전 상황 μ†Œκ°œ



λ¨Όμ €, μ„œλΉ„μŠ€λ₯Ό κ°œλ°œν•˜λ©΄μ„œ μ§λ©΄ν–ˆλ˜ λ¬Έμ œμ λ“€μ„ λ§μ”€λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€.

μ„œλΉ„μŠ€ 개발 κ³Όμ •μ—μ„œ κ°€μž₯ 크게 느꼈던 문제점 쀑 ν•˜λ‚˜λŠ” λ°”λ‘œ μ½”λ“œμ˜ 길이와 가독성 λ¬Έμ œμ˜€μŠ΅λ‹ˆλ‹€.

특히 β€˜fromm’ μ•±μ—μ„œ μ±„νŒ… κΈ°λŠ₯을 κ΄€λ¦¬ν•˜λŠ” 클래슀만 봐도 μ½”λ“œλŠ” 무렀 1170쀄에 λ‹¬ν•˜λŠ”λ°μš”. μ΄λ ‡κ²Œ κΈ΄ μ½”λ“œλŠ” 가독성을 크게 μ €ν•΄ν•  뿐만 μ•„λ‹ˆλΌ, νŠΉμ • 뢀뢄을 μˆ˜μ •ν•˜κ±°λ‚˜ κΈ°λŠ₯을 μΆ”κ°€ν•˜λ € ν•  λ•Œ 전체 μ½”λ“œλ₯Ό νŒŒμ•…ν•˜λŠ” 데 λ§Žμ€ μ‹œκ°„μ΄ μ†Œμš”λ˜μ–΄ 업무 νš¨μœ¨μ„±μ΄ λ–¨μ–΄μ§€λŠ” λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€.

κ·Έ λ‹€μŒμœΌλ‘œ 느꼈던 λ¬Έμ œμ μ€, μ˜μ‘΄μ„± κ΄€λ¦¬μ˜ 어렀움 λ¬Έμ œμ˜€μŠ΅λ‹ˆλ‹€.

fromm μ„œλΉ„μŠ€κ°€ 점점 μ»€μ§€κ²Œ λ˜λ©΄μ„œ, λ‹€μ–‘ν•œ μ»΄ν¬λ„ŒνŠΈλ‚˜ κΈ°λŠ₯λ“€ 사이에 λ³΅μž‘ν•œ μ˜μ‘΄μ„±μ΄ ν˜•μ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 이 λ³΅μž‘ν•œ μ˜μ‘΄μ„±μœΌλ‘œ 인해 ν•œ λΆ€λΆ„μ˜ λ³€ν™”κ°€ λ‹€λ₯Έ 뢀뢄에도 λ―ΈμΉ˜λŠ” 영ν–₯을 μ˜ˆμΈ‘ν•˜κ³  κ΄€λ¦¬ν•˜λŠ” 것이 점점 μ–΄λ €μ›Œμ§€κ³  μžˆμŠ΅λ‹ˆλ‹€.

λ§ˆμ§€λ§‰μœΌλ‘œ 느꼈던 λ¬Έμ œμ μ€, λΉŒλ“œ 및 컴파일 μ‹œκ°„μ˜ λ¬Έμ œμ˜€μŠ΅λ‹ˆλ‹€.

ν˜„μž¬ μ½”λ“œ ꡬ쑰상, A κΈ°λŠ₯μ—μ„œ μ•„μ£Ό μž‘μ€ μˆ˜μ •μ΄ 이루어져도 전체 앱을 λΉŒλ“œν•΄μ•Ό ν•˜λŠ” 상황이 λ°œμƒν•©λ‹ˆλ‹€.

맀번 μž‘μ€ μˆ˜μ •μ‚¬ν•­λ§ˆλ‹€ 전체λ₯Ό λΉŒλ“œν•˜κ³  ν™•μΈν•΄μ•Όν•˜λŠ” 것은 μ‹œκ°„ μ†Œλͺ¨κ°€ 크며, μ΄λŠ” λΆˆν•„μš”ν•œ μ‹œκ°„ λ‚­λΉ„λ₯Ό μ΄ˆλž˜ν•˜κ²Œ λ©λ‹ˆλ‹€. μ΄λŸ¬ν•œ λ¬Έμ œλ“€μ„ ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„œλŠ” 각 κΈ°λŠ₯λ³„λ‘œ 독립적인 λΉŒλ“œμ™€ ν…ŒμŠ€νŠΈκ°€ κ°€λŠ₯ν•œ ꡬ쑰둜 κ°œμ„ ν•  ν•„μš”κ°€ μžˆλ‹€κ³  μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ“ˆν™”λž€?



λͺ¨λ“ˆν™”λž€ λŒ€κ·œλͺ¨ ν”„λ‘œμ νŠΈλ₯Ό 더 μž‘κ³  κ΄€λ¦¬ν•˜κΈ° μ‰¬μš΄ λͺ¨λ“ˆλ‘œ λ‚˜λˆ„λŠ” μ†Œν”„νŠΈμ›¨μ–΄ 개발 κΈ°μˆ μž…λ‹ˆλ‹€.

각 λͺ¨λ“ˆμ€ λ…λ¦½μ μœΌλ‘œ μž‘λ™ν•˜λ©° νŠΉμ • κΈ°λŠ₯을 λ‹΄λ‹Ήν•˜κ²Œ λ©λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν”„λ‘œμ νŠΈλ₯Ό μ—¬λŸ¬ λͺ¨λ“ˆλ‘œ λ‚˜λˆ„λ©΄, 각 λͺ¨λ“ˆμ€ 자체적으둜 ν…ŒμŠ€νŠΈμ™€ 디버깅이 κ°€λŠ₯ν•˜λ©° λ…λ¦½μ μœΌλ‘œ μ—…λ°μ΄νŠΈν•˜κ±°λ‚˜ μˆ˜μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ“ˆν™” 섀계 및 κ΅¬ν˜„



λͺ¨λ“ˆν™” 섀계


κΈ°μ‘΄μ—λŠ” ν•˜λ‚˜μ˜ ν”„λ‘œμ νŠΈ μ•ˆμ— Application, Domain, Data, Presentation, Common λ“± ν•¨κ»˜ μ‘΄μž¬ν•˜μ—¬ κ΄€λ¦¬λ˜κ³  μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό λͺ¨λ“ˆν™” ν•˜κΈ° μœ„ν•΄ App, Base, Feature, Data, Core Module 관계λ₯Ό κ·Έλ €λ³΄μ•˜μŠ΅λ‹ˆλ‹€.



λ¨Όμ € App Module은 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ£Όμš” μ§„μž…μ μœΌλ‘œ, ν”„λ‘œμ νŠΈμ˜ μ „λ°˜μ μΈ μ„€μ •κ³Ό μ΄ˆκΈ°ν™” μž‘μ—…μ„ λ‹΄λ‹Ήν•©λ‹ˆλ‹€.

ν˜„μž¬ fromm μ„œλΉ„μŠ€μ˜ 경우, μ•± λͺ¨λ“ˆμ΄ fan-appκ³Ό arti-app으둜 λ‚˜λˆ„μ–΄μ Έ μžˆμŠ΅λ‹ˆλ‹€.



Feature Module은 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 각 κΈ°λŠ₯ λ‹¨μœ„λ‘œ κ΅¬λΆ„λ˜μ–΄, νŠΉμ • κΈ°λŠ₯을 λ…λ¦½μ μœΌλ‘œ λ‹΄λ‹Ήν•©λ‹ˆλ‹€.

이 λͺ¨λ“ˆμ€ 주둜 두 λΆ€λΆ„μœΌλ‘œ κ΅¬μ„±λ˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

  • Reducer : λΉ„μ¦ˆλ‹ˆμŠ€ 둜직과 μƒνƒœ 관리λ₯Ό λ‹΄λ‹Ήν•˜λŠ” λΆ€λΆ„
  • Presentation : View와 κ΄€λ ¨λœ μž‘μ—…μ„ μ²˜λ¦¬ν•˜λŠ” λΆ€λΆ„ ( 주둜 ViewController, View λ“±μ˜ μ»΄ν¬λ„ŒνŠΈλ‘œ ꡬ성 )


Data Module은 데이터 κ΄€λ ¨ μž‘μ—…μ„ λͺ¨μ•„놓은 λͺ¨λ“ˆλ‘œ, 크게 DataModel, Client, Network μ„Έ 뢀뢄을 λ‹΄λ‹Ήν•©λ‹ˆλ‹€.

  • DataModel

μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ‚¬μš©λ˜λŠ” λͺ¨λ“  데이터 λͺ¨λΈμ„ μ •μ˜ν•˜κ³  μžˆλŠ” νŒ¨ν‚€μ§€μž…λ‹ˆλ‹€.

λŒ€ν‘œμ μœΌλ‘œ APIμ—μ„œ λ°›μ•„μ˜¨ 응닡 데이터λ₯Ό λͺ¨λΈλ§ν•œ Response λͺ¨λΈκ³Ό 둜컬 λ°μ΄ν„°λ² μ΄μŠ€μ˜ λͺ¨λΈ 객체λ₯Ό μ˜λ―Έν•˜λŠ” Entity λͺ¨λΈμ΄ ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

  • Client

λͺ¨λ“  ν΄λΌμ΄μ–ΈνŠΈλ“€μ˜ μΈν„°νŽ˜μ΄μŠ€μ™€ 그에 λŒ€ν•œ κΈ°λ³Έ κ΅¬ν˜„μ„ ν¬ν•¨ν•˜κ³  μžˆλŠ” νŒ¨ν‚€μ§€μž…λ‹ˆλ‹€.

  • NetworkProvider

μ™ΈλΆ€ API ν˜ΈμΆœμ— ν•„μš”ν•œ Service μΈν„°νŽ˜μ΄μŠ€μ™€ CacheManagerλ₯Ό ν¬ν•¨ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.



Core Module은 νŒ¬μ•±, μ•„ν‹°μ•±μ—μ„œ κ³΅ν†΅μœΌλ‘œ μ‚¬μš©ν•˜λŠ” UI μ»΄ν¬λ„ŒνŠΈ 및 λ¦¬μ†ŒμŠ€ 등을 λ‹΄λ‹Ήν•©λ‹ˆλ‹€.

λͺ¨λ“ˆν™” κ΅¬ν˜„


자 이제 인트둜 ν™”λ©΄μ—μ„œ 둜그인 λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ λ‚˜νƒ€λ‚˜λŠ” β€œλ‘œκ·ΈμΈ IDλ₯Ό μž…λ ₯ν•˜λŠ” λͺ¨λ“ˆ(Feature λͺ¨λ“ˆ)”을 λ§Œλ“€μ–΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

Platform νŒ¨ν‚€μ§€λŠ” 두 μ•±μ—μ„œ κ³΅ν†΅μœΌλ‘œ μ‚¬μš©ν•˜λŠ” λͺ¨λ“ˆλ“€μ„ ν¬ν•¨ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

NetworkProvider λͺ¨λ“ˆμ€ μ™ΈλΆ€ API ν˜ΈμΆœμ— ν•„μš”ν•œ Service μΈν„°νŽ˜μ΄μŠ€μ™€ 데이터 캐싱을 κ΄€λ¦¬ν•˜λŠ” CacheManagerκ°€ μ‘΄μž¬ν•˜λ©° 이λ₯Ό 톡해 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ μ™ΈλΆ€ μ‹œμŠ€ν…œκ³Όμ˜ 효율적인 톡신λ₯Ό μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Platform",
    platforms: [.iOS(.v14)],
    products: [
        .library(
            name: "NetworkProvider",
            targets: ["NetworkProvider"]),
        .library(
            name: "FrommUI",
            targets: ["FrommUI"])
    ],
    dependencies: [
        ...
    ],
    targets: [
        ...
        .target(
            name: "NetworkProvider",
            dependencies: [
                "Alamofire",
                .product(name: "UserKit", package: "UserKit"),
                .product(name: "CommonKit", package: "CommonKit"),
            ]
        ),
        .target(
            name: "FrommUI",
            dependencies: [
                ...
            ]
        )
    ]
)

Client νŒ¨ν‚€μ§€μ—λŠ” μΈν„°νŽ˜μ΄μŠ€μ™€ νŠΉμ • μΈν„°νŽ˜μ΄μŠ€μ˜ κ΅¬ν˜„μ΄ 각각 κ΅¬λΆ„λ˜μ–΄ λͺ¨λ“ˆν™” λ˜μ–΄μžˆμŠ΅λ‹ˆλ‹€.

AuthClientλŠ” 둜그인 μž‘μ—…μ— ν•„μš”ν•œ 계정 쑴재 μ—¬λΆ€λ₯Ό ν™•μΈν•˜κ±°λ‚˜, λΉ„λ°€λ²ˆν˜Έλ₯Ό μž¬μ„€μ •ν•˜κ±°λ‚˜, λ‘œκ·ΈμΈν•˜λŠ” λ“±μ˜ μž‘μ—…λ“€μ„ μ •μ˜ν•œ μΈν„°νŽ˜μ΄μŠ€μž…λ‹ˆλ‹€. AuthClientLiveλŠ” AuthClient μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ μ‹€μ œ 클래슀둜, 각 λ©”μ†Œλ“œμ—μ„œ λ„€νŠΈμ›Œν¬ μš”μ²­μ„ μˆ˜ν–‰ν•˜κ³  κ·Έ κ²°κ³Όλ₯Ό μ²˜λ¦¬ν•©λ‹ˆλ‹€.

import PackageDescription

let package = Package(
    name: "Client",
    platforms: [.iOS(.v14)],
    products: [
        .library(name: "AuthClient", targets: ["AuthClient"]),
        .library(name: "AuthClientLive", targets: ["AuthClientLive"])
    ],
    dependencies: [
        .package(path: "../../Pacakge/DataModel"),
        .package(path: "../../Pacakge/Platform"),
        .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.1.5"),
    ],
    targets: [
        .target(
            name: "AuthClient",
            dependencies: [
                .product(name: "AuthDataModels", package: "DataModel"),
                .product(name: "Dependencies", package: "swift-dependencies"),
                .product(name: "DependenciesMacros", package: "swift-dependencies"),
            ]
        ),
        .target(
            name: "AuthClientLive",
            dependencies: [
                "AuthClient",
                .product(name: "NetworkProvider", package: "Platform"),
            ]
        )
    ]
)

이제 frommArti-app νŒ¨ν‚€μ§€λ₯Ό μƒμ„±ν•œ λ’€ λ§Œλ“€λ €κ³  ν•˜λŠ” Feature λͺ¨λ“ˆ(ArtiSignInUsername)을 μΆ”κ°€ν•΄μ€λ‹ˆλ‹€. 이 νŒ¨ν‚€μ§€λŠ” 둜컬 κ²½λ‘œμ— μœ„μΉ˜ν•œ β€˜Client’, β€˜CommonKit’, β€˜Platform’ νŒ¨ν‚€μ§€λ“€κ³Ό GitHubμ—μ„œ κ°€μ Έμ˜¨ β€˜swift-composable-architecture’ νŒ¨ν‚€μ§€μ™€ μ˜μ‘΄μ„±μ„ 가지고 μžˆμŒμ„ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

그리고 4가지 μ˜μ‘΄μ„±μ„ μ§€λ‹ˆκ³  μžˆλŠ” ArtiSignInUsername λͺ¨λ“ˆμ„ μ’€ 더 μ‚΄νŽ΄λ³΄λ©΄, 각 νŒ¨ν‚€μ§€ μ€‘μ—μ„œ 이 Feature λͺ¨λ“ˆμ— ν•„μš”ν•œ AuthClient, CommonKit, ComposableArchitecture, β€˜FrommUIβ€™λ§Œ μ˜μ‘΄μ„±μ„ 가지고 μžˆμŒμ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ 의쑴 관계λ₯Ό νŒŒμ•…ν•˜κΈ° μˆ˜μ›”ν•΄μ‘ŒμœΌλ©° κ΄€λ¦¬ν•˜κΈ° μš©μ΄ν•΄μ‘ŒμŠ΅λ‹ˆλ‹€.

// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "frommArti-app",
    defaultLocalization: "en",
    platforms: [
        .iOS(.v14)
    ],
    
    products: [
        .library(name: "ArtiCoordinator", targets: ["ArtiCoordinator"]),
        .library(name: "ArtiSignInUsername", targets: ["ArtiSignInUsername"])
    ],
    
    dependencies: [
        .package(path: "../../Pacakge/Client"),
        .package(path: "../../Pacakge/CommonKit"),
        .package(path: "../../Pacakge/Platform"),
        .package(url: "https://github.com/pointfreeco/swift-composable-architecture",
                 from: "1.6.0"),
    ],
    
    targets: [
        .target(
            name: "ArtiCoordinator",
            dependencies: [
                "ArtiSignInUsername",
            ]
        ),
        .target(
            name: "ArtiSignInUsername",
            dependencies: [
                .product(name: "AuthClient", package: "Client"),
                .product(name: "CommonKit", package: "CommonKit"),
                .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
                .product(name: "FrommUI", package: "Platform"),
            ]
        ),
        .testTarget(
            name: "ArtiSignInUsernameTests",
            dependencies: ["ArtiSignInUsername"]
        )
    ]
)

λ§ˆμ§€λ§‰μœΌλ‘œ ArtiSignInUsername λͺ¨λ“ˆμ˜ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직과 View κ΄€λ ¨ μ½”λ“œλ₯Ό μž‘μ„±ν•΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

import AuthClient
import ComposableArchitecture

public struct SignInUsernameFeature: Reducer {
    public struct State: Equatable { ... }

    public enum Action: BindableAction { ... }

    @Dependency(\.authClient.checkExistAccount) var isExistUser

    public var body: some ReducerOf<Self> { ... }

    private func changeInputState(
        username: String
    ) -> State.InputState { ... }
import Combine
import CommonKit
import ComposableArchitecture
import UIKit

public final class SignInUsernameViewController: UIViewController {
    struct ViewState: Equatable { ... }
    }
    
    enum ViewAction { ... }
    
    let store: StoreOf<SignInUsernameFeature>
    
    let viewStore: ViewStore<ViewState, ViewAction>

    private lazy var layout: SignInUsernameLayout = .init(rootView: view)

    ... 
}

extension SignInUsernameFeature.Action {
    init(action: SignInUsernameViewController.ViewAction) {
        ...
    }
}

λ§Œμ•½ ArtiSignInUsername에 변경사항 ν›„ λ¬Έμ œκ°€ μ—†λŠ”μ§€ ν™•μΈν•˜κ³  μ‹Άλ‹€λ©΄ ν•΄λ‹Ή λͺ¨λ“ˆλ§Œ λΉŒλ“œν•  수 μžˆμŠ΅λ‹ˆλ‹€.

이제 전체 μ„œλΉ„μŠ€λ₯Ό λΉŒλ“œν•˜μ—¬ κΈ°μ‘΄ 인트둜 ν™”λ©΄μ—μ„œ ArtiSignInUsername λͺ¨λ“ˆμ΄ 잘 λ™μž‘λ˜λŠ” 지 ν™•μΈν•˜λ©΄ λ©λ‹ˆλ‹€.

import ArtiSignInUsername

public final class IntroViewController: UIViewController {
        ...
        private func pushToSigninUsername() {
        let vc = SignInUsernameViewController()
        navigationController?.pushViewController(vc, animated: true)
    }
}

λͺ¨λ“ˆν™” λ„μž…μ΄ κ°€μ Έμ˜¨ 영ν–₯κ³Ό κ²°λ‘ 



κ·Έλ ‡λ‹€λ©΄, 이제 β€˜fromm' μ•±μ—μ„œ λͺ¨λ“ˆν™” λ„μž…μ„ ν†΅ν•œ μ—¬λŸ¬κ°€μ§€ λ³€ν™”λ₯Ό μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

μš°μ„ , β€˜fromm’ μ•±μ˜ μ½”λ“œ 관리와 κ°œμ„  μž‘μ—…μ΄ λ”μš± 효과적으둜 μ΄λ£¨μ–΄μ§€κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 각 λͺ¨λ“ˆμ€ λ…λ¦½μ μœΌλ‘œ λΉŒλ“œλ˜κ³  ν…ŒμŠ€νŠΈλ  수 μžˆμ–΄, νŠΉμ • λͺ¨λ“ˆμ˜ 변경사항이 λ‹€λ₯Έ λͺ¨λ“ˆμ— 영ν–₯을 주지 μ•ŠμŠ΅λ‹ˆλ‹€. 이둜 인해 μ•±μ˜ μ•ˆμ •μ„±μ΄ ν–₯μƒλ˜κ³ , κ°œλ³„ λͺ¨λ“ˆμ— λŒ€ν•œ κ°œμ„  μž‘μ—…μ΄ λ”μš± μˆ˜μ›”ν•΄μ‘ŒμŠ΅λ‹ˆλ‹€.

λ˜ν•œ, μž‘μ€ 변경사항 λ°œμƒ μ‹œμ—λ„ 전체 앱을 λΉŒλ“œν•  ν•„μš” 없이 ν•΄λ‹Ή λͺ¨λ“ˆλ§Œ λΉŒλ“œν•¨μœΌλ‘œμ¨ 컴파일 속도가 크게 ν–₯μƒλ˜μ—ˆμŠ΅λ‹ˆλ‹€. 이둜써 개발 μ‹œκ°„μ„ λ‹¨μΆ•μ‹œν‚€κ³  업무 νš¨μœ¨μ„ 크게 ν–₯μƒμ‹œμΌ°μŠ΅λ‹ˆλ‹€.

λ§ˆμ§€λ§‰μœΌλ‘œ, λͺ¨λ“ˆν™”λ₯Ό 톡해 각 λͺ¨λ“ˆμ€ μžμ‹ μ΄ ν•„μš”λ‘œ ν•˜λŠ” μ˜μ‘΄μ„±λ§Œμ„ κ°€μ§€κ²Œ λ˜μ–΄, λΆˆν•„μš”ν•œ μ˜μ‘΄μ„±μ˜ 포함이 λ°©μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

μ΄λ ‡κ²Œ fromm 앱에 λͺ¨λ“ˆν™” λ„μž…μœΌλ‘œ, 개발 과정을 효율적이고 κ΄€λ¦¬ν•˜κΈ° μ‰¬μš΄ λ°©ν–₯으둜 λ¦¬νŒ©ν† λ§ 진행쀑에 μžˆμŠ΅λ‹ˆλ‹€. 아직은 적용 단계이기 λ•Œλ¬Έμ— λΆ€μ‘±ν•œ 점이 λ§Žμ§€λ§Œ μ‘°κΈˆμ΄λ‚˜λ§ˆ 도움이 λ˜μ…¨κΈ°λ₯Ό λ°”λΌλ©΄μ„œ 이만 글을 λ§ˆμΉ˜κ² μŠ΅λ‹ˆλ‹€. λκΉŒμ§€ μ½μ–΄μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€.

← λͺ©λ‘μœΌλ‘œ λŒμ•„κ°€κΈ°

Art Changes Life

λ…Έλ¨ΈμŠ€μ™€ ν•¨κ»˜ μ—”ν„°ν…Œν¬ 산업을 ν˜μ‹ ν•΄λ‚˜κ°ˆ 멀버λ₯Ό μ°ΎμŠ΅λ‹ˆλ‹€.

μ±„μš© 쀑인 곡고 보기