<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>dynamic-ddd 님의 블로그</title>
    <link>https://dynamic-ddd.tistory.com/</link>
    <description>역할의 경계를 넘어 유저에게 도달하는 프로덕트를 완성해온 메이커들의 커뮤니티입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 17 Jun 2026 14:38:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>dynamic-ddd</managingEditor>
    <image>
      <title>dynamic-ddd 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/8470537/attach/b07f4a8a0ae64b36ba6114c61651969f</url>
      <link>https://dynamic-ddd.tistory.com</link>
    </image>
    <item>
      <title>Tuist Plugin 만들기 + Settings 설정까지 완전 정복!</title>
      <link>https://dynamic-ddd.tistory.com/11</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;이 글을 쓰게 된 계기&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;안녕하세요! iOS 운영진 서원지입니다 ㅋㅋㅋ&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Tuist로 모듈화 하다 보면 이런 고민이 생겨요:&lt;/p&gt;
&lt;blockquote style=&quot;color: #333333; text-align: center;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;매번 비슷한 코드 반복하기 싫은데... 템플릿화 못 하나?&quot; &quot;환경별로 설정 다르게 하고 싶은데 어떻게 하지?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 오늘은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Tuist Plugin 만들기&lt;/b&gt;랑&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Settings 설정&lt;/b&gt;을 한 방에 정리해볼게요!  &lt;/p&gt;
&lt;blockquote style=&quot;color: #333333; text-align: center;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;⚠️ 제가 공부하면서 정리한 내용이라 틀린 부분 있을 수 있어요! 잘못된 부분 있으면 편하게 알려주세요  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Part 1: Tuist Plugin이란?&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Tuist Plugin은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Tuist 환경에서만 작동하는 확장 코드&lt;/b&gt;예요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이걸로 뭘 할 수 있냐면:&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;플러그인 유형역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ProjectTemplatePlugin모듈 템플릿 정의 (makeAppModule, makeModule)&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;DependencyPlugin&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;내부 모듈 enum 정의 (.Shared(.DesignSystem))&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;DependencyPackagePlugin&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;외부 라이브러리 정의 (SPM 설정)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;플러그인 디렉토리 구조&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;Plugins/
├── ProjectTemplatePlugin/
│   └── ProjectDescriptionHelpers/
│       └── TemplateHelpers.swift
├── DependencyPlugin/
│   └── ProjectDescriptionHelpers/
│       └── TargetDependency+Module.swift
└── DependencyPackagePlugin/
    ├── Package.swift
    └── ProjectDescriptionHelpers/
        └── Extension+TargetDependencySPM.swift&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote style=&quot;color: #333333; text-align: center;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;  플러그인 이름은 자유롭게! 근데 역할별로 명확하게 나누는 게 유지보수에 좋아요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Tuist.swift에서 플러그인 등록하기&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 루트에 Tuist.swift 파일 만들어서:&lt;/p&gt;
&lt;pre class=&quot;lisp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;import ProjectDescription
import Foundation

let tuist = Tuist(
    project: .tuist(
        compatibleXcodeVersions: .all,
        swiftVersion: .some(&quot;6.0.0&quot;),
        plugins: [
            .local(path: .relativeToRoot(&quot;Plugins/ProjectTemplatePlugin&quot;)),
            .local(path: .relativeToRoot(&quot;Plugins/DependencyPackagePlugin&quot;)),
            .local(path: .relativeToRoot(&quot;Plugins/DependencyPlugin&quot;)),
        ],
        generationOptions: .options(),
        installOptions: .options()
    )
)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Plugin.swift도 필요해요!&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 플러그인 폴더에 Plugin.swift 파일 필수!&lt;/p&gt;
&lt;pre class=&quot;ceylon&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;// Plugins/DependencyPackagePlugin/Plugin.swift
@preconcurrency import ProjectDescription

let plugin = Plugin(name: &quot;DependencyPackagePlugin&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이거 없으면 Tuist가 플러그인 인식 못 해요 ㅠㅠ&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;ModulePath로 모듈 경로 관리하기&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모듈이 많아지면 경로 관리가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;진짜 귀찮아요&lt;/b&gt;...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 ModulePath enum으로 계층 구조를 명확하게!&lt;/p&gt;
&lt;pre class=&quot;crystal&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;import Foundation
import ProjectDescription

public enum ModulePath {
    case Presentations(Presentations)
    case Core(Cores)
    case Network(Networks)
    case Interface(Interfaces)
    case Domain(Domains)
    case Data(Datas)
    case Shared(Shareds)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;각 모듈 계층 정의&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;// App 모듈
public extension ModulePath {
    enum App: String, CaseIterable {
        case iOS
        case iPad

        public static let name: String = &quot;App&quot;
    }
}

// Core 모듈
public extension ModulePath {
    enum Cores: String, CaseIterable {
        case Core

        public static let name: String = &quot;Core&quot;
    }
}

// Network 모듈
public extension ModulePath {
    enum Networks: String, CaseIterable {
        case API
        case Networks
        case Foundations
        case Service
        case ThirdPartys

        public static let name: String = &quot;Network&quot;
    }
}

// Data 모듈
public extension ModulePath {
    enum Datas: String, CaseIterable {
        case Model
        case Repository

        public static let name: String = &quot;Data&quot;
    }
}

// Domain 모듈
public extension ModulePath {
    enum Domains: String, CaseIterable {
        case UseCase
        case DomainInterface

        public static let name: String = &quot;Domain&quot;
    }
}

// Shared 모듈
public extension ModulePath {
    enum Shareds: String, CaseIterable {
        case Shareds
        case DesignSystem
        case Utill
        case ThirdParty

        public static let name: String = &quot;Shared&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  Path 확장으로 경로 자동화&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;매번 이렇게 쓰기 싫잖아요?&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;// ❌ 이렇게 하면 오타나기 쉬움
.relativeToRoot(&quot;Projects/Shared/DesignSystem&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Path Extension 만들기!&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;public extension ProjectDescription.Path {
    // App
    static var app: Self {
        return .relativeToRoot(&quot;Projects/\(ModulePath.App.name)&quot;)
    }

    // Shared
    static var Shared: Self {
        return .relativeToRoot(&quot;Projects/\(ModulePath.Shareds.name)&quot;)
    }

    static func Shared(implementation module: ModulePath.Shareds) -&amp;gt; Self {
        return .relativeToRoot(&quot;Projects/\(ModulePath.Shareds.name)/\(module.rawValue)&quot;)
    }

    // Network
    static var Networking: Self {
        return .relativeToRoot(&quot;Projects/\(ModulePath.Cores.name)/\(ModulePath.Networks.name)&quot;)
    }

    static func Network(implementation module: ModulePath.Networks) -&amp;gt; Self {
        return .relativeToRoot(&quot;Projects/\(ModulePath.Cores.name)/\(ModulePath.Networks.name)/\(module.rawValue)&quot;)
    }

    // Domain
    static var Domain: Self {
        return .relativeToRoot(&quot;Projects/\(ModulePath.Cores.name)/\(ModulePath.Domains.name)&quot;)
    }

    static func Domain(implementation module: ModulePath.Domains) -&amp;gt; Self {
        return .relativeToRoot(&quot;Projects/\(ModulePath.Cores.name)/\(ModulePath.Domains.name)/\(module.rawValue)&quot;)
    }

    // Data
    static var Data: Self {
        return .relativeToRoot(&quot;Projects/\(ModulePath.Cores.name)/\(ModulePath.Datas.name)&quot;)
    }

    static func Data(implementation module: ModulePath.Datas) -&amp;gt; Self {
        return .relativeToRoot(&quot;Projects/\(ModulePath.Cores.name)/\(ModulePath.Datas.name)/\(module.rawValue)&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;TargetDependency 확장으로 의존성 간결하게!&lt;/h2&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;// ❌ 이렇게 쓰면 길고 귀찮음
.project(target: &quot;DesignSystem&quot;, path: .relativeToRoot(&quot;Projects/Shared/DesignSystem&quot;))

// ✅ 이렇게 쓰면 깔끔!
.Shared(implements: .DesignSystem)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Extension 만들기&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;public extension TargetDependency {
    // Shared
    static func Shared(implements module: ModulePath.Shareds) -&amp;gt; Self {
        return .project(target: module.rawValue, path: .Shared(implementation: module))
    }

    // Network
    static func Network(implements module: ModulePath.Networks) -&amp;gt; Self {
        return .project(target: module.rawValue, path: .Network(implementation: module))
    }

    // Domain
    static func Domain(implements module: ModulePath.Domains) -&amp;gt; Self {
        return .project(target: module.rawValue, path: .Domain(implementation: module))
    }

    // Data
    static func Data(implements module: ModulePath.Datas) -&amp;gt; Self {
        return .project(target: module.rawValue, path: .Data(implementation: module))
    }

    // Core
    static func Core(implements module: ModulePath.Cores) -&amp;gt; Self {
        return .project(target: module.rawValue, path: .Core(implementation: module))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;사용 예시&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;let project = Project(
    name: &quot;LoginFeature&quot;,
    targets: [
        .target(
            name: &quot;LoginFeature&quot;,
            dependencies: [
                .Shared(implements: .DesignSystem),
                .Network(implements: .Service),
                .Domain(implements: .UseCase),
                .SPM.composableArchitecture
            ]
        )
    ]
)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;진짜 깔끔해요!&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; &lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt; ️ Project 템플릿 함수 만들기&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;makeAppModule - 앱 타겟용&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;static func makeAppModule(
    name: String = Environment.appName,
    bundleId: String,
    platform: Platform = .iOS,
    product: Product,
    packages: [Package] = [],
    deploymentTarget: DeploymentTargets = Environment.deploymentTarget,
    destinations: Destinations = Environment.deploymentDestination,
    settings: Settings,
    scripts: [TargetScript] = [],
    dependencies: [TargetDependency] = [],
    sources: SourceFilesList = [&quot;Sources/**&quot;],
    resources: ResourceFileElements? = nil,
    infoPlist: InfoPlist = .default,
    entitlements: Entitlements? = nil,
    schemes: [Scheme] = []
) -&amp;gt; Project&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생성되는 타겟:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;appTarget - 기본 앱 타겟&lt;/li&gt;
&lt;li&gt;appDevTarget - Debug 환경용&lt;/li&gt;
&lt;li&gt;appStageTarget - Stage 환경용&lt;/li&gt;
&lt;li&gt;appProdTarget - Prod 환경용&lt;/li&gt;
&lt;li&gt;appTestTarget - 유닛 테스트용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;makeModule - 일반 모듈용&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;static func makeModule(
    name: String = Environment.appName,
    bundleId: String,
    platform: Platform = .iOS,
    product: Product,
    packages: [Package] = [],
    deploymentTarget: DeploymentTargets = Environment.deploymentTarget,
    destinations: Destinations = Environment.deploymentDestination,
    settings: Settings,
    scripts: [TargetScript] = [],
    dependencies: [TargetDependency] = [],
    sources: SourceFilesList = [&quot;Sources/**&quot;],
    resources: ResourceFileElements? = nil,
    infoPlist: InfoPlist = .default,
    entitlements: Entitlements? = nil,
    schemes: [Scheme] = []
) -&amp;gt; Project&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;makeScheme - 스킴 템플릿&lt;/h3&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;extension Scheme {
    public static func makeScheme(target: ConfigurationName, name: String) -&amp;gt; Scheme {
        return Scheme.scheme(
            name: name,
            shared: true,
            buildAction: .buildAction(targets: [&quot;\(name)&quot;]),
            testAction: .targets(
                [&quot;\(name)Tests&quot;],
                configuration: target,
                options: .options(coverage: true, codeCoverageTargets: [&quot;\(name)&quot;])
            ),
            runAction: .runAction(configuration: target),
            archiveAction: .archiveAction(configuration: target),
            profileAction: .profileAction(configuration: target),
            analyzeAction: .analyzeAction(configuration: target)
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;사용 예시&lt;/h3&gt;
&lt;pre class=&quot;vim&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;let project = Project.makeModule(
    name: &quot;LoginFeature&quot;,
    bundleId: &quot;com.myapp.login&quot;,
    product: .framework,
    settings: .settings(configurations: [...]),
    dependencies: [
        .Domain(implements: .UseCase),
        .Shared(implements: .DesignSystem)
    ],
    schemes: [
        .makeScheme(target: .debug, name: &quot;LoginFeature&quot;)
    ]
)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  Part 2: Settings 설정 완전 정복&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서부터가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;진짜 중요한 부분&lt;/b&gt;이에요!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;환경별로 설정 다르게 하고 싶을 때 Settings.swift를 활용합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;commonSettings 함수&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;환경별로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;중복되는 설정을 재사용&lt;/b&gt;하기 위한 유틸 함수:&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;private static func commonSettings(
    appName: String,
    displayName: String,
    provisioningProfile: String,
    setSkipInstall: Bool
) -&amp;gt; SettingsDictionary {
    return SettingsDictionary()
        .setProductName(appName)
        .setCFBundleDisplayName(displayName)
        .setOtherLdFlags(&quot;-ObjC -all_load&quot;)
        .setDebugInformationFormat(&quot;dwarf-with-dsym&quot;)
        .setProvisioningProfileSpecifier(provisioningProfile)
        .setSkipInstall(setSkipInstall)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;설정 항목 설명&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;메서드설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setProductName실행 파일 이름 (PRODUCT_NAME)&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;setCFBundleDisplayName&lt;/td&gt;
&lt;td&gt;홈 화면에 표시될 앱 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setOtherLdFlags&lt;/td&gt;
&lt;td&gt;Objective-C 런타임 로딩 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setDebugInformationFormat&lt;/td&gt;
&lt;td&gt;.dSYM 생성 (크래시 추적용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setProvisioningProfileSpecifier&lt;/td&gt;
&lt;td&gt;서명에 사용할 프로파일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setSkipInstall&lt;/td&gt;
&lt;td&gt;아카이브 대상 포함 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  appMainSetting - 앱 전용 빌드 설정&lt;/h2&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;public static let appMainSetting: Settings = .settings(
    base: SettingsDictionary()
        .setProductName(Project.Environment.appName)
        .setCFBundleDisplayName(Project.Environment.appName)
        .setMarketingVersion(.appVersion())
        .setASAuthenticationServicesEnabled()       // Apple 로그인
        .setPushNotificationsEnabled()              // 푸시 알림
        .setEnableBackgroundModes()                 // 백그라운드 모드
        .setArchs()
        .setOtherLdFlags()
        .setCurrentProjectVersion(.appBuildVersion())
        .setCodeSignIdentity()
        .setCodeSignStyle()
        .setSwiftVersion(&quot;6.0&quot;)
        .setVersioningSystem()
        .setProvisioningProfileSpecifier(&quot;match Development \(Project.Environment.bundlePrefix)&quot;)
        .setDevelopmentTeam(Project.Environment.organizationTeamId)
        .setCFBundleDevelopmentRegion()
        .setDebugInformationFormat(),

    configurations: [
        //   Debug
        .debug(
            name: .debug,
            settings: commonSettings(
                appName: Project.Environment.appName,
                displayName: Project.Environment.appName,
                provisioningProfile: &quot;match Development \(Project.Environment.bundlePrefix)&quot;,
                setSkipInstall: false
            ),
            xcconfig: .relativeToRoot(&quot;./Config/dev.xcconfig&quot;)
        ),

        //   Stage (QA용)
        .debug(
            name: &quot;Stage&quot;,
            settings: commonSettings(
                appName: Project.Environment.appStageName,
                displayName: Project.Environment.appName,
                provisioningProfile: &quot;match Development \(Project.Environment.bundlePrefix)&quot;,
                setSkipInstall: false
            ),
            xcconfig: .relativeToRoot(&quot;./Config/qa.xcconfig&quot;)
        ),

        //   Release
        .release(
            name: .release,
            settings: commonSettings(
                appName: Project.Environment.appName,
                displayName: Project.Environment.appName,
                provisioningProfile: &quot;match AppStore \(Project.Environment.bundlePrefix)&quot;,
                setSkipInstall: false
            ),
            xcconfig: .relativeToRoot(&quot;./Config/realse.xcconfig&quot;)
        ),

        //   Prod (실제 배포)
        .release(
            name: &quot;Prod&quot;,
            settings: commonSettings(
                appName: Project.Environment.appProdName,
                displayName: Project.Environment.appName,
                provisioningProfile: &quot;match AppStore \(Project.Environment.bundlePrefix)&quot;,
                setSkipInstall: false
            ),
            xcconfig: .relativeToRoot(&quot;./Config/realse.xcconfig&quot;)
        )
    ],
    defaultSettings: .recommended
)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  base 설정 항목 정리&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;항목설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setProductName빌드 타겟의 실행 파일 이름&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;setCFBundleDisplayName&lt;/td&gt;
&lt;td&gt;디바이스에 표시되는 앱 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setMarketingVersion&lt;/td&gt;
&lt;td&gt;사용자에게 보이는 앱 버전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setASAuthenticationServicesEnabled&lt;/td&gt;
&lt;td&gt;Apple 로그인 활성화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setPushNotificationsEnabled&lt;/td&gt;
&lt;td&gt;푸시 알림 권한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setEnableBackgroundModes&lt;/td&gt;
&lt;td&gt;백그라운드 fetch, location 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setArchs&lt;/td&gt;
&lt;td&gt;아키텍처 설정 (arm64)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setCurrentProjectVersion&lt;/td&gt;
&lt;td&gt;빌드 넘버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setCodeSignIdentity&lt;/td&gt;
&lt;td&gt;서명 인증서&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setCodeSignStyle&lt;/td&gt;
&lt;td&gt;Automatic or Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setSwiftVersion&lt;/td&gt;
&lt;td&gt;Swift 버전 고정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setDevelopmentTeam&lt;/td&gt;
&lt;td&gt;Apple Developer Team ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setDebugInformationFormat&lt;/td&gt;
&lt;td&gt;.dSYM 생성 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  Configuration별 구성&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구성용도xcconfig&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.debug개발용, 디버깅dev.xcconfig&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&quot;Stage&quot;&lt;/td&gt;
&lt;td&gt;QA/테스트용&lt;/td&gt;
&lt;td&gt;qa.xcconfig&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.release&lt;/td&gt;
&lt;td&gt;내부 릴리즈용&lt;/td&gt;
&lt;td&gt;release.xcconfig&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;Prod&quot;&lt;/td&gt;
&lt;td&gt;실제 App Store 배포&lt;/td&gt;
&lt;td&gt;release.xcconfig&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote style=&quot;color: #333333; text-align: center;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;  Stage, Prod는 Tuist 기본 enum이 아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;문자열로 직접 지정&lt;/b&gt;해야 해요!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  활용 팁&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Prod와 Release를 분리하는 이유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;CI 파이프라인 분리&lt;/b&gt;: release는 내부 테스트, prod는 실제 배포&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설정 분기&lt;/b&gt;: 앱 이름, 아이콘, API 엔드포인트 등 다르게 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;xcconfig 활용&lt;/h3&gt;
&lt;pre class=&quot;ini&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;# dev.xcconfig
API_BASE_URL = https://dev-api.myapp.com
FEATURE_FLAG_DEBUG = YES

# release.xcconfig  
API_BASE_URL = https://api.myapp.com
FEATURE_FLAG_DEBUG = NO&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote style=&quot;color: #333333; text-align: center;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;민감한 정보&lt;/b&gt;는 .gitignore에 추가하거나 암호화된 저장소에서 관리하세요!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;✅ 전체 정리&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Plugin 정리&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;항목내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plugin 구조ProjectTemplatePlugin, DependencyPlugin, DependencyPackagePlugin&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;ModulePath&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;enum 기반 모듈 경로 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Path 확장&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;.Shared(implementation:) 등으로 경로 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;TargetDependency 확장&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;.Shared(implements:) 등으로 의존성 간결화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;템플릿 함수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;makeAppModule, makeModule, makeScheme&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Settings 정리&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;항목내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 설정 추출commonSettings() 함수로 중복 제거&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;코드 기반 설정&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Git으로 추적 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;환경별 분리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Debug, Stage, Release, Prod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;xcconfig 연동&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;민감한 설정 외부 파일로 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Tuist Plugin이랑 Settings 설정, 처음엔 좀 복잡해 보이는데...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 번 세팅해두면 진짜 편해요!&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;ModulePath + Path Extension&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;= 경로 오타 방지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TargetDependency Extension&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;= 의존성 선언 깔끔&lt;/li&gt;
&lt;li&gt;&lt;b&gt;makeModule 템플릿&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;= 새 모듈 추가 쉬움&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Settings 분리&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;= 환경별 빌드 설정 명확&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>iOS</category>
      <author>dynamic-ddd</author>
      <guid isPermaLink="true">https://dynamic-ddd.tistory.com/11</guid>
      <comments>https://dynamic-ddd.tistory.com/11#entry11comment</comments>
      <pubDate>Sun, 5 Apr 2026 13:51:01 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Tuist를 쓰면서 Asset 코드 생성은 직접 만들기로 한 이유</title>
      <link>https://dynamic-ddd.tistory.com/10</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 사이드 프로젝트 DDD 동아리의 iOS 운영진 은표입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 프로젝트를 하다 보면, Tuist를 통해서든 다른 방식으로든 모듈화를 한 번쯤 고민하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Tuist를 사용하여 프로젝트 모듈화를 진행해봤는데요, 처음에는 정말 만족스러웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;git rebase&lt;/b&gt; 할 때 &lt;b&gt;.pbxproj &lt;/b&gt;파일 충돌도 나지 않고, 프로젝트 전체 구조를 내가 작성한 코드에 맞춰 알아서 잘 생성해주니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 프로젝트를 진행하다 보니 문득 이런 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b14kyR/dJMcahRnp0R/ZCHZJ8x0fhUhoBaWQKkgsk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b14kyR/dJMcahRnp0R/ZCHZJ8x0fhUhoBaWQKkgsk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b14kyR/dJMcahRnp0R/ZCHZJ8x0fhUhoBaWQKkgsk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb14kyR%2FdJMcahRnp0R%2FZCHZJ8x0fhUhoBaWQKkgsk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;320&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Asset 접근할 때 내가 쓰고 있는 방식이 진짜 좋은 방법이 맞나?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 별생각이 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist의 &lt;b&gt;generate&lt;/b&gt; 과정에서 자동으로 만들어지는 코드를 통해 Asset에 접근하면 오타도 줄고, 자동완성도 되고 꽤 편했으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 프로젝트를 진행할수록 조금씩 거슬리기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Asset을 하나 추가하고 나서 바로 화면에서 확인하고 싶은데 Asset 접근 코드를 생성하는 과정이 &lt;b&gt;tuist generate&lt;/b&gt; 에 묶여있어 생각보다 자주 작업 흐름이 끊기게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 최근에는 &lt;b&gt;buildable folders&lt;/b&gt;를 적극적으로 활용하면 관리가 훨씬 편해지는 부분이 있는데, 정작 Tuist를 사용하면 이 장점을 살리지 못하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 문제를 저는 어떤 고민을 했고 최종적으로, 어떻게 결정했는지에 대한 과정을 공유드리려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  해결책 찾기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 떠오른건 Xcode나 &lt;b&gt;Asset Catalog&lt;/b&gt;가 기본으로 제공하는 자동 생성 기능을 적극적으로 활용하는 방법이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 단일 앱 타겟에서는 꽤 만족스럽게 쓸 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 사용성도 괜찮고, 별도 도구 없이 시작할 수 있다는 장점도 분명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 프로젝트 구조가 조금만 복잡해지면 또 다시 아쉬움이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Asset을 담아놓은 모듈이 따로 있고, 그걸 여러 모듈이 공통으로 참조하는 구조라면 이야기가 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 &lt;code&gt;dynamic framework&lt;/code&gt; 같은 형태에서는 접근 제어나 번들 처리, 사용 방식에서 미묘하게 불편한 부분이 생기기 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동으로 만들어주긴 하는데, 결국 제가 원하는 방식으로는 쓰지 못하게 되더라구요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;609&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bycdGC/dJMcaaEKJQO/yQafSTzTBLB5hdgVu7Iye1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bycdGC/dJMcaaEKJQO/yQafSTzTBLB5hdgVu7Iye1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bycdGC/dJMcaaEKJQO/yQafSTzTBLB5hdgVu7Iye1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbycdGC%2FdJMcaaEKJQO%2FyQafSTzTBLB5hdgVu7Iye1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;609&quot; height=&quot;339&quot; data-origin-width=&quot;609&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  다시 옛날으로..?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다보니 옛날에 쓰던 라이브러리들이 다시 떠오릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;R.swift&lt;/code&gt;&lt;/b&gt; 와 &lt;b&gt;&lt;code&gt;SwiftGen&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이쯤 되면 이 둘은 꽤 현실적인 선택지입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 많은 팀에서 사용해왔고&lt;/li&gt;
&lt;li&gt;문자열 기반 접근보다 훨씬 안전하고&lt;/li&gt;
&lt;li&gt;빌드 전에 코드를 생성한다는 흐름도 직관적입니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;그냥 다시 쓸까?&lt;/b&gt; 했는데, 막상 다시 보니 완전히 마음에 들지는 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 기능 부족이 아니라, 생성되는 코드의 형태였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 팀은 메서드 기반 접근이 더 명확하다고 느낄 수 있습니다.&lt;br /&gt;그런데 어떤 팀은 &lt;b&gt;static property&lt;/b&gt; 중심의 API를 훨씬 더 선호합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 차이는 사소해 보여도, 코드베이스 전체에서는 꽤 크게 남습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 저는 아래 같은 형태가 더 읽기 좋다고 느꼈습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let image: UIImage = Asset.Image.Home.banner&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 스타일은 사용성 자체도 깔끔하고, 생성 규칙도 단순한 편입니다.&lt;br /&gt;반대로 어떤 생성 도구는 이와 조금 다른 형태를 기본으로 내보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;물론 그게 틀렸다는 건 아니지만, 팀 스타일과 안 맞으면 결국 또 래핑하거나 적응해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 보니 생각이 조금씩 바뀌었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기성 도구를 어떻게든 맞춰 쓰는 것보다, 옛날과 달리 성능이 기가 막힌 LLM도 있겠다 필요한 만큼만 직접 만드는 게 더 낫지 않을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 최종 결정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;757&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dlfxo/dJMcajayOT4/g0kR5S19tx8jxXfmKkKtUK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dlfxo/dJMcajayOT4/g0kR5S19tx8jxXfmKkKtUK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dlfxo/dJMcajayOT4/g0kR5S19tx8jxXfmKkKtUK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDlfxo%2FdJMcajayOT4%2Fg0kR5S19tx8jxXfmKkKtUK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;331&quot; height=&quot;358&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;757&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결국은 커스텀 스크립트를 만들기로 했습니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;비슷한 고민을 먼저 해본 팀들의 사례를 찾아보면, 생각보다 많은 팀이 결국 커스텀 스크립트 쪽으로 가 있었습니다.&lt;br /&gt;&lt;br /&gt;방식은 단순합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Asset 폴더를 스캔하고&lt;/li&gt;
&lt;li&gt;이름 규칙에 맞춰 코드를 생성하고&lt;/li&gt;
&lt;li&gt;실제 Asset이 존재하는지 확인하는 테스트도 같이 만든다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 작업자는 아래와 같은 명령 하나만 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774623051315&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;make generate_asset&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 방식이 좋았던 이유는 아주 명확했습니다.&lt;br /&gt;&lt;br /&gt;첫 번째는 &lt;b&gt;tuist generate&lt;/b&gt;에 덜 의존할 수 있다는 점입니다.&lt;br /&gt;Asset 하나 추가할 때마다 프로젝트 생성 타이밍을 다시 의식하지 않아도 됩니다.&lt;br /&gt;&lt;br /&gt;두 번째는 생성되는 API를 팀 스타일에 맞출 수 있다는 점입니다.&lt;br /&gt;이름 규칙, 폴더 구조 반영 방식, 접근 형태, 공개 범위까지 전부 우리가 정할 수 있습니다.&lt;br /&gt;&lt;br /&gt;세 번째는 예외 처리도 우리가 결정할 수 있다는 점입니다.&lt;br /&gt;예를 들어 app_Icon와 appIcon처럼 코드 생성 시 이름이 충돌하는 경우를 어떻게 처리할지, 특정 폴더는 생성 대상에서 제외할지 같은 규칙도 넣을 수 있습니다.&lt;br /&gt;&lt;br /&gt;즉, 이 시점부터는 &lt;b&gt;기성 도구가 얼마나 완벽하냐&lt;/b&gt;보다 &lt;b&gt;우리 팀이 어떤 개발 경험을 원하느냐&lt;/b&gt;가 훨씬 중요해집니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✍️ 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 경험을 통해 느낀 건, Asset 접근 방식도 결국 팀의 개발 경험을 설계하는 문제라는 점이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 저에게는 &lt;b&gt;tuist generate&lt;/b&gt;에 계속 의존하는 흐름이 생각보다 큰 마찰로 다가왔고, 그 과정에서 &amp;ldquo;무엇을 쓸까?&amp;rdquo;보다 &amp;ldquo;우리는 어떤 흐름으로 작업하고 싶은가?&amp;rdquo;가 더 중요한 질문이라는 걸 체감하게 됐습니다.&lt;br /&gt;&lt;br /&gt;또 하나 흥미로웠던 점은, 예전 같았으면 직접 스크립트를 만든다는 선택이 꽤 무겁게 느껴졌을 텐데 지금은 그렇지 않다는 점입니다.&lt;br /&gt;&lt;br /&gt;반복적인 코드 생성이나 초안 작성은 LLM의 도움을 받으면 훨씬 빠르게 시작할 수 있고, 그 덕분에 팀은 정말 필요한 규칙과 API 모양을 정하는 데 더 집중할 수 있게 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉽게도 이번 글에서는 예시 코드를 크게 다루지 않았습니다.&lt;br /&gt;중요한 건 구현 디테일보다는, 각 팀이 원하는 사용 방식과 생성 흐름을 먼저 정하는 일이라고 생각했기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답이 하나로 정해져 있다고 생각하지는 않습니다.&lt;br /&gt;어떤 팀에는 &lt;b&gt;Tuist&lt;/b&gt; 기본 기능이 더 잘 맞을 수도 있고, 어떤 팀에는 &lt;b&gt;R.swift&lt;/b&gt;나 &lt;b&gt;SwiftGen&lt;/b&gt;이 더 나은 선택일 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 한 가지는 분명했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;tuist generate&lt;/b&gt;를 예로 들었지만, 특정 도구의 기본 기능이 팀의 워크플로우와 어긋나기 시작하는 순간, 그 의존성을 줄이기 위한 작은 도구를 직접 만드는 선택도 충분히 현실적이라는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그때부터 중요한 건 &lt;b&gt;무엇을 쓸까? &lt;/b&gt;보다 &lt;b&gt;우리는 어떤 흐름으로 일하고 싶은가? &lt;/b&gt;라는 질문이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 고민을 하고 계신다면, 파이썬으로 작은 생성 스크립트를 한 번 직접 만들어서 사용해보시길 권장합니다.&lt;br /&gt;생각보다 금방 만들 수 있고, 한 번 팀 스타일에 맞게 돌아가기 시작하면 왜 직접 만들고 싶어졌는지 금방 체감하게 됩니다.&lt;br /&gt;&lt;br /&gt;긴 글 읽어주셔서 감사합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✍️ 작성자: &lt;/b&gt;은표&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;직군:&lt;/b&gt; iOS Developer&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GitHub: &lt;a href=&quot;https://github.com/honghoker&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/honghoker&lt;/a&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>iOS</category>
      <category>Asset</category>
      <category>ios</category>
      <category>Tuist</category>
      <author>dynamic-ddd</author>
      <guid isPermaLink="true">https://dynamic-ddd.tistory.com/10</guid>
      <comments>https://dynamic-ddd.tistory.com/10#entry10comment</comments>
      <pubDate>Sat, 28 Mar 2026 18:47:59 +0900</pubDate>
    </item>
    <item>
      <title>[Android] java.io.stream에 대한 고찰</title>
      <link>https://dynamic-ddd.tistory.com/9</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! ✋&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트 동아리 DDD에서 Android 개발자로 활동하고 있는 오세민입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Java 개발을 하다 보면 반드시 마주치게 되는 주제,&lt;br /&gt;&lt;b&gt;Stream, InputStream, OutputStream&lt;/b&gt;의 설계 철학에 대해 이야기해보려고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stream이라는 개념이 왜 존재하는지&lt;/li&gt;
&lt;li&gt;왜 입력과 출력이 분리되어 있는지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flush()&lt;/code&gt;와 &lt;code&gt;close()&lt;/code&gt;는 왜 필요한지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 질문들을 중심으로&lt;br /&gt;&lt;b&gt;&quot;왜 이렇게 설계되었을까?&quot;&lt;/b&gt;라는 질문에서 시작해 보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Stream은 뭘까요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream이라는 단어를 처음 들으면 Java 8의 &lt;code&gt;Stream API&lt;/code&gt;가 떠오를 수도 있어요.&lt;br /&gt;하지만 여기서 이야기하는 Stream은 그것보다 훨씬 근본적인 개념이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Stream은 &quot;데이터의 흐름(Flow)&quot;을 추상화한 구조예요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 설계 철학은 이래요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 입출력은 Stream을 통해 이루어진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일이든, 네트워크든, 디바이스든&lt;br /&gt;동일한 방식으로 다룰 수 있도록&lt;br /&gt;데이터를 &lt;b&gt;'흐름(Stream)'&lt;/b&gt;이라는 개념으로 통일한 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Stream을 이렇게 정리할 수 있어요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream = &quot;JVM &amp;harr; OS 간 데이터 통로&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM이 OS의 입출력 시스템(파일, 네트워크, 디바이스 등)과 통신하기 위한&lt;br /&gt;&lt;b&gt;통일된 인터페이스&lt;/b&gt;라고 보면 돼요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실제로는 어떤 리소스와 연결될까요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream이라는 하나의 추상화 아래에는&lt;br /&gt;실제로 다양한 리소스가 연결되어 있어요.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;실제 리소스&lt;/th&gt;
&lt;th&gt;Stream 구현체&lt;/th&gt;
&lt;th&gt;OS 레벨 매핑&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;파일&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FileInputStream&lt;/code&gt; / &lt;code&gt;FileOutputStream&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;file descriptor (fd)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;네트워크 소켓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SocketInputStream&lt;/code&gt; / &lt;code&gt;SocketOutputStream&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;socket fd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파이프&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PipedInputStream&lt;/code&gt; / &lt;code&gt;PipedOutputStream&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pipe fd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ByteArrayInputStream&lt;/code&gt; / &lt;code&gt;ByteArrayOutputStream&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JVM heap 메모리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 읽든, 소켓으로 데이터를 보내든,&lt;br /&gt;코드에서 다루는 방식은 동일해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 바로 Stream이라는 추상화가 가져다주는 힘이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;harr;️ 왜 InputStream과 OutputStream으로 나뉘어 있을까요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java는 Stream을 &lt;b&gt;단방향(one-way)&lt;/b&gt;으로 설계했어요.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;방향&lt;/th&gt;
&lt;th&gt;주요 메서드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;InputStream&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;입력 (read)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;read()&lt;/code&gt;, &lt;code&gt;read(byte[])&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OutputStream&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;출력 (write)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;write()&lt;/code&gt;, &lt;code&gt;flush()&lt;/code&gt;, &lt;code&gt;close()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 분리한 이유는 명확해요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 흐름의 &lt;b&gt;책임을 명확히&lt;/b&gt; 하고,&lt;br /&gt;&lt;b&gt;예측 가능한 I/O&lt;/b&gt;를 보장하기 위해서예요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 Stream은 읽기만 한다&quot; 또는 &quot;이 Stream은 쓰기만 한다&quot;가&lt;br /&gt;코드 레벨에서 명확하게 드러나는 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양방향 통신이 필요한 Socket 같은 경우에도&lt;br /&gt;내부적으로는 &lt;code&gt;InputStream + OutputStream&lt;/code&gt; 쌍으로 처리해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;데이터는 한 방향으로만 흐른다&quot;&lt;/b&gt;는 철학을 일관되게 지키는 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 왜 추상 클래스로 설계했을까요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;InputStream&lt;/code&gt;과 &lt;code&gt;OutputStream&lt;/code&gt;은 인터페이스가 아니라 &lt;b&gt;추상 클래스&lt;/b&gt;예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 이래요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일, 네트워크, 메모리 등&lt;br /&gt;&lt;b&gt;다양한 데이터 소스를 동일한 인터페이스로 다루기 위해서&lt;/b&gt;예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 보면 이렇게 생겼어요.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;            InputStream / OutputStream  &amp;larr; 추상화 계층 (Closeable)
                         │
     ┌───────────────────┼─────────────────────┐
     │                   │                     │
FileInputStream     SocketInputStream     ByteArrayInputStream
(fd 기반)            (fd 기반)              (메모리 기반)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Stream은 동일한 추상 메서드를 통해 동작하지만,&lt;br /&gt;각 구현체는 자신이 다루는 리소스(fd, socket, memory)에 따라&lt;br /&gt;&lt;b&gt;실제 I/O 방식을 다르게 정의&lt;/b&gt;해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 우리는 &quot;이게 파일인지 소켓인지&quot;를 신경 쓰지 않고&lt;br /&gt;동일한 코드로 데이터를 읽고 쓸 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Closeable을 상속한 이유가 뭘까요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream이 OS 리소스와 연결된 이상,&lt;br /&gt;그 통로(fd)는 &lt;b&gt;커널에 의해 관리되며 유한한 리소스&lt;/b&gt;예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Closeable&lt;/code&gt;을 상속한 이유는 명확해요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream이 열리면 OS에 리소스가 등록되고,&lt;br /&gt;&lt;code&gt;close()&lt;/code&gt;는 그 리소스를 OS에 반납해요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계별로 보면 이래요.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;동작&lt;/th&gt;
&lt;th&gt;대응되는 OS 동작&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stream 생성&lt;/td&gt;
&lt;td&gt;OS에 &lt;code&gt;open()&lt;/code&gt; syscall 호출&lt;/td&gt;
&lt;td&gt;fd 할당&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 I/O&lt;/td&gt;
&lt;td&gt;&lt;code&gt;read()&lt;/code&gt;/&lt;code&gt;write()&lt;/code&gt; 호출&lt;/td&gt;
&lt;td&gt;커널 버퍼를 통한 I/O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;close()&lt;/code&gt; 호출&lt;/td&gt;
&lt;td&gt;OS에 &lt;code&gt;close(fd)&lt;/code&gt; syscall&lt;/td&gt;
&lt;td&gt;fd 해제, 커널 리소스 반납&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;code&gt;close()&lt;/code&gt;는 이렇게 이해하면 돼요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;close()&lt;/code&gt; = &quot;JVM이 OS에게 &lt;b&gt;이제 이 통로(fd)를 닫아도 좋습니다&lt;/b&gt;&quot;라는 신호&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;close()&lt;/code&gt;를 호출하지 않으면&lt;br /&gt;fd가 계속 열린 채로 남아서 &lt;b&gt;리소스 누수(resource leak)&lt;/b&gt;가 발생해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 단순히 메모리 문제가 아니라,&lt;br /&gt;OS 레벨의 리소스 고갈로 이어질 수 있는 심각한 문제예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  flush는 단순히 &quot;버퍼를 비우는 것&quot;이 아니에요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;flush()&lt;/code&gt;를 &quot;버퍼를 비운다&quot;고만 알고 계신 분이 많을 거예요.&lt;br /&gt;하지만 조금 더 정확하게 말하면 이래요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;flush()&lt;/code&gt; = &quot;지연된 I/O를 즉시 수행하여&lt;br /&gt;&lt;b&gt;논리적 상태와 물리적 상태를 일치시키는 행위&lt;/b&gt;&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터는 여러 단계의 버퍼를 거쳐요.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[앱 코드] &amp;rarr; [JVM 버퍼] &amp;rarr; [OS 커널 버퍼] &amp;rarr; [디스크/네트워크]&lt;/code&gt;&lt;/pre&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;계층&lt;/th&gt;
&lt;th&gt;flush 대상&lt;/th&gt;
&lt;th&gt;대표 메서드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JVM 버퍼&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BufferedOutputStream&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;flush()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS 버퍼&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FileDescriptor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sync()&lt;/code&gt; (fsync(fd))&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;flush()&lt;/code&gt;는 &lt;b&gt;&quot;현재 계층의 데이터를 다음 계층으로 강제 전송(commit)&quot;&lt;/b&gt;하는 역할을 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;flush()&lt;/code&gt; = &lt;b&gt;&quot;이제 진짜로 보내라&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 버퍼를 OS 커널로,&lt;br /&gt;OS 버퍼를 디스크로 내보내는 &lt;b&gt;타이밍 제어 수단&lt;/b&gt;이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 통신에서 &lt;code&gt;flush()&lt;/code&gt;를 빼먹으면&lt;br /&gt;데이터가 버퍼에 머물러서 상대방에게 도착하지 않는 경우도 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;code&gt;flush()&lt;/code&gt;는 &lt;b&gt;데이터 일관성을 보장하는 중요한 메서드&lt;/b&gt;예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  설계 철학을 한눈에 정리하면요&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stream&lt;/td&gt;
&lt;td&gt;데이터 흐름(Flow)의 추상화. JVM &amp;harr; OS I/O의 논리적 통로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InputStream / OutputStream&lt;/td&gt;
&lt;td&gt;OS의 read/write 개념을 분리하여 표현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Closeable&lt;/td&gt;
&lt;td&gt;OS 리소스(fd, socket 등)를 명시적으로 해제하는 계약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flush()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;지연된 I/O를 즉시 커밋하여 데이터 일관성 보장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단방향성&lt;/td&gt;
&lt;td&gt;데이터 흐름의 책임과 안정성을 확보하기 위한 구조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;추상화 목적&lt;/td&gt;
&lt;td&gt;파일, 네트워크, 메모리 등 모든 I/O를 일관된 인터페이스로 다루기 위함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 정리해보면 이렇습니다&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Stream&lt;/b&gt;은 &quot;JVM과 OS 사이의 통로&quot;예요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;InputStream과 OutputStream&lt;/b&gt;은 그 통로를 통해 데이터를 &quot;읽고/쓰는 방향&quot;을 정의해요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;flush()&lt;/code&gt;&lt;/b&gt;는 데이터를 다음 계층으로 커밋하는 시점 제어 도구예요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;close()&lt;/code&gt;&lt;/b&gt;는 OS 리소스를 해제하는 명시적 종료 신호예요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 Stream은 &lt;b&gt;&quot;I/O를 일관된 추상화로 표현한 계약(Contract)&quot;&lt;/b&gt;이며,&lt;br /&gt;Java의 일관성과 안전성 철학을 가장 잘 보여주는 구조예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇을 쓰느냐보다&lt;br /&gt;&lt;b&gt;왜 이런 구조가 존재하는지 이해하는 게 더 중요해요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I/O는 조용히 동작하지만,&lt;br /&gt;문제가 생기면 고치기 어렵거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글 읽어주셔서 감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  레퍼런스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 정리하면서 실제로 참고한 자료들이에요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html&quot;&gt;Java InputStream - Oracle Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html&quot;&gt;Java OutputStream - Oracle Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/io/Closeable.html&quot;&gt;Closeable - Oracle Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/essential/io/&quot;&gt;Java I/O Tutorial - Oracle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Android</category>
      <author>dynamic-ddd</author>
      <guid isPermaLink="true">https://dynamic-ddd.tistory.com/9</guid>
      <comments>https://dynamic-ddd.tistory.com/9#entry9comment</comments>
      <pubDate>Sun, 15 Mar 2026 01:26:42 +0900</pubDate>
    </item>
    <item>
      <title>요즘 주니어 디자이너에게 더 중요해진 능력: 툴 vs 사고력</title>
      <link>https://dynamic-ddd.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;DDD&lt;/span&gt;에서&lt;span&gt; &lt;/span&gt;디자인&lt;span&gt; &lt;/span&gt;운영진을&lt;span&gt; &lt;/span&gt;맡고&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;이윤경입니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;오늘은 &lt;b&gt;A&lt;span&gt;i&lt;/span&gt;시대에&lt;span&gt;&amp;nbsp;&lt;/span&gt;생각하는&lt;span&gt; &lt;/span&gt;디자이너로&lt;span&gt; &lt;/span&gt;살아남기&lt;/b&gt;위한 글을 가져왔습니다!&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인을 시작하면 가장 먼저 배우는 것은 보통 툴입니다.&lt;br /&gt;프로그램과 AI를 얼마나 잘 다루는지가 실력의 기준처럼 느껴지기도 합니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제&lt;span&gt; &amp;lsquo;&lt;/span&gt;툴을&lt;span&gt; &lt;/span&gt;잘&lt;span&gt; &lt;/span&gt;쓰는&lt;span&gt; &lt;/span&gt;디자이너&lt;span&gt;&amp;rsquo;&lt;/span&gt;만으로는&lt;span&gt; &lt;/span&gt;경쟁력이&lt;span&gt; &lt;/span&gt;되기&lt;span&gt; &lt;/span&gt;어렵습니다&lt;span&gt;.&lt;br /&gt;&amp;ldquo;&lt;/span&gt;툴을&lt;span&gt; &lt;/span&gt;잘&lt;span&gt; &lt;/span&gt;쓰는&lt;span&gt; &lt;/span&gt;디자이너&lt;span&gt;&amp;rdquo;&lt;/span&gt;보다&lt;b&gt;&lt;i&gt;&lt;span&gt; &amp;ldquo;&lt;/span&gt;생각을&lt;span&gt; &lt;/span&gt;할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;디자이너&lt;span&gt;&amp;rdquo;&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;가&lt;span&gt; &lt;/span&gt;더&lt;span&gt; &lt;/span&gt;중요해지고&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;서론. 툴 실력은 기본값이 되어버렸다  &lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 손이 빠르고 기술이 많은 디자이너가 경쟁력이 있었습니다.&lt;br /&gt;하지만 지금은 상황이 많이 달라졌습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디자인 툴의 진입 장벽이 낮아졌고&lt;/li&gt;
&lt;li&gt;온라인 강의와 유튜브, 다양한 템플릿이 넘쳐나며&lt;/li&gt;
&lt;li&gt;AI 툴까지 등장했습니다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &amp;lsquo;툴을 다룰 줄 안다&amp;rsquo;는 것은&lt;br /&gt;강점이라기보다 기본 조건에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;툴은&lt;span&gt; &lt;/span&gt;배우면&lt;span&gt; &lt;/span&gt;익숙해질&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있지만&lt;span&gt;, &lt;/span&gt;문제를&lt;span&gt; &lt;/span&gt;어떻게&lt;span&gt; &lt;/span&gt;바라보는지는&lt;span&gt; &lt;/span&gt;쉽게&lt;span&gt; &lt;/span&gt;배워지지&lt;span&gt; &lt;/span&gt;않습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-06 오전 12.44.14.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6DF4f/dJMcachZcJ7/UWSgICDGsppT5glz8kniw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6DF4f/dJMcachZcJ7/UWSgICDGsppT5glz8kniw0/img.png&quot; data-alt=&quot;이미지 출처: Pinterest&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6DF4f/dJMcachZcJ7/UWSgICDGsppT5glz8kniw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6DF4f%2FdJMcachZcJ7%2FUWSgICDGsppT5glz8kniw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;376&quot; height=&quot;228&quot; data-filename=&quot;스크린샷 2026-03-06 오전 12.44.14.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처: Pinterest&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 시대에 더 중요해진 사고력 &lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 이미 이미지 생성, 레이아웃 제안, 색상 추천까지 해주고 있습니다.&lt;br /&gt;앞으로 툴을 &amp;lsquo;조작하는 능력&amp;rsquo;은 더 빠르게 자동화될 가능성이 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 디자이너는 무엇을 해야 할까요?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무엇을 만들지 결정하고&lt;/li&gt;
&lt;li&gt;어떤 방향이 맞는지 판단하며&lt;/li&gt;
&lt;li&gt;그 결과를 평가하는 역할입니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 영역은 여전히 AI보다 사람이 더 잘할 수 있는 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉&lt;span&gt;, AI &lt;/span&gt;시대의&lt;span&gt; &lt;/span&gt;디자이너는&lt;span&gt;&lt;br /&gt;&lt;/span&gt;손이&lt;span&gt; &lt;/span&gt;빠른&lt;span&gt; &lt;/span&gt;사람이&lt;span&gt; &lt;/span&gt;아니라&lt;span&gt; &lt;/span&gt;&lt;u&gt;&lt;b&gt;판단을&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;잘하는&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;사람&lt;/b&gt;&lt;/u&gt;이&lt;span&gt; &lt;/span&gt;되어야&lt;span&gt; &lt;/span&gt;합니다&lt;span&gt;.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;본론. 사고력이란 무엇일까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인에서 말하는 사고력은 &amp;lsquo;아이디어가 많은 것&amp;rsquo;이나 &amp;lsquo;감각이 좋은 것&amp;rsquo;을 의미하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;디자이너의 사고력은 보통 문제를 구조적으로 바라보는 능력&lt;/b&gt;&lt;/span&gt;에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금&lt;span&gt; &lt;/span&gt;더&lt;span&gt; &lt;/span&gt;풀어보면&lt;span&gt;, &lt;/span&gt;사고력은&lt;span&gt; &lt;/span&gt;다음&lt;span&gt; 세&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;단계로&lt;span&gt; &lt;/span&gt;나누어&lt;span&gt; &lt;/span&gt;볼&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt; &lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;i&gt;&lt;span&gt;&lt;b&gt;1) &lt;/b&gt;&lt;/span&gt;&lt;b&gt;현상을&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;문제로&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;바꾸는&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;능력&lt;/b&gt;&lt;/i&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-08 오후 7.21.40.png&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dl3bxK/dJMcacPOPeE/qkUB2zZxJjiBZb64yaCUD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dl3bxK/dJMcacPOPeE/qkUB2zZxJjiBZb64yaCUD1/img.png&quot; data-alt=&quot;이미지 출처: Writing - Material Design&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dl3bxK/dJMcacPOPeE/qkUB2zZxJjiBZb64yaCUD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdl3bxK%2FdJMcacPOPeE%2FqkUB2zZxJjiBZb64yaCUD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;365&quot; height=&quot;151&quot; data-filename=&quot;스크린샷 2026-03-08 오후 7.21.40.png&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처: Writing - Material Design&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;사고력의 시작은 &amp;ldquo;불편하다&amp;rdquo;는 말을 그대로 받아들이지 않는 데서 출발합니다.&lt;br /&gt;&lt;br /&gt;예를 들어 &amp;ldquo;결제 페이지가 불편해요&amp;rdquo;라는 피드백이 있다면,&lt;br /&gt;툴 중심 사고는 &amp;rarr; &amp;ldquo;그럼 UI를 조금 더 간략하게 바꿔야겠다.&amp;rdquo;라고 생각합니다.&lt;br /&gt;&lt;br /&gt;반면 사고 중심 접근은 &amp;rarr; &amp;ldquo;왜 불편할까?&amp;rdquo;라고 질문합니다.&lt;br /&gt;버튼이 잘 안 보이는 걸까? 정보가 너무 많아서 헷갈리는 걸까? 신뢰가 가지 않는 걸까? 화면이 느리게 느껴지는 걸까? &lt;br /&gt;&lt;br /&gt;같은 피드백이라도 원인을 어떻게 나누어 보느냐에 따라 방향은 달라집니다.&lt;br /&gt;디자이너의 사고력은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;문제를 디자인 이전 단계에서 정의하는 능력&lt;/b&gt;&lt;/span&gt;에 가깝습니다.✏️&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;i&gt;&lt;span&gt;&lt;b&gt;2) &lt;/b&gt;&lt;/span&gt;&lt;b&gt;선택에&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;이유를&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;붙이는&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;능력&lt;/b&gt;&lt;/i&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-08 오후 7.26.43.png&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YF2eb/dJMcaf6PFIm/GxqIM4KDpFEkLEnSTz9qtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YF2eb/dJMcaf6PFIm/GxqIM4KDpFEkLEnSTz9qtK/img.png&quot; data-alt=&quot;이미지 출처: Buttons - Material Design&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YF2eb/dJMcaf6PFIm/GxqIM4KDpFEkLEnSTz9qtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYF2eb%2FdJMcaf6PFIm%2FGxqIM4KDpFEkLEnSTz9qtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;377&quot; height=&quot;63&quot; data-filename=&quot;스크린샷 2026-03-08 오후 7.26.43.png&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처: Buttons - Material Design&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;디자인에는 정답이 없다고 말하지만, 이유 없는 선택은 설득력이 없습니다.&lt;br /&gt;사고력이 있는 디자이너는 이렇게 말합니다.&lt;br /&gt;&lt;br /&gt;&amp;ldquo;이 버튼을 크게 만든 이유는 모바일 화면에서 가장 중요한 행동이기 때문입니다.&amp;rdquo;&lt;br /&gt;&amp;ldquo;이 단계에서 리뷰를 보여준 이유는 결제 직전에 불안을 줄이기 위함입니다.&amp;rdquo;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;반대로 사고력이 부족하면 설명이 이렇게 바뀝니다.&lt;br /&gt;&lt;br /&gt;&amp;ldquo;그냥 이게 예뻐 보여서요.&amp;rdquo; &amp;ldquo;원래 이렇게들 해서요.&amp;rdquo;&lt;br /&gt;&lt;br /&gt;즉, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;사고력의 차이는 결과물이 아니라 설명 방식&lt;/b&gt;&lt;/span&gt;에서 드러납니다. &lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;i&gt;&lt;span&gt;&lt;b&gt;3) &lt;/b&gt;&lt;/span&gt;&lt;b&gt;결과를&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;해석하는&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;능력&lt;/b&gt;&lt;/i&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;모두의연구소.jpg&quot; data-origin-width=&quot;5000&quot; data-origin-height=&quot;3327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfDyiO/dJMcajnNtX2/1CuYGLAPlNcCaovVuCt4dk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfDyiO/dJMcajnNtX2/1CuYGLAPlNcCaovVuCt4dk/img.jpg&quot; data-alt=&quot;이미지 출처: 모두의 연구소&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfDyiO/dJMcajnNtX2/1CuYGLAPlNcCaovVuCt4dk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfDyiO%2FdJMcajnNtX2%2F1CuYGLAPlNcCaovVuCt4dk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;335&quot; height=&quot;223&quot; data-filename=&quot;모두의연구소.jpg&quot; data-origin-width=&quot;5000&quot; data-origin-height=&quot;3327&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처: 모두의 연구소&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;사고력은 디자인을 만드는 순간에 끝나지 않습니다. 출시 이후에도 계속 이어집니다.&lt;br /&gt;&lt;br /&gt;사용자가 어디에서 멈추는지 어느 지점에서 많이 이탈하는지 예상과 다른 행동을 보였는지 &lt;br /&gt;이런 부분들을 다시 살펴보아야 합니다.&lt;br /&gt;&lt;br /&gt;이때 &amp;ldquo;디자인이 실패했습니다&amp;rdquo;라고 말하기보다는,&lt;br /&gt;&amp;ldquo;세운 가설이 틀렸구나&amp;rdquo;라고 해석할 수 있는지가 중요합니다.&lt;br /&gt;&lt;br /&gt;사고력이란 결과를 감정이 아니라 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;데이터와 관찰을 통해 이해하는 힘&lt;/b&gt;&lt;/span&gt;이라고 볼 수 있습니다. &lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;결론. 사고력은 재능이 아니라 훈련이다!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많이 오해하는 부분이&lt;br /&gt;&amp;ldquo;사고력은 타고나는 것 아니야?&amp;rdquo;라는 질문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;u&gt;&lt;b&gt;사고력은 감각보다는 습관&lt;/b&gt;&lt;/u&gt;에 더 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 질문을 평소에 자주 던지는 디자이너는 자연스럽게 사고력이 쌓이게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 화면에서 사용자는 뭘 할까?&lt;/li&gt;
&lt;li&gt;왜 여기서 멈출까?&lt;/li&gt;
&lt;li&gt;이 문구는 오해를 만들지 않을까?&lt;/li&gt;
&lt;li&gt;내가 이 사용자라면 기분이 어떨까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은&lt;span&gt; &lt;/span&gt;특별한&lt;span&gt; &lt;/span&gt;재능이라기보다&lt;span&gt;, &lt;/span&gt;&lt;u&gt;&lt;b&gt;관점을&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;반복하는&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;연습&lt;/b&gt;&lt;/u&gt;에&lt;span&gt; &lt;/span&gt;가깝습니다&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;마무리하며 ...&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qF0yI/dJMcadgSUXi/i6It8kktu4XPCOWc3GkIL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qF0yI/dJMcadgSUXi/i6It8kktu4XPCOWc3GkIL0/img.png&quot; data-alt=&quot;이미지 출처: Pinterest&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qF0yI/dJMcadgSUXi/i6It8kktu4XPCOWc3GkIL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqF0yI%2FdJMcadgSUXi%2Fi6It8kktu4XPCOWc3GkIL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;287&quot; height=&quot;307&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처: Pinterest&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;툴 실력은 화면 위에서 바로 드러납니다.&lt;br /&gt;하지만 사고력은 화면 뒤에서 조용히 작동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AI &lt;/span&gt;시대에&lt;span&gt; &lt;/span&gt;디자이너로&lt;span&gt; &lt;/span&gt;살아남는&lt;span&gt; &lt;/span&gt;힘은&lt;span&gt;&lt;br /&gt;&lt;/span&gt;무엇을&lt;span&gt; &lt;/span&gt;만들지&lt;span&gt; &lt;/span&gt;판단하고&lt;span&gt;, &lt;/span&gt;&lt;b&gt;그&lt;span&gt; &lt;/span&gt;이유를&lt;span&gt; &lt;/span&gt;스스로&lt;span&gt; &lt;/span&gt;설명할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;사고력에&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt; &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>Design</category>
      <category>AI디자이너</category>
      <category>it동아리</category>
      <category>UIUX디자이너</category>
      <category>디자이너</category>
      <category>디자인</category>
      <category>디자인사고력</category>
      <category>주니어디자이너</category>
      <category>직장인동아리</category>
      <category>포트폴리오</category>
      <category>프로덕트디자이너</category>
      <author>dynamic-ddd</author>
      <guid isPermaLink="true">https://dynamic-ddd.tistory.com/8</guid>
      <comments>https://dynamic-ddd.tistory.com/8#entry8comment</comments>
      <pubDate>Sun, 8 Mar 2026 19:55:21 +0900</pubDate>
    </item>
    <item>
      <title>Tuist 모듈 자동화 CLI 자동화 만들기</title>
      <link>https://dynamic-ddd.tistory.com/7</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;titleImage.svg&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqiOsu/dJMcaiB5e2g/K35KuLTIxAnERdBdU116IK/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqiOsu/dJMcaiB5e2g/K35KuLTIxAnERdBdU116IK/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqiOsu/dJMcaiB5e2g/K35KuLTIxAnERdBdU116IK/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqiOsu%2FdJMcaiB5e2g%2FK35KuLTIxAnERdBdU116IK%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;488&quot; data-filename=&quot;titleImage.svg&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;tuisttool.swift 만든 썰 - Tuist 명령어 치다가 손가락 아파서 만듦&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  이 글을 쓰게 된 계기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! iOS 운영진 서원지입니다 ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist 쓰다 보면 이런 거 계속 치잖아요:&lt;/p&gt;
&lt;pre class=&quot;verilog&quot;&gt;&lt;code&gt;tuist generate
tuist fetch
tuist clean
tuist generate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하루에 몇 번을 치는지 모르겠어요... 손가락 아파 죽겠음  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 그냥 &lt;b&gt;CLI 도구를 만들어버렸습니다!&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  만들고 싶었던 기능들&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;tuist generate&lt;/code&gt;, &lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;clean&lt;/code&gt; 등 &lt;b&gt;한 방에 실행&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;새 모듈 만들 때 &lt;b&gt;의존성 선택지 자동으로 보여주기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Modules.swift&lt;/code&gt;, &lt;code&gt;SPM.swift&lt;/code&gt; 파싱해서 &lt;b&gt;뭐 있는지 알아서 찾기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;도메인 모듈이면 &lt;b&gt;Interface 폴더도 자동 생성&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거 다 구현했어요! ㅋㅋㅋ&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  명령어 정리&lt;/h2&gt;
&lt;pre class=&quot;verilog&quot;&gt;&lt;code&gt;./tuisttool.swift generate      # tuist generate
./tuisttool.swift fetch         # tuist fetch
./tuisttool.swift build         # clean + fetch + generate (한 방에!)
./tuisttool.swift clean         # tuist clean
./tuisttool.swift reset         # DerivedData + 캐시 싹 지우고 generate
./tuisttool.swift moduleinit    # 새 모듈 만들기 + 의존성 자동 삽입&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;build&lt;/code&gt; 이거 진짜 편해요. 세 개 따로 안 쳐도 됨!  &lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  핵심 구현 포인트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 쉘 명령 실행하는 함수&lt;/h3&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func run(_ command: String, arguments: [String] = []) -&amp;gt; Int32
func runCapture(_ command: String, arguments: [String] = []) throws -&amp;gt; String&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;run()&lt;/code&gt;: 그냥 실행만 하면 될 때&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runCapture()&lt;/code&gt;: 결과 값 받아야 할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거 두 개만 있으면 웬만한 건 다 됨!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 모듈 이름이랑 의존성 입력받기&lt;/h3&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;let moduleInput = prompt(&quot;모듈 이름 입력&quot;)
let moduleName = prompt(&quot;생성할 이름 입력&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성은 두 가지 방식으로 선택 가능하게 했어요:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SPM&lt;/b&gt;: &lt;code&gt;Extension+TargetDependencySPM.swift&lt;/code&gt; 파싱해서 목록 보여줌&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내부 모듈&lt;/b&gt;: &lt;code&gt;Modules.swift&lt;/code&gt;에서 enum case 파싱해서 보여줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 타이핑 안 해도 됨! 선택만 하면 됨!  &lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Tuist scaffold 실행&lt;/h3&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;let result = run(&quot;tuist&quot;, arguments: [
  &quot;scaffold&quot;, &quot;Module&quot;,
  &quot;--layer&quot;, layer,
  &quot;--name&quot;, moduleName,
  &quot;--author&quot;, author,
  &quot;--current-date&quot;, currentDate
])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 타입(Presentation, Shared, Core/Domain)에 따라 layer 자동 지정!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Project.swift에 의존성 자동 삽입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 제일 귀찮았던 부분인데요:&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;if var content = try? String(contentsOfFile: projectFile),
   let range = content.range(of: &quot;dependencies: [&quot;) {
   ...
   content.insert(contentsOf: &quot;  .SPM.alerter&quot;, at: insertIndex)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 만들고 나서 &lt;code&gt;Project.swift&lt;/code&gt; 열어서 의존성 추가하는 거...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;b&gt;자동으로 넣어줌!&lt;/b&gt; ㅋㅋㅋ&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 도메인 모듈이면 Interface 폴더도 자동 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 모듈은 Interface 분리하잖아요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것도 자동으로 만들어줍니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Projects/Core/Domain/MyModule/Interface/Sources/Base.swift&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿까지 같이 생성! 손 안 대도 됨!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  생성되는 구조&lt;/h2&gt;
&lt;pre class=&quot;monkey&quot;&gt;&lt;code&gt;Projects/
└── Core/
    └── Domain/
        └── MyFeature/
            ├── Interface/
            │   └── Sources/
            │       └── Base.swift
            └── Sources/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깔끔하죠? ㅋㅋㅋ&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  삽질 포인트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일 파싱할 때 주의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Modules.swift&lt;/code&gt; 파싱할 때 정규식 잘못 쓰면 이상한 거 잡혀요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum case만 정확히 잡으려고 꽤 고생했음 ㅠㅠ&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;경로 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상대 경로 / 절대 경로 헷갈리면 파일 못 찾음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;FileManager.default.currentDirectoryPath&lt;/code&gt; 잘 활용하세요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 자주 만드는 분들한테 강추예요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;반복 작업 도구화하면 생산성 확 올라감!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist가 구조화 잘 되어 있어서, 거기에 CLI 도구 얹으면 시너지 대박이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원들이랑 같이 쓰면 더 좋고요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 계속 기능 추가할 예정입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다음 글 예고&lt;/b&gt;: Tuist Plugin 만들기 다뤄볼 예정입니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작성자&lt;/b&gt;: iOS 운영진 서원지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GitHub&lt;/b&gt;: &lt;a href=&quot;https://github.com/Roy-wonji&quot;&gt;https://github.com/Roy-wonji&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>ios</category>
      <category>swift</category>
      <category>Tuist</category>
      <category>모듛화</category>
      <category>자동화</category>
      <author>dynamic-ddd</author>
      <guid isPermaLink="true">https://dynamic-ddd.tistory.com/7</guid>
      <comments>https://dynamic-ddd.tistory.com/7#entry7comment</comments>
      <pubDate>Wed, 25 Feb 2026 15:18:53 +0900</pubDate>
    </item>
    <item>
      <title>[Android] joda‑time, java.time, kotlinx‑datetime에 대하여..</title>
      <link>https://dynamic-ddd.tistory.com/6</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;196&quot; data-end=&quot;208&quot;&gt;  들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;209&quot; data-end=&quot;219&quot;&gt;안녕하세요! ✋&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;209&quot; data-end=&quot;219&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;209&quot; data-end=&quot;219&quot;&gt;사이드 프로젝트 동아리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;DDD&lt;/b&gt;에서 Android 개발자로 활동하고 있는 오세민입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;209&quot; data-end=&quot;219&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;302&quot; data-end=&quot;379&quot;&gt;이번 글에서는 개발을 하다 보면 한 번쯤은 꼭 마주치게 되는 주제,&lt;br /&gt;&lt;b&gt;시간과 날짜를 다루는 라이브러리&lt;/b&gt;에 대해 이야기해보려고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;381&quot; data-end=&quot;384&quot;&gt;특히,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;386&quot; data-end=&quot;434&quot;&gt;
&lt;li data-start=&quot;386&quot; data-end=&quot;399&quot;&gt;Joda-Time&lt;/li&gt;
&lt;li data-start=&quot;400&quot; data-end=&quot;413&quot;&gt;java.time&lt;/li&gt;
&lt;li data-start=&quot;414&quot; data-end=&quot;434&quot;&gt;kotlinx-datetime&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;436&quot; data-end=&quot;487&quot;&gt;이 세 가지를 중심으로&lt;br /&gt;&amp;ldquo;왜 이렇게 나뉘어 있을까?&amp;rdquo;라는 질문에서 시작해 보려고 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;494&quot; data-end=&quot;518&quot;&gt;⏰ 시작은 항상 이 의문에서 합니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;520&quot; data-end=&quot;600&quot;&gt;Joda-Time은 꽤 범용적인 시간 라이브러리예요.&lt;br /&gt;실제로 오래전부터 많은 프로젝트에서 사용됐고, 지금도 레거시 코드에서는 자주 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;602&quot; data-end=&quot;679&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;602&quot; data-end=&quot;679&quot;&gt;그런데 Java에는 또 java.time이 있고,&lt;br /&gt;Kotlin에는 kotlinx-datetime이라는 라이브러리가 따로 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;681&quot; data-end=&quot;705&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;681&quot; data-end=&quot;705&quot;&gt;그러다 보면 자연스럽게 이런 생각이 들어요.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot; data-start=&quot;707&quot; data-end=&quot;775&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;709&quot; data-end=&quot;775&quot;&gt;Joda-Time으로도 충분히 잘 되는데&lt;br /&gt;왜 Java와 Kotlin은 굳이 시간 라이브러리를 다시 만든 걸까요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;777&quot; data-end=&quot;848&quot;&gt;이 글은 이 질문에서 출발해요.&lt;br /&gt;&amp;ldquo;뭘 써야 할까?&amp;rdquo;보다는&lt;br /&gt;&lt;b&gt;&amp;ldquo;왜 이렇게 나뉘게 되었을까?&amp;rdquo;&lt;/b&gt;를 먼저 정리해보려 해요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;855&quot; data-end=&quot;889&quot;&gt; ️ Joda-Time은 왜 그렇게 많이 쓰였을까요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;891&quot; data-end=&quot;939&quot;&gt;Java 8 이전을 떠올려보면,&lt;br /&gt;시간을 다루는 기본 API가 썩 친절하지 않았어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;941&quot; data-end=&quot;1024&quot;&gt;
&lt;li data-start=&quot;941&quot; data-end=&quot;986&quot;&gt;java.util.Date는 이름과 다르게 날짜/시간 개념이 섞여 있고&lt;/li&gt;
&lt;li data-start=&quot;987&quot; data-end=&quot;1024&quot;&gt;Calendar는 mutable하고 사용하기도 불편했어요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1026&quot; data-end=&quot;1057&quot;&gt;이런 상황에서 등장한 게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Joda-Time&lt;/b&gt;이에요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;1059&quot; data-end=&quot;1110&quot;&gt;
&lt;li data-start=&quot;1059&quot; data-end=&quot;1068&quot;&gt;불변 객체&lt;/li&gt;
&lt;li data-start=&quot;1069&quot; data-end=&quot;1094&quot;&gt;날짜, 시간, 기간 개념의 명확한 분리&lt;/li&gt;
&lt;li data-start=&quot;1095&quot; data-end=&quot;1110&quot;&gt;타임존을 고려한 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1112&quot; data-end=&quot;1173&quot;&gt;그래서 한동안은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;ldquo;Java에서 시간 다루려면 Joda-Time&amp;rdquo;&lt;/b&gt;이라는 말이 거의 정설처럼 쓰였어요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;1560&quot; data-end=&quot;1590&quot;&gt;  그래서 Joda-Time의 현재 위치는요&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-message-author-role=&quot;assistant&quot; data-message-id=&quot;17a02628-1fc9-472b-b474-b2e3df576bb4&quot; data-message-model-slug=&quot;gpt-5-2-instant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;483&quot; data-end=&quot;570&quot;&gt;여기서 흥미로운 사실 하나가 있어요.&lt;br /&gt;&lt;b&gt;Joda-Time의 핵심 개발자와 java.time(JSR-310)의 설계자가 동일 인물&lt;/b&gt;이라는 점이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;483&quot; data-end=&quot;570&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;572&quot; data-end=&quot;666&quot;&gt;Joda-Time을 만들었던 Stephen Colebourne이&lt;br /&gt;Java 8의 시간 API 설계에도 직접 참여했고,&lt;br /&gt;그 결과물이 바로 java.time이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;572&quot; data-end=&quot;666&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;572&quot; data-end=&quot;666&quot;&gt;그래서 구조를 보면 익숙한 이유도 여기에 있어요.&lt;br /&gt;Joda-Time에서 검증된 개념과 설계를 그대로 가져와&lt;br /&gt;&lt;b&gt;Java 공식 표준으로 정리한 게 java.time&lt;/b&gt;이기 때문이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;572&quot; data-end=&quot;666&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;775&quot; data-end=&quot;807&quot;&gt;이 개발자는 공식 문서와 여러 글에서 분명하게 이야기해요.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot; data-start=&quot;809&quot; data-end=&quot;864&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;811&quot; data-end=&quot;864&quot;&gt;Joda-Time은 유지보수 모드이며, 새 코드에서는 java.time을 사용하라&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;866&quot; data-end=&quot;905&quot;&gt;그래서 지금 시점에서 Joda-Time은 이렇게 보는 게 자연스러워요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;907&quot; data-end=&quot;960&quot;&gt;
&lt;li data-start=&quot;907&quot; data-end=&quot;929&quot;&gt;기존 레거시 유지보수 &amp;rarr; 괜찮아요&lt;/li&gt;
&lt;li data-start=&quot;930&quot; data-end=&quot;960&quot;&gt;새 프로젝트 설계 &amp;rarr; 굳이 선택할 이유는 없어요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;962&quot; data-end=&quot;1029&quot;&gt;문제가 있어서 밀려난 게 아니라,&lt;br /&gt;&lt;b&gt;자신의 설계가 표준으로 흡수되면서 역할을 마무리한 라이브러리&lt;/b&gt;에 더 가까워요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;962&quot; data-end=&quot;1029&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1031&quot; data-end=&quot;1096&quot; data-is-last-node=&quot;&quot; data-is-only-node=&quot;&quot;&gt;Joda-Time이 실패해서 사라진 게 아니라,&lt;br /&gt;성공했기 때문에 java.time으로 이어졌다고 보는 게 맞아요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;1811&quot; data-end=&quot;1845&quot;&gt;  Kotlin에서는 왜 또 다른 선택지가 있을까요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1847&quot; data-end=&quot;1876&quot;&gt;여기서 kotlinx-datetime이 등장해요.&lt;br /&gt;이 라이브러리는 Java의 시간 API를 대체하려는 목적이 아니에요.&lt;br /&gt;핵심은 딱 하나예요.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot; data-start=&quot;1933&quot; data-end=&quot;1982&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1935&quot; data-end=&quot;1982&quot;&gt;Kotlin Multiplatform에서 공통으로 사용할 수 있는 시간 API&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1984&quot; data-end=&quot;2001&quot;&gt;그래서 몇 가지 차이가 있어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;2003&quot; data-end=&quot;2106&quot;&gt;
&lt;li data-start=&quot;2003&quot; data-end=&quot;2036&quot;&gt;JVM, Android, iOS, JS에서 공통 사용&lt;/li&gt;
&lt;li data-start=&quot;2037&quot; data-end=&quot;2073&quot;&gt;Instant, LocalDate 같은 개념은 유지&lt;/li&gt;
&lt;li data-start=&quot;2074&quot; data-end=&quot;2106&quot;&gt;타임존은 TimeZone을 명시적으로 함께 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1771056313035&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val instant = Clock.System.now() 
val local = instant.toLocalDateTime(TimeZone.of(&quot;Asia/Seoul&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;2219&quot; data-end=&quot;2278&quot;&gt;Java의 ZonedDateTime을 그대로 쓰지 않은 이유도 멀티플랫폼 환경을 고려한 선택이에요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;2285&quot; data-end=&quot;2315&quot;&gt; ️ 개념으로 보면 라이브러리는 덜 헷갈려요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;2317&quot; data-end=&quot;2360&quot;&gt;시간 라이브러리가 어려운 이유는 문법보다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;개념이 섞이기 때문&lt;/b&gt;이에요.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-start=&quot;2362&quot; data-end=&quot;2498&quot;&gt;
&lt;tbody data-start=&quot;2384&quot; data-end=&quot;2498&quot;&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;개념&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;2384&quot; data-end=&quot;2405&quot;&gt;
&lt;td data-start=&quot;2384&quot; data-end=&quot;2394&quot; data-col-size=&quot;sm&quot;&gt;Instant&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;2394&quot; data-end=&quot;2405&quot;&gt;절대적인 시점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;2406&quot; data-end=&quot;2424&quot;&gt;
&lt;td data-start=&quot;2406&quot; data-end=&quot;2418&quot; data-col-size=&quot;sm&quot;&gt;LocalDate&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;2418&quot; data-end=&quot;2424&quot;&gt;날짜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;2425&quot; data-end=&quot;2443&quot;&gt;
&lt;td data-start=&quot;2425&quot; data-end=&quot;2437&quot; data-col-size=&quot;sm&quot;&gt;LocalTime&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;2437&quot; data-end=&quot;2443&quot;&gt;시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;2444&quot; data-end=&quot;2477&quot;&gt;
&lt;td data-start=&quot;2444&quot; data-end=&quot;2460&quot; data-col-size=&quot;sm&quot;&gt;ZonedDateTime&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;2460&quot; data-end=&quot;2477&quot;&gt;날짜 + 시간 + 타임존&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;2478&quot; data-end=&quot;2498&quot;&gt;
&lt;td data-start=&quot;2478&quot; data-end=&quot;2489&quot; data-col-size=&quot;sm&quot;&gt;Duration&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;2489&quot; data-end=&quot;2498&quot;&gt;시간의 양&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;2500&quot; data-end=&quot;2558&quot;&gt;이 개념은&lt;br /&gt;Joda-Time, java.time, kotlinx-datetime 모두 거의 동일해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;2500&quot; data-end=&quot;2558&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;2560&quot; data-end=&quot;2583&quot;&gt;차이는 &amp;ldquo;어디에서 쓰느냐&amp;rdquo;에 더 가까워요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;2590&quot; data-end=&quot;2617&quot;&gt;  Android에서는 이렇게 선택해요&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;2619&quot; data-end=&quot;2637&quot;&gt;minSdk 26 이상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;2639&quot; data-end=&quot;2681&quot;&gt;java.time을 그대로 사용하면 돼요.&lt;br /&gt;추가 고민은 거의 없어요.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;2688&quot; data-end=&quot;2706&quot;&gt;minSdk 26 미만&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;2708&quot; data-end=&quot;2724&quot;&gt;이 경우에도 방법은 명확해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;2708&quot; data-end=&quot;2724&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;2726&quot; data-end=&quot;2801&quot;&gt;&lt;b&gt;Core Library Desugaring&lt;/b&gt;을 활성화해서&lt;br /&gt;java.time을 백포트해서 사용하는 방식이 가장 안정적이에요.&lt;/p&gt;
&lt;pre id=&quot;code_1771056313036&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;android {
  compileOptions {
    coreLibraryDesugaringEnabled true
  }
}

dependencies {
  coreLibraryDesugaring(
    &quot;com.android.tools:desugar_jdk_libs:...&quot;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;2983&quot; data-end=&quot;3004&quot;&gt;실무에서 가장 많이 쓰이는 방식이에요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;3011&quot; data-end=&quot;3051&quot;&gt;  Kotlin Multiplatform이라면 선택이 달라져요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3053&quot; data-end=&quot;3093&quot;&gt;공용 모듈에서는 kotlinx-datetime이 현실적인 선택이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3053&quot; data-end=&quot;3093&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3095&quot; data-end=&quot;3146&quot;&gt;그리고 시간 계산은&lt;br /&gt;kotlin.time.Duration이 정말 잘 만들어져 있어요.&lt;/p&gt;
&lt;pre id=&quot;code_1771056313036&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;5.hours + 30.minutes&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3184&quot; data-end=&quot;3218&quot;&gt;읽는 순간 바로 이해된다는 점에서&lt;br /&gt;이건 꽤 큰 장점이에요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;3225&quot; data-end=&quot;3253&quot;&gt;  시간 라이브러리에서 가장 중요한 원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3255&quot; data-end=&quot;3282&quot;&gt;라이브러리 선택보다 더 중요한 건 이 원칙이에요.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot; data-start=&quot;3284&quot; data-end=&quot;3345&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;3286&quot; data-end=&quot;3345&quot;&gt;저장과 전송은 항상 Instant로 해요&lt;br /&gt;사람에게 보여줄 때만 날짜와 타임존을 입혀요&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3347&quot; data-end=&quot;3356&quot;&gt;이 기준을 지키면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;3358&quot; data-end=&quot;3409&quot;&gt;
&lt;li data-start=&quot;3358&quot; data-end=&quot;3379&quot;&gt;서버와 클라이언트 간 시간 차이&lt;/li&gt;
&lt;li data-start=&quot;3380&quot; data-end=&quot;3390&quot;&gt;타임존 문제&lt;/li&gt;
&lt;li data-start=&quot;3391&quot; data-end=&quot;3409&quot;&gt;DST(일광절약시간) 이슈&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3411&quot; data-end=&quot;3431&quot;&gt;대부분을 자연스럽게 피할 수 있어요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;3438&quot; data-end=&quot;3456&quot;&gt;✅ 정리해보면 이렇습니다&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;3458&quot; data-end=&quot;3573&quot;&gt;
&lt;li data-start=&quot;3458&quot; data-end=&quot;3493&quot;&gt;Joda-Time은 한 시대를 잘 마무리한 라이브러리예요&lt;/li&gt;
&lt;li data-start=&quot;3494&quot; data-end=&quot;3530&quot;&gt;java.time은 그 설계를 공식 표준으로 만든 결과예요&lt;/li&gt;
&lt;li data-start=&quot;3531&quot; data-end=&quot;3573&quot;&gt;kotlinx-datetime은 멀티플랫폼을 위한 현실적인 선택이에요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3575&quot; data-end=&quot;3627&quot;&gt;무엇을 쓰느냐보다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;왜 이런 타입과 라이브러리가 존재하는지 이해하는 게 더 중요해요.&lt;br /&gt;&lt;/b&gt;시간은 조용히 쌓이고, 문제가 생기면 고치기 어렵거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3575&quot; data-end=&quot;3627&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3575&quot; data-end=&quot;3627&quot;&gt;긴 글 읽어주셔서 감사합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;1145&quot; data-end=&quot;1165&quot;&gt;  레퍼런스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1167&quot; data-end=&quot;1259&quot;&gt;이 글을 정리하면서 실제로 참고한 자료들이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1167&quot; data-end=&quot;1259&quot;&gt;&lt;a href=&quot;https://www.joda.org/joda-time/&quot;&gt;https://www.joda.org/joda-time/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.time/&quot;&gt;https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.time/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1167&quot; data-end=&quot;1259&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3575&quot; data-end=&quot;3627&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>dynamic-ddd</author>
      <guid isPermaLink="true">https://dynamic-ddd.tistory.com/6</guid>
      <comments>https://dynamic-ddd.tistory.com/6#entry6comment</comments>
      <pubDate>Sat, 14 Feb 2026 17:06:37 +0900</pubDate>
    </item>
    <item>
      <title>[Design] 픽셀을 넘어 공간으로: AI 시대, 디자이너가 XR에 주목해야 하는 이유]</title>
      <link>https://dynamic-ddd.tistory.com/5</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요!✋&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트 동아리 &lt;b&gt;DDD&lt;/b&gt;에서 디자인 운영진을 맡고 있는 이소현입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD는 개발자, 디자이너, PM이 함께하는 사이드 프로젝트 동아리입니다. DDD에서 세 번째 글로 인사드리게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 요즘 디자이너들 사이에서 가장 뜨거운 화두인 '&lt;b&gt;AI&lt;/b&gt;', 그리고 그 너머의 '&lt;b&gt;Next Generation&lt;/b&gt;'에 대해 이야기해 보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  AI의 역설: 정복되지 않을 분야는 없다?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 &quot;AI가 발전하면서 가장 빠르게 대체될 직업 중 하나가 디자이너&quot;라는 이야기를 들어보셨나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 등장하기 전, 사람들은 미술이나 음악 같은 예체능 분야만큼은 기계가 절대 범접할 수 없는 영역이라고 믿었습니다. 하지만 인간은 금기시된 영역이라고 생각할수록 더 정복하고 싶어 하나 봐요. 어느덧 &lt;b&gt;Nano Banana, Midjourney&lt;/b&gt; 같은 생성형 AI들이 누구보다 빠르게 우리 곁에 찾아왔으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;기술의 발전이 조금은 무섭게 느껴질 수도 있지만, 저는 디자이너가 단순히&lt;i&gt; '도구를 다루는 사람'에 머물러서는 안 된다'라고&lt;/i&gt; 생각합니다. 미래의 디자이너는 &lt;b&gt;AI의 결과물을 비판적으로 해석하고, 그 안에 새로운 의미를 부여하는 사람&lt;/b&gt;으로 진화해야 합니다. 불편하더라도 현실을 직시하고 기술을 도구로서 영리하게 활용해야 하죠.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 스크린의 해체, 'XR'이라는 새로운 무대&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;우리는 다음 세대(Next Generation)를 준비해야 합니다. 그래서 저는 &lt;b&gt;XR&lt;/b&gt;(eXtended Reality, 확장 현실)에 주목했습니다. XR은 AR(증강 현실), VR(가상현실), MR(혼합 현실)을 아우르는 몰입형 기술입니다. 쉽게 말해 '스마트 글라스'의 세상을 떠올려 보세요. 이미 메타(Meta)와 애플(Apple)이 시장의 문을 열었고, 구글과 삼성은 젠틀몬스터와 함께 새로운 미래를 준비하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직은 먼 얘기라고 생각하실 수도 있지만, 우리 일상에 곧 자연스럽게 녹아든 새로운 패러다임을 만날 수 있지 않을까 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스마트 글래스가 일상이 되면 어떤 변화가 일어날까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스크린의 확장&lt;/b&gt;: 손바닥 안의 디스플레이를 넘어 내가 머무는 모든 공간이 스크린이 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인터랙션의 변화&lt;/b&gt;: '클릭' 중심에서 사용자의 '시선'과 '손동작'이 핵심 입력 수단이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 변화를 멀티모달 인터랙션(Multimodal Interaction)이라 부르며, 멀티모달은 텍스트, 이미지, 음성, 영상 등 다양한 형태의 데이터를 동시에 인식하고 처리하며 이해하는 인공지능 기술입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;549&quot;&gt;&lt;a href=&quot;https://youtu.be/YPxDjVKxfBA?si=3nbTVfLLh9mme-Od&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSpnAS/dJMcaaKTXGX/8UL5NcUk6sI1xcPPw2XYK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSpnAS%2FdJMcaaKTXGX%2F8UL5NcUk6sI1xcPPw2XYK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;998&quot; height=&quot;549&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/YPxDjVKxfBA?si=CWhSCPXOjcKPYJko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/YPxDjVKxfBA?si=CWhSCPXOjcKPYJko&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜버 잇섭님의 영상 하나를 첨부드리겠습니다. 영상을 보시면 과거 영화에 미래를 표현해 내기 위해 나왔던 공간에 떠다니는 스크린이 이제는 실제 스마트 글래스를 통해 보이는 디스플레이가 그대로 구현되어 있는 것을 보실 수 있습니다. 화면 안의 구성을 넘어 공간 전체를 인터페이스로 활용하는 공간 디자인(Spatial Design) 또한 디자이너의 새로운 핵심 역량이 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;  디자이너의 문법이 완전히 바뀌는 지점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 영상에서 보듯, 이제 디자인은 더 이상 평면에 갇혀 있지 않습니다. '디자이너가 가져야 할 새로운 능력'은 크게 세 가지입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;실재감을 만드는 감각 설계:&lt;/b&gt; 마우스 커서가 없는 세상에서는 사용자의 손짓이 버튼에 닿았는지 인지시키는 것이 중요합니다. 미세한 효과음(Sound)이나 시각적인 떨림(Visual Feedback)을 통해 물리적 촉감이 없는 환경에서도 '실재감'을 만들어내야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;맥락을 인지하는 영리함:&lt;/b&gt; 현실과 가상이 공존하기에 사용자의 안전을 우선해야 합니다. 길을 걷는 사용자에게 시야를 가리는 팝업을 띄우는 것은 위험합니다. 주변의 빛, 사용자의 위치, 현재 행동에 따라 UI의 투명도와 사이즈를 자동으로 조절하는 '맥락 인식 디자인'이 필수적입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;픽셀에서 볼륨으로:&lt;/b&gt; 이전까지 정해진 규격 안에서 시각적 위계를 잡았다면, 이제는 사용자의 공간과 디지털 오브젝트가 놓이는 '깊이감'과 공간과의 조화를 설계해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;♀️ 개발자&amp;middot;PM&amp;middot;디자이너, 우리는 어떻게 상생할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD는 협업 동아리인 만큼, 이 세 직군이 미래에 어떤 역할을 해야 할지 &lt;b&gt;Gemini&lt;/b&gt;에게 직접 물어보았습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Future Role in XR Era&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;디자이너:&lt;/b&gt; &quot;이제 픽셀(Pixel)이 아니라 볼륨(Volume)을 설계해야 합니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PM:&lt;/b&gt; &quot;사용자가 가상 화면에 가로막혀 벽에 부딪히지 않도록 안전한 사용자 여정을 설계해야 합니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발자:&lt;/b&gt; &quot;3D 객체를 실제 공간 좌표에 고정(Anchoring)시키는 기술이 필수적입니다.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp; 마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 AI가 많은 도구를 대신하고, 세상이 급변해도 사용자가 머무는&lt;b&gt;&amp;nbsp;그 안의 경험을 조율하는 것&lt;/b&gt;은 여전히 우리 디자이너의 몫입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next Generation을 준비하는 동료분들, DDD에서 함께 그 미래를 그려보면 어떨까요? 제가 최근 깊게 빠져있는 이 분야의 이야기가 여러분께 조금이라도 흥미로운 영감이 되었기를 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  DDD 13기 크루 모집 중!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD는 사이드 프로젝트 동아리로, 책임감 있는 협업을 기반으로 상호간의 인사이트를 공유할 수 있도록 다양한 기회를 제공합니다. 사이드 프로젝트에 관심있는, 열정과 재능이 넘치는 13기 멤버들을 모집하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  모집 링크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://linktr.ee/dddstudy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;linktr.ee/dddstudy&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗모집 대상&lt;br /&gt;- 프로젝트를 통해 함께 성장하고 싶은 기획자와 디자이너, 개발자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 격주 토요일 오후 2시 - 6시에 성실하게 참여 가능하신 분.&lt;br /&gt;&lt;br /&gt;  모집 일정&lt;br /&gt;- 2026년 1월 25일(일) ~ 2월 7일(토)&lt;br /&gt;&lt;br /&gt;  서류 합격자 발표&lt;br /&gt;- 2월 14일(토) 안내 예정&lt;br /&gt;&lt;br /&gt;  인터뷰 일정&lt;br /&gt;- 2월 21일(토)~22일(일)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 온라인으로 최대 1시간 동안 진행&lt;br /&gt;&lt;br /&gt;  최종 합격자 발표&lt;br /&gt;- 2월 23일(월) 문자로 안내&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 3월 7일(토) 오프라인 OT 진행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[활동 기간]&lt;br /&gt;- 2026.03.07 ~ 2026.06.27&lt;br /&gt;- 서울지역 격주 토요일 오후 2시 - 6시 오프라인 활동&lt;br /&gt;&lt;br /&gt;[참여 비용]&lt;br /&gt;- 운영비용 8만원 + 노쇼비용 8만원&lt;br /&gt;- 4개월 동안 모든 비용은 간식비, 장소비, 경품비로 사용되며 노쇼비는 활동이 끝나고 돌려드립니다.&lt;br /&gt;&lt;br /&gt;[문의방법]&lt;br /&gt;- 인스타그램 : DM으로 문의&lt;br /&gt;- 이메일 문의: dddstudy1@gmail.com&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;엔딩.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oeR5c/dJMcabiLe7m/TlIr8XT5uutc62ReVxKYYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oeR5c/dJMcabiLe7m/TlIr8XT5uutc62ReVxKYYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oeR5c/dJMcabiLe7m/TlIr8XT5uutc62ReVxKYYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoeR5c%2FdJMcabiLe7m%2FTlIr8XT5uutc62ReVxKYYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1350&quot; data-filename=&quot;엔딩.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✍️ 작성자: 이소현&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;직군:&lt;/b&gt;&lt;span&gt; &lt;/span&gt;Designer&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Design</category>
      <category>Design</category>
      <category>UXUI</category>
      <category>디자이너</category>
      <category>디자인</category>
      <category>브랜딩</category>
      <category>사이드프로젝트</category>
      <category>이직</category>
      <category>직장인동아리</category>
      <category>커리어</category>
      <category>포트폴리오</category>
      <author>dynamic-ddd</author>
      <guid isPermaLink="true">https://dynamic-ddd.tistory.com/5</guid>
      <comments>https://dynamic-ddd.tistory.com/5#entry5comment</comments>
      <pubDate>Sat, 31 Jan 2026 00:27:48 +0900</pubDate>
    </item>
    <item>
      <title>Tuist 모듈화에 빠져보기</title>
      <link>https://dynamic-ddd.tistory.com/4</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;titleImage.svg&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqiOsu/dJMcaiB5e2g/K35KuLTIxAnERdBdU116IK/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqiOsu/dJMcaiB5e2g/K35KuLTIxAnERdBdU116IK/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqiOsu/dJMcaiB5e2g/K35KuLTIxAnERdBdU116IK/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqiOsu%2FdJMcaiB5e2g%2FK35KuLTIxAnERdBdU116IK%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;488&quot; data-filename=&quot;titleImage.svg&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;  Tuist 많이 사용한 개발자 알려주는 팁&lt;/h2&gt;
&lt;p&gt;안녕하세요! iOS 운영진 서원지입니다 ㅋㅋㅋ&lt;/p&gt;
&lt;p&gt;이 글을 쓰게 된 계기는... 요즘 주니어 개발자분들이 Tuist를 하도 많이 써서요..&lt;/p&gt;
&lt;p&gt;저도 그렇고. 거의 모든 프로젝트에서 Tuist를 써봤다 보니 경험 공유해 드리면 좋을 것 같더라고요!&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;제가 경험한 내용 위주로 작성했습니다. 더 좋은 방법 있으면 편하게 알려주세요  &lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;Tuist가 뭐길래 이렇게 난리야?&lt;/h2&gt;
&lt;p&gt;솔직히 처음 Tuist 접했을 때는 &amp;quot;이게 뭐가 좋은데?&amp;quot; 싶었어요 ㅋㅋ&lt;/p&gt;
&lt;p&gt;README 보면 이렇게 쓰여있거든요:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&amp;quot;Xcode Project를 생성하거나 유지보수하거나 상호작용하는 Command Line Tool&amp;quot;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;아 네... 뭔 소리인지 1도 모르겠더라고요  &lt;/p&gt;
&lt;h3&gt;대충 분석해 보니까&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Xcode 프로젝트 파일을 코드로 관리&lt;/strong&gt;하는 거더라고요&lt;/li&gt;
&lt;li&gt;GUI에서 클릭클릭 하던 걸 Swift 코드로 작성&lt;/li&gt;
&lt;li&gt;팀 작업할 때 프로젝트 설정 충돌 안남&lt;/li&gt;
&lt;li&gt;모듈화 할 때 진짜 편함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;quot;아~ 이거구나!&amp;quot; 싶어서 바로 도입해 봤는데... 진짜 편하더라고요!&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;설치부터 해보자 (삽질 경험담 포함)&lt;/h2&gt;
&lt;h3&gt;mise로 설치하기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 요즘 mise 쓰는 게 트렌드래요
mise install tuist&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;# 이것만 하면 안되고... (저도 여기서 삽질함 ㅠ)
mise use -g tuist&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;# 이제야 됩니다!
tuist --version&lt;/code&gt;&lt;/pre&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;  삽질 포인트: mise install 만 하고 mise use -g 안 하면 command not found 나와요. 저도 여기서 30분 헤맸음  &lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;첫 프로젝트 만들기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 프로젝트 초기화 (여기서 Project.swift 파일 생성됨)
tuist init

# 실제 Xcode 프로젝트 생성 (여기가 핵심!)
tuist generate

# 빌드도 터미널에서 가능
tuist build

# 설정 파일들만 따로 편집
tuist edit&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;매번 &lt;code&gt;tuist generate&lt;/code&gt; 하는 게 좀 귀찮긴 한데... 익숙해지면 괜찮아요!&lt;br&gt;(나중에는 자동화 만들었어욬ㅋㅋㅋ)&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;의존성 관리가 진짜 핵심이에요&lt;/h2&gt;
&lt;p&gt;여기가 진짜 중요한 부분이에요. 저도 처음엔 이해 안 됐는데 몇 번 써보니까 이해되더라고요.&lt;/p&gt;
&lt;h3&gt;Package.swift에서 모든 걸 관리&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Plugin/DependencyPackagePlugin/Package.swift&lt;/code&gt; 파일에서 모든 외부 라이브러리를 선언합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@preconcurrency import PackageDescription

#if TUIST
@preconcurrency import ProjectDescription

// 이 부분이 진짜 중요! Firebase 때문에 추가함
let packageSettings = PackageSettings(
  productTypes: [
    &amp;quot;FirebaseCore&amp;quot;: .staticLibrary,
    &amp;quot;FirebaseAuth&amp;quot;: .staticLibrary,
    &amp;quot;FirebaseFirestore&amp;quot;: .staticLibrary,
    &amp;quot;FirebaseAnalytics&amp;quot;: .staticLibrary,
    &amp;quot;FirebaseCrashlytics&amp;quot;: .staticLibrary,
    &amp;quot;FirebaseRemoteConfig&amp;quot;: .staticLibrary
  ]
)
#endif

let package = Package(
  name: &amp;quot;MyAwesomeProject&amp;quot;, // 여기는 본인 프로젝트명으로!
  dependencies: [
    // Firebase (필수템)
    .package(url: &amp;quot;&amp;lt;https://github.com/firebase/firebase-ios-sdk&amp;gt;&amp;quot;, from: &amp;quot;10.27.0&amp;quot;),

    // TCA (요즘 핫한 아키텍처)
    .package(url: &amp;quot;&amp;lt;https://github.com/pointfreeco/swift-composable-architecture&amp;gt;&amp;quot;, exact: &amp;quot;1.18.0&amp;quot;),

    // Swift Concurrency 관련 유틸들
    .package(url: &amp;quot;&amp;lt;https://github.com/pointfreeco/swift-concurrency-extras.git&amp;gt;&amp;quot;, from: &amp;quot;1.1.0&amp;quot;),

    // TCA 화면전환 관리
    .package(url: &amp;quot;&amp;lt;https://github.com/johnpatrickmorgan/TCACoordinators.git&amp;gt;&amp;quot;, exact: &amp;quot;0.11.1&amp;quot;),

    // 네트워킹 (직접 만든 거 써봤어요)
    .package(url: &amp;quot;&amp;lt;https://github.com/Roy-wonji/AsyncMoya&amp;gt;&amp;quot;, from: &amp;quot;1.1.0&amp;quot;),
    .package(url: &amp;quot;&amp;lt;https://github.com/Roy-wonji/WeaveDi.git&amp;gt;&amp;quot;, from: &amp;quot;3.4.0&amp;quot;)
  ]
)&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Firebase 삽질담&lt;/h2&gt;
&lt;p&gt;Firebase 쓰시는 분들 주목! 저도 여기서 진짜 많이 삽질했어요...&lt;/p&gt;
&lt;h3&gt;왜 static library로?&lt;/h3&gt;
&lt;p&gt;Firebase 그냥 쓰면 링크 에러 나거든요. 특히 모듈화 할 때 진짜 골치아픔.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let packageSettings = PackageSettings(
  productTypes: [
    &amp;quot;FirebaseCore&amp;quot;: .staticLibrary,        // 이렇게 static으로!
    &amp;quot;FirebaseAuth&amp;quot;: .staticLibrary,        // 안그러면 링크 에러남
    &amp;quot;FirebaseFirestore&amp;quot;: .staticLibrary,   // 진짜임...
    // ... 쓰는 Firebase 모듈 다 static으로
  ]
)&lt;/code&gt;&lt;/pre&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;  꿀팁: #if TUIST 써서 Tuist에서만 이 설정이 적용되게 했어요. 일반 SPM에서는 무시됨!&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;enum으로 깔끔하게 정리하는 법&lt;/h2&gt;
&lt;p&gt;매번 &lt;code&gt;TargetDependency.external(name: &amp;quot;긴 이름의 라이브러리&amp;quot;)&lt;/code&gt; 이렇게 쓰기 싫잖아요? ㅋㅋㅋ&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Plugin/DependencyPackagePlugin/ProjectDescriptionHelpers/Extension+TargetDependencySPM.swift&lt;/code&gt; 파일 만들어서:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import ProjectDescription

public extension TargetDependency {
  enum SPM {} // SPM용 네임스페이스
}

public extension TargetDependency.SPM {
  // 이렇게 하면 오타도 안나고 자동완성도 됨!
  static let asyncMoya = TargetDependency.external(name: &amp;quot;AsyncMoya&amp;quot;, condition: .none)
  static let weaveDi = TargetDependency.external(name: &amp;quot;WeaveDi&amp;quot;, condition: .none)
  static let composableArchitecture = TargetDependency.external(name: &amp;quot;ComposableArchitecture&amp;quot;, condition: .none)
  static let tcaCoordinator = TargetDependency.external(name: &amp;quot;TCACoordinators&amp;quot;, condition: .none)
  static let concurrencyExtras = TargetDependency.external(name: &amp;quot;ConcurrencyExtras&amp;quot;, condition: .none)
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이제 각 모듈에서 이렇게 쓸 수 있어요:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dependencies: [
  .SPM.composableArchitecture,  // 깔끔!
  .SPM.weaveDi,                // 오타 걱정 없음!
  .SPM.asyncMoya              // 자동완성 됨!
]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;진짜 편해요!  &lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;제가 주로 쓰는 라이브러리들&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;라이브러리&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;th&gt;개인적 평가&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;ComposableArchitecture&lt;/td&gt;
&lt;td&gt;TCA 아키텍처&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐ (강추!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ConcurrencyExtras&lt;/td&gt;
&lt;td&gt;Swift Concurrency 확장&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐ (LockIsolated 짱)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collections&lt;/td&gt;
&lt;td&gt;고성능 컬렉션&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐ (Deque 좋음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCACoordinators&lt;/td&gt;
&lt;td&gt;화면전환 관리&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐ (TCA 쓰면 필수)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AsyncMoya&lt;/td&gt;
&lt;td&gt;네트워킹&lt;/td&gt;
&lt;td&gt;⭐⭐⭐ (직접 만든 거라..)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WeaveDi&lt;/td&gt;
&lt;td&gt;DI&lt;/td&gt;
&lt;td&gt;⭐⭐⭐ (심플함)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;진짜 솔직한 후기&lt;/h2&gt;
&lt;h3&gt;좋은 점들:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;한 곳에서 모든 의존성 관리&lt;/strong&gt;: 진짜 깔끔함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;프로젝트 설정 버전관리&lt;/strong&gt;: Git으로 관리되니까 좋음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;팀 작업 시 충돌 없음&lt;/strong&gt;: Xcode 파일 merge 지옥에서 해방!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;빌드 최적화&lt;/strong&gt;: 모듈별로 빌드해서 빠름&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;아쉬운 점들:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;초기 세팅 복잡&lt;/strong&gt;: 처음 할 때 좀 헤맸어요&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;러닝커브&lt;/strong&gt;: Swift 코드로 프로젝트 설정하는 게 처음엔 어색함&lt;/li&gt;
&lt;li&gt;업데이트: 너무 자주 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;꿀팁들&lt;/h2&gt;
&lt;h3&gt;1. 점진적으로 도입하세요&lt;/h3&gt;
&lt;p&gt;기존 프로젝트에 갑자기 도입하면 멘탈 터져요. 새 모듈부터 시작하시길!&lt;/p&gt;
&lt;h3&gt;2. 템플릿 만들어두세요&lt;/h3&gt;
&lt;p&gt;비슷한 모듈 구조 계속 만들 거면 템플릿 만들어두면 편해요.&lt;/p&gt;
&lt;h3&gt;3. 캐시 활용하세요&lt;/h3&gt;
&lt;p&gt;빌드 빨라지려면 캐시 필수예요. Tuist Cloud도 있던데... 아직 안 써봄 ㅋㅋ&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Tuist 진짜 좋은데 처음엔 좀 어려워요. 하지만 한번 익숙해지면 다른 걸로 못 갑니다!&lt;/p&gt;
&lt;p&gt;특히 팀 프로젝트할 때 프로젝트 파일 충돌 나는 거 진짜 스트레스였는데, Tuist 쓰고 나서 그런 걱정 없어졌어요.&lt;/p&gt;
&lt;p&gt;아직 안 써보신 분들은 토이프로젝트부터 시작해 보세요! 금방 매력에 빠질 거예요 ㅋㅋㅋ&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;다음 글 예고&lt;/strong&gt;: Plugin 만들기, 고급 설정들 다뤄볼 예정입니다!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;작성자&lt;/strong&gt;: iOS 운영진 서원지&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href=&quot;https://github.com/Roy-wonji&quot;&gt;https://github.com/Roy-wonji&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>#Tuist #iOS #모듈화 #Swift</category>
      <author>dynamic-ddd</author>
      <guid isPermaLink="true">https://dynamic-ddd.tistory.com/4</guid>
      <comments>https://dynamic-ddd.tistory.com/4#entry4comment</comments>
      <pubDate>Sat, 24 Jan 2026 17:01:59 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] 당연하게 쓰던 메서드에게 뒤통수 맞아보기</title>
      <link>https://dynamic-ddd.tistory.com/3</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 사이드 프로젝트 DDD 동아리의 iOS 운영진 은표입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 동아리에서 새로운 기수를 맞아 운영진들끼리 여러 팀을 구성하게 되었습니다. 저는 콘텐츠 브랜딩 팀에 소속되어 블로그 글을 작성하게 되었는데요. 마침 제가 첫 글이네요. 부담이 살짝 되기도 합니다 ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글 주제 선정에 대해서 참 고민이 많았습니다. 요즘 많은 LLM이 시장에 나와있고 궁금한 건 LLM을 통해 웬만해서 다 해결되는 시대라, 점점 가면 갈수록 질문을 할 기회도 적어지고 제가 속해있던 커뮤니티들도 점점 잠잠해지더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 뻔한 주제는 작성하기는 너무 싫었습니다. 클린 아키텍처 같은거요. 헥사고날이면 모를까 개념만으로 존재하고, 각자 구현 방법이 조금씩은 다르니까요. 그렇다고 제가 작성하는 코드가 그렇게까지 클린한 것 같지도 않기도 하구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린한 코드를 작성하는 방법은 다른 멋진 개발자 분들께서 잘 작성해주시니, 저는 이번 글에서는 우리가 평소에 너무나 당연하게 쓰면서 위험이 존재하는 메서드를 하나 소개하고 개선하는 방법을 소개해볼까 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.hackingwithswift.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;hackingwithswift &lt;/b&gt;&lt;/a&gt;라는 플랫폼을 아시나요? 저는 예전에는 가끔 들어가서 아티클들을 읽었었는데요, 서두에 말씀드렸듯이 LLM이 발전함에 따라 서서히 안보게 되던 플랫폼 중 하나입니다. 오랜만에 갑자기 눈에 들어서 아티클을 하나 읽었는데 이 내용을 조금 더 살을 붙여 공유드리려고 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  본론을 위한 밑밥깔기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서론이 꽤나 길었죠? 제가 생각보다 말이 많네요. 오랜만에 글이란걸 써봐서 그래요 이해해주세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 어떻게 보면 너무 뻔하고 지루한 글을 작성할겁니다. iOS 개발을 조금 하셨거나, 컴퓨터 공학을 전공했다면 너무나 당연한 이야기를 하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Objective-C 이야기도 조금 섞여 있으니 아조씨들도 나가지말고 읽어주세요.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 개발자라면 보통 &lt;b&gt;Swift&lt;/b&gt;를 사용하실건데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Swift&lt;/b&gt;를 사용한다면 어떻게 해서든 자연스럽게 &lt;b&gt;String&lt;/b&gt;을 사용하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분은 &lt;b&gt;String&lt;/b&gt;이 어떤 구조를 가지고 있는지 아시나요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1676&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/covFJy/dJMcaaDX4ZZ/1cqSITlY6IPR7YdnoekwV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/covFJy/dJMcaaDX4ZZ/1cqSITlY6IPR7YdnoekwV0/img.png&quot; data-alt=&quot;Apple 공식 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/covFJy/dJMcaaDX4ZZ/1cqSITlY6IPR7YdnoekwV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcovFJy%2FdJMcaaDX4ZZ%2F1cqSITlY6IPR7YdnoekwV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1676&quot; height=&quot;644&quot; data-origin-width=&quot;1676&quot; data-origin-height=&quot;644&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Apple 공식 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서를 보면 character들의 모음을 String이라고 부르네요! 근데 &lt;b&gt;Unicode&lt;/b&gt; 라는 단어가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유니코드(Unicode)&lt;/b&gt;가 무엇이고 왜 생겨났을까요?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 국제 표준&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나라나 시스템마다 다른 문자 인코딩 방식(예: ASCII, EUC-KR)을 사용하는데요! (ASCII는 많이들 들어보셨죠?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 시스템끼리 문서 같은걸 주고 받으면 글자가 깨지는 현상을 겪어보신 분들도 계실거에요. 이 문제를 해결하기 위해 &lt;b&gt;유니코드&lt;/b&gt;가 생겨났어요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 유니코드로 어떻게 해결했냐면, 생각보다 간단합니다. 이 세상의 모든 문자, 기호, 이모티콘 등에 고유한 번호를 매겨주는겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 'A'는 &lt;i&gt;U+0041&lt;/i&gt;, '가'는 &lt;i&gt;U+AC00&lt;/i&gt; 과 같은 고유한 번호를요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니코드를 통해 우리는 한글, 영어, 일본어, 한자, 아랍어 등등등... 대부분의 문자를 하나의 체계로 통합하여 다룰 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 고유한 번호들을 바이트로 변환하는 작업을 인코딩이라고 부르며 &lt;b&gt;UTF-8&lt;/b&gt;, &lt;b&gt;UTF-16&lt;/b&gt;과 같은 방법들이 있습니다. 그 중 &lt;b&gt;UTF-8&lt;/b&gt;은 가장 널리 사용되는 방식이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 왜 갑자기 유니코드 이야기를 이렇게 길게 했냐구요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 오늘 이야기할 주제가 유니코드로 인하여 발생되는 문제이기 때문입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  본론 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 프로젝트 진행하면서나 코딩테스트 문제를 풀 때 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;replacingOccurrences(of:with:)&lt;/b&gt;&lt;/span&gt;&lt;b&gt; &lt;/b&gt;메서드, 한번쯤은 써보거나 본 적 있으시죠? (없어도 있다고 해주세요.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fd4kK/dJMcaiPuyVw/P6qKphfzJjQP41VfvfUrY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fd4kK/dJMcaiPuyVw/P6qKphfzJjQP41VfvfUrY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fd4kK/dJMcaiPuyVw/P6qKphfzJjQP41VfvfUrY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFd4kK%2FdJMcaiPuyVw%2FP6qKphfzJjQP41VfvfUrY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1700&quot; height=&quot;820&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 크게 설명드릴게 없습니다. 문자열에서 특정 문자열을 찾아, 원하는 문자열로 바꾸고 싶을 때 사용하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이 메서드를 사용할 때 어떤 문제가 발생할 수 있는지 한번 볼까요?&lt;/p&gt;
&lt;pre id=&quot;code_1767632428201&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let vacation: String = &quot; &quot; // 캐나다와 미국 국기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 두 개의 국기 이모티콘으로 이루어진 문자열이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 문자열에서 있지도 않은 &lt;b&gt;호주 국기( )&lt;/b&gt;를 &lt;b&gt;니카라과 국기( )&lt;/b&gt;로 바꾸려고 시도하면 어떻게 될까요?&lt;/p&gt;
&lt;pre id=&quot;code_1767632538865&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(vacation.replacingOccurrences(of: &quot; &quot;, with: &quot; &quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 당연히 호주 국기는 없으니까 아무것도 안 바뀌고 &quot; &quot;가 그대로 나오겠지? 싶잖아요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 결과는 아래와 같이 표시됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767632674801&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot; &quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;206&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drtiA5/dJMcacIzgkT/Ek6UMKFwZeRLty56m2uwZk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drtiA5/dJMcacIzgkT/Ek6UMKFwZeRLty56m2uwZk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drtiA5/dJMcacIzgkT/Ek6UMKFwZeRLty56m2uwZk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrtiA5%2FdJMcacIzgkT%2FEk6UMKFwZeRLty56m2uwZk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;206&quot; height=&quot;244&quot; data-origin-width=&quot;206&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아니 캐나다, 미국 국기였는데 갑자기 중국과 아이슬란드 국기가 튀어나왔어요! 왤까요? &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  원인 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;replacingOccurrences(of:with:)&lt;/b&gt;&lt;/span&gt; 메서드는 Swift Native 메서드가 아니에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd; color: #ee2323;&quot;&gt;Objective-C&lt;/span&gt; 시절부터 있던 고인물 메서드입니다. 그리고 이 고여버린 메서드는 우리가 앞에서 이야기했던 &lt;b&gt;유니코드를 제대로 이해하지 못하고 처리하지 못합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;국기 이모티콘은 사실 두 개의 특수한 유니코드 문자가 합쳐져서 만들어져요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-  는 &lt;b&gt;'C'&lt;/b&gt;와 &lt;b&gt;'A'&lt;/b&gt;를 나타내는 특수 문자의 조합입니다.&lt;br /&gt;-  는 &lt;b&gt;'U'&lt;/b&gt;와 &lt;b&gt;'S'&lt;/b&gt;를 나타내는 특수 문자의 조합이고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &quot; &quot;라는 문자열은 내부적으로는 &lt;b&gt;[C, A, U, S]&lt;/b&gt; 라는 4개의 특수 문자로 이루어져 있는 셈이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 문제가 발생합니다. 우리가 바꾸려던 호주 국기  는 &lt;b&gt;[A, U]&lt;/b&gt; 문자 조합이거든요. &lt;span style=&quot;background-color: #dddddd; color: #ee2323;&quot;&gt;Objective-C&lt;/span&gt;의 낡은 메서드는 이걸 글자가 아니라 그냥 바이트 덩어리로 보고, &lt;b&gt;&quot;CAUS&quot;&lt;/b&gt; 중간에 있는 &lt;b&gt;&quot;AU&quot;&lt;/b&gt;를 찾아내버리게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 찾아낸 &lt;b&gt;&quot;AU&quot;&lt;/b&gt;를 니카라과 국기  의 조합인 &lt;b&gt;[N, I]&lt;/b&gt;로 바꿔치기합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과, 원래 &lt;b&gt;[C, A, U, S]&lt;/b&gt; 였던 문자열은 &lt;b&gt;[C, N, I, S]&lt;/b&gt; 로 바뀌게 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift가 이 새로운 문자 조합을 다시 화면에 표시하려고 보니...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;[C, N]&lt;/b&gt;은 중국 국기  &lt;br /&gt;- &lt;b&gt;[I, S]&lt;/b&gt;는 아이슬란드 국기  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 끔찍한 결과가 나와버린 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sdJOK/dJMcaiosqju/ZC1ZCOugzZtyDhGHlJlaIk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sdJOK/dJMcaiosqju/ZC1ZCOugzZtyDhGHlJlaIk/img.jpg&quot; data-alt=&quot;Swift 좋아.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sdJOK/dJMcaiosqju/ZC1ZCOugzZtyDhGHlJlaIk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsdJOK%2FdJMcaiosqju%2FZC1ZCOugzZtyDhGHlJlaIk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;351&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Swift 좋아.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  해결책&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 해결책은 정말 너무나 쉽습니다. Swift가 제공하는 최신 네이티브 메서드를 쓰면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beiscw/dJMcabJEY5p/shyb9OcPgimek8qc9MRKs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beiscw/dJMcabJEY5p/shyb9OcPgimek8qc9MRKs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beiscw/dJMcabJEY5p/shyb9OcPgimek8qc9MRKs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbeiscw%2FdJMcabJEY5p%2Fshyb9OcPgimek8qc9MRKs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1694&quot; height=&quot;954&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용법도 너무 쉽습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767633956190&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(vacation.replacing(&quot; &quot;, with: &quot; &quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 설계 단계부터 유니코드를 완벽하게 이해하고 만들어졌기 때문에,  와  를 각각 하나의 의미 단위(글자)로 정확하게 인지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 그 안에 호주 국기  가 통째로 들어있지 않다는 걸 정확히 알고, 아무것도 바꾸지 않은 채 원래 문자열 &quot; &quot;을 그대로 반환해 주죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 우리가 상상하고 원했던 결과잖아요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 앞에서 말은 길게 써놓고 해결책은 간단해서 어이없죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 하나 있어요. (저한테만 있을 수도)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI0jy3/dJMcah399LX/nTFyPh4gjGCDkhDMczhJL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI0jy3/dJMcah399LX/nTFyPh4gjGCDkhDMczhJL0/img.png&quot; data-alt=&quot;✨ 최소 지원 버전 ✨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI0jy3/dJMcah399LX/nTFyPh4gjGCDkhDMczhJL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI0jy3%2FdJMcah399LX%2FnTFyPh4gjGCDkhDMczhJL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;902&quot; height=&quot;76&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;✨ 최소 지원 버전 ✨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 사이드 프로젝트면 문제없는데요, 저희 회사는 iOS 9도 지원하거든요? 아 이거 절대 못쓰죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 iOS 15 이하 환경에서는, 한땀한땀 가내수공업 말고 없을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnuFYz/dJMcabiAcUb/R56h7eCKkvk8WqutpbZXq0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnuFYz/dJMcabiAcUb/R56h7eCKkvk8WqutpbZXq0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnuFYz/dJMcabiAcUb/R56h7eCKkvk8WqutpbZXq0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cnuFYz/dJMcabiAcUb/R56h7eCKkvk8WqutpbZXq0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;508&quot; height=&quot;250&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예. 그것이 iOS 개발이니까.&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  iOS 15 이하 대응해주기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과만 필요하신 분들을 위해 코드부터 드리고 시작하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에 대한 상세 설명은 아래에서 진행할게요!&lt;/p&gt;
&lt;pre id=&quot;code_1767637084355&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension String {
  func replacingCompat(
    _ target: String,
    with replacement: String,
    maxReplacements: Int = .max
  ) -&amp;gt; String {
    if #available(iOS 16.0, *) { // 선택 사항
      return replacing(target, with: replacement, maxReplacements: maxReplacements)
    }

    guard !target.isEmpty, maxReplacements &amp;gt; 0 else { return self }

    var result = &quot;&quot;
    result.reserveCapacity(utf8.count)

    var searchStart = startIndex
    var replaced = 0

    while replaced &amp;lt; maxReplacements,
          let r = range(of: target, range: searchStart..&amp;lt;endIndex) {

      result.append(contentsOf: self[searchStart..&amp;lt;r.lowerBound])
      result.append(contentsOf: replacement)

      replaced += 1
      searchStart = r.upperBound
    }

    result.append(contentsOf: self[searchStart..&amp;lt;endIndex])
    return result
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;replacingCompat(&lt;b&gt;_:with:maxReplacements:)&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;이라는 메서드를 하나 만들었습니다. 이 메서드는 3개의 파라미터를 받습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;target&lt;/b&gt;: 찾아 바꿀 문자열&lt;/li&gt;
&lt;li&gt;&lt;b&gt;replacement&lt;/b&gt;: target 대신 들어갈 새로운 문자열&lt;/li&gt;
&lt;li&gt;&lt;b&gt;maxReplacements&lt;/b&gt;: 최대 몇 번까지 바꿀지 정하는 횟수(기본값은 .max로, 제한 없음을 의미합니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 전에 보았던 iOS 16+의 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;String.replacing(_:with:maxReplacements:)&lt;/b&gt;&lt;/span&gt;와 동일한 파라미터를 받아 호환되도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 예외 처리 (Early Exit)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적인 작업에 앞서, 굳이 실행할 필요가 없는 예외 케이스를 미리 처리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767638023973&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;guard !target.isEmpty, maxReplacements &amp;gt; 0 else { return self }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;!target.isEmpty:&lt;/b&gt; 찾아야 할 target이 비어있으면 작업을 수행할 수 없으므로 바로 종료합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;maxReplacements &amp;gt; 0:&lt;/b&gt; 바꿔야 할 횟수가 0번이면 아무것도 할 필요가 없으므로 바로 종료합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;return self:&lt;/b&gt; 위 조건 중 하나라도 해당되면, 아무것도 바꾸지 않고 원본 문자열(self)을 그대로 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 결과 변수 준비 및 최적화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교체 결과를 담을 새로운 문자열을 만들고, 성능을 위해 메모리를 미리 준비합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767638111475&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var result = &quot;&quot;
result.reserveCapacity(utf8.count)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;var result = &quot;&quot;:&lt;/b&gt; 교체 작업의 결과를 차곡차곡 쌓아나갈 새로운 빈 문자열 result를 만듭니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;result.reserveCapacity(utf8.count):&lt;/b&gt; (성능 최적화) result 문자열에 내용이 추가될 때마다 메모리를 새로 할당하는 비효율을 막기 위해, 미리 원본 문자열(self)의 크기만큼 메모리 공간을 예약합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 반복문 상태 변수 초기화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문을 제어하기 위한 변수들을 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767638288908&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var searchStart = startIndex
var replaced = 0&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;var searchStart = startIndex:&lt;/b&gt; target을 찾기 시작할 위치를 저장하는 변수입니다. 처음에는 당연히 문자열의 맨 앞(startIndex)에서 시작합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;var replaced = 0:&lt;/b&gt; 지금까지 몇 번 교체했는지 횟수를 세는 변수입니다. maxReplacements와 비교하기 위해 사용됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. 핵심 반복문 (target 검색)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;target을 찾고 교체하는 핵심 작업을 반복 수행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767638343115&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while replaced &amp;lt; maxReplacements,
	let r = range(of: target, range: searchStart..&amp;lt;endIndex) {&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;replaced &amp;lt; maxReplacements:&lt;/b&gt; 교체 횟수가 maxReplacements보다 적을 때.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;let r = range(of: target, range: searchStart..&amp;lt;endIndex):&lt;/b&gt; (핵심 로직) searchStart 위치부터 문자열 끝(endIndex)까지의 범위에서 target을 검색합니다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;성공 시: target을 찾으면 그 위치 범위(Range)가 r에 저장되고 while문 안의 코드가 실행됩니다.&lt;/li&gt;
&lt;li&gt;실패 시: 더 이상 target을 찾지 못하면 nil이 반환되어 while문이 종료됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5. 찾은 내용 교체 및 상태 갱신 (반복문 내부)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;target을 성공적으로 찾았을 때 실행되는 로직입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767638439642&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;result.append(contentsOf: self[searchStart..&amp;lt;r.lowerBound])
result.append(contentsOf: replacement)

replaced += 1
searchStart = r.upperBound&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;result.append(contentsOf: self[searchStart..&amp;lt;r.lowerBound]):&lt;/b&gt; result에 (이전 탐색 위치 ~ 방금 찾은 `target`의 시작 위치 바로 앞)` 까지의 문자열을 먼저 붙여넣습니다. (즉, 건드리지 않을 부분을 그대로 복사)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;result.append(contentsOf: replacement):&lt;/b&gt; 그 다음, target이 있던 자리에 replacement 문자열을 이어서 붙입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;replaced += 1:&lt;/b&gt; 교체를 한 번 완료했으므로, 횟수를 1 증가시킵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;searchStart = r.upperBound:&lt;/b&gt; (가장 중요한 부분) 다음 검색은 방금 찾은 target의 끝나는 지점 바로 다음에서 시작하도록 searchStart 위치를 갱신합니다. 이것이 무한 루프와 비효율적인 중복 검색을 막는 핵심입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;6. 마무리 및 반환&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문이 끝나고 남은 부분을 처리한 뒤, 최종 결과를 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767638501799&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;result.append(contentsOf: self[searchStart..&amp;lt;endIndex])
return result&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;result.append(contentsOf: self[searchStart..&amp;lt;endIndex]):&lt;/b&gt; while 반복문이 모두 끝난 후, 마지막으로 찾았던 위치부터 문자열의 맨 끝까지 남아있던 부분을 result에 마저 붙여줍니다. (만약 target을 한 번도 못 찾았다면, searchStart는 여전히 startIndex이므로 이 라인에서 원본 문자열 전체가 복사됩니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;return result:&lt;/b&gt; 이렇게 완성된 새로운 문자열 result를 최종적으로 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj;♂️ 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 써보지만 블로그 글 쓰는건 언제나 힘든 것 같습니다. 매번 꾸준히 글 쓰시는 분들 정말 존경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 지식이 크게 없는 분들도 이해시켜드리기 위해서 최대한 상세히 작성은 했지만, 읽으시기 쉬울지는 잘 모르겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 조금이나마 도움이 되었으면 좋겠습니다. 아니면 흥미를 느꼈거나 재미라도 있으셨다면 만족합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글 읽어주셔서 감사합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;915&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WABmy/dJMcajnjTOe/LnpKcWEqKhMAk85nKQ5lF0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WABmy/dJMcajnjTOe/LnpKcWEqKhMAk85nKQ5lF0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WABmy/dJMcajnjTOe/LnpKcWEqKhMAk85nKQ5lF0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWABmy%2FdJMcajnjTOe%2FLnpKcWEqKhMAk85nKQ5lF0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;915&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;915&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;figure id=&quot;og_1767802472635&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;One Swift mistake everyone should stop making today&quot; data-og-description=&quot;TL;DR: You should use replacing(_:with:) rather than replacingOccurrences(of:with:)&quot; data-og-host=&quot;www.hackingwithswift.com&quot; data-og-source-url=&quot;https://www.hackingwithswift.com/articles/280/one-swift-mistake-everyone-should-stop-making-today&quot; data-og-url=&quot;https://www.hackingwithswift.com/articles/280/one-swift-mistake-everyone-should-stop-making-today&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/UHDBo/hyZQMGL7ax/2sRmqUcwAJZm86B27Etymk/img.jpg?width=1500&amp;amp;height=1000&amp;amp;face=0_0_1500_1000,https://scrap.kakaocdn.net/dn/fKwuX/hyZPMPa2jL/FuRjd6QC6TuoWKuq6agmkK/img.jpg?width=1500&amp;amp;height=1000&amp;amp;face=0_0_1500_1000,https://scrap.kakaocdn.net/dn/jxV6V/hyZRik5P7w/93hCD1kXSR3HKGOGsw6cd1/img.jpg?width=1500&amp;amp;height=1000&amp;amp;face=0_0_1500_1000&quot;&gt;&lt;a href=&quot;https://www.hackingwithswift.com/articles/280/one-swift-mistake-everyone-should-stop-making-today&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.hackingwithswift.com/articles/280/one-swift-mistake-everyone-should-stop-making-today&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/UHDBo/hyZQMGL7ax/2sRmqUcwAJZm86B27Etymk/img.jpg?width=1500&amp;amp;height=1000&amp;amp;face=0_0_1500_1000,https://scrap.kakaocdn.net/dn/fKwuX/hyZPMPa2jL/FuRjd6QC6TuoWKuq6agmkK/img.jpg?width=1500&amp;amp;height=1000&amp;amp;face=0_0_1500_1000,https://scrap.kakaocdn.net/dn/jxV6V/hyZRik5P7w/93hCD1kXSR3HKGOGsw6cd1/img.jpg?width=1500&amp;amp;height=1000&amp;amp;face=0_0_1500_1000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;One Swift mistake everyone should stop making today&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;TL;DR: You should use replacing(_:with:) rather than replacingOccurrences(of:with:)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.hackingwithswift.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✍️ 작성자: 은표&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;직군:&lt;/b&gt; iOS Developer&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GitHub:&lt;/b&gt; &lt;a href=&quot;https://github.com/honghoker&quot;&gt;https://github.com/honghoker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>iOS</category>
      <category>DDD</category>
      <category>Dynamic Developer Designer</category>
      <category>ios</category>
      <category>Objective-C</category>
      <category>replacing</category>
      <category>replacingOccurrences</category>
      <category>string</category>
      <category>swift</category>
      <author>dynamic-ddd</author>
      <guid isPermaLink="true">https://dynamic-ddd.tistory.com/3</guid>
      <comments>https://dynamic-ddd.tistory.com/3#entry3comment</comments>
      <pubDate>Tue, 6 Jan 2026 03:44:27 +0900</pubDate>
    </item>
  </channel>
</rss>