<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>baegteun - iOS</title>
    <link>https://baegteun.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 17 Aug 2026 20:51:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>baegteun</managingEditor>
    <image>
      <title>baegteun - iOS</title>
      <url>https://tistory1.daumcdn.net/tistory/5531731/attach/a06c35094adb4a6c9a68c74100cada00</url>
      <link>https://baegteun.tistory.com</link>
    </image>
    <item>
      <title>Tuist 4 버전&amp;nbsp;업그레이드</title>
      <link>https://baegteun.tistory.com/16</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;24년 2월 8일, Tuist 4.0.0이 공식적으로 릴리즈되었습니다.&lt;br /&gt;Tuist가 4.0.0 Major 버전이 업그레이드되면서 변경된 점을 알아보겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/releases/tag/4.0.0&quot;&gt;https://github.com/tuist/tuist/releases/tag/4.0.0&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;tuistenv를 통한 버전 관리 Drop&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/5556&quot;&gt;https://github.com/tuist/tuist/pull/5556&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist 4 이전에는 &lt;code&gt;tuist install&lt;/code&gt; &lt;code&gt;tuist uninstall&lt;/code&gt; 등의 명령어를 통해 로컬에 있는 Tuist의 버전을 변경하거나, &lt;code&gt;.tuist-version&lt;/code&gt; 파일에 버전을 정의해놓아서 프로젝트에서 사용할 Tuist 버전을 고정해놓아서 사용할 수 있었습니다. 그리고 이런 기능을 제공하는 것이 &lt;code&gt;tuistenv&lt;/code&gt;라는 도구였습니다. (tuist 설치 시 같이 설치되었습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 tuistenv는 Tuist 4에는 완전히 삭제가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist 4로 업그레이드했다면 기존의 tuistenv는 &lt;code&gt;curl-Ls https://uninstall.tuist.io | bash&lt;/code&gt; 로 제거하고 &lt;a href=&quot;https://mise.jdx.dev/&quot;&gt;Mise&lt;/a&gt;를 사용하기를 적극적으로 권장한다고 하네요.&lt;br /&gt;혹은 &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; 또한 지원한다고 합니다.&lt;/p&gt;
&lt;h1&gt;ProjectDescription 모델들의 init 생성자 Drop&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/5843&quot;&gt;https://github.com/tuist/tuist/pull/5843&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Target, Scheme과 같은 ProjectDescription 모델들의 init 생성자를 제거합니다.&lt;br /&gt;대신 static constructor를 통해 인스턴스 생성을 할 수 있도록 제공한다고 합니다.&lt;br /&gt;생성자 API의 가독성과 표현력을 개선하기 위해 static constructor로 변경하게 되었다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;Target(name: ...)

-&amp;gt;

Target.target(name: ...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static constructor는 모델 이름을 사용하여 작명합니다.&lt;br /&gt;e.g.&lt;br /&gt;Target.target(&amp;hellip;)&lt;br /&gt;Arguments.arguments(&amp;hellip;)&lt;br /&gt;Path.path(&amp;hellip;)&lt;/p&gt;
&lt;h1&gt;--no-cache 가 --no-binary-cache로 rename&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;tuist generate&lt;/code&gt; &lt;code&gt;tuist build&lt;/code&gt; 등의 명령어에서 사용할 수 있었던 --no-cache argument가 --no-binary-cache로 rename되었습니다.&lt;br /&gt;--no-cache의 flag가 명확하지 않아 binary로 rename되었다고 합니다.&lt;/p&gt;
&lt;h1&gt;tuist fetch 가 tuist install 로 rename&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/5857&quot;&gt;https://github.com/tuist/tuist/pull/5857&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성을 가져오는 명령어였던 tuist fetch가 tuist install로 rename되었습니다.&lt;br /&gt;tuist와 결이 비슷한 툴과의 컨벤션을 맞추기 위함이라고 합니다.&lt;br /&gt;npm install, yarn install 같은 이런 툴들을 말하는거같네요.&lt;/p&gt;
&lt;h1&gt;의존성 정의 방식을 Package.swift를 통한 방식으로 변경&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Package.swift 파일 위치 -  &lt;a href=&quot;https://github.com/tuist/tuist/pull/5862&quot;&gt;https://github.com/tuist/tuist/pull/5862&lt;/a&gt;&lt;br /&gt;Package.swift 지원 - &lt;a href=&quot;https://github.com/tuist/tuist/pull/5503&quot;&gt;https://github.com/tuist/tuist/pull/5503&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist 4 이전에는 Tuist/Dependencies.swift 에서 혹은 프로젝트의 packages에 package를 추가해주어서 (Native SPM방식 사용) 서드파티 의존성을 가져올 수 있었으나 Dependencies.swift를 완전히 제거했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 Dependabot, Renovatebot같은 툴을 사용할 수 없었고 사용자에게 불필요한 간접 참조?를 제공했기 때문이라네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 방식으로는 Package.swift에 의존성을 정의해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Package.swift의 경로는 Tuist/Package.swift가 아닌 프로젝트의 루트에 위치해야한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 Package.swift를 통한 의존성을 정의하는 방식은 3.31.0부터 지원을 했습니다. 3버전 후반에는 Dependencies.swift는 deprecated처리만 되고 4버전부터는 사용이 완전히 불가능하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dependencies.swift에서 할 수 있었던 의존성의 productType을 바꾸거나 사용 platform을 지정하는 설정은 #if TUIST 안에서 정의해야한다고 합니다.&lt;br /&gt;자세한건 &lt;a href=&quot;https://docs.tuist.io/guide/project/dependencies#swift-packages&quot;&gt;이 문서&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;// swift-tools-version: 5.8
import PackageDescription

#if TUIST
    import ProjectDescription
    import ProjectDescriptionHelpers

    let packageSettings = PackageSettings(
        productTypes: [
            &quot;Alamofire&quot;: .framework, // default is .staticFramework
        ],
        platforms: [.iOS]
    )
#endif

let package = Package(
    name: &quot;PackageName&quot;,
    dependencies: [
        .package(url: &quot;https://github.com/Alamofire/Alamofire&quot;, from: &quot;5.0.0&quot;),
    ]
)&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;tuist cache warm이 tuist cache로 rename&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tuist cache warm가 tuist cache로 더 짧게 사용할 수 있게 되었습니다.&lt;/p&gt;
&lt;h1&gt;tuist cache print-hashes가 tuist cache --print-hashes로 rename&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;print-hashes가 cache캐시 명령어의 플래그임을 명확히 하기 위해 tuist cache --print-hashes로 rename된다고 합니다.&lt;/p&gt;
&lt;h1&gt;Caching Profile 제거&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/5833&quot;&gt;https://github.com/tuist/tuist/pull/5833&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist 4 이전에는 Config.swift에서 cache의 profile을 지정할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Uemqh/btsGOG6Du3t/iGPeRWst3XnSTsbAVak4Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Uemqh/btsGOG6Du3t/iGPeRWst3XnSTsbAVak4Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Uemqh/btsGOG6Du3t/iGPeRWst3XnSTsbAVak4Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUemqh%2FbtsGOG6Du3t%2FiGPeRWst3XnSTsbAVak4Fk%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;802&quot; height=&quot;268&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 debug, release의 configuration별로 profile을 분리해서 각각 cache이 적용되도록 하는 느낌으로 사용되는거같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 cache profile이 제거된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제거하기로 한 이유는 프로젝트 생성 과정 중 생성에 사용된 프로필이 아닌 다른 프로필을 사용할 때 혼동을 일으킬 수 있다고 합니다.&lt;br /&gt;또한 사용자가 debug 프로필로 릴리즈 버전을 빌드할 때 예상할 수 없는 결과가 발생할 수 있어서 제거한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 cache 명령어의 옵션에--configuration를 추가하여 프로젝트를 생성할 때 configuration을 지정할 수 있게 되었습니다.&lt;/p&gt;
&lt;h1&gt;--skip-cache 제거&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;tuist generate&lt;/code&gt; 를 할 때 --skip-cache와 대상을 지정해서 캐시를 건너뛸 대상을 지정할 수 있었습니다.&lt;br /&gt;하지만 Tuist 4 부터는 &lt;code&gt;tuist generate Foo&lt;/code&gt; 와 같이 argument로 명시하는 대상이 캐시 스킵 대상으로 지정됩니다.&lt;/p&gt;
&lt;h1&gt;Signing Capabilities 기능 Drop&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/5716&quot;&gt;https://github.com/tuist/tuist/pull/5716&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist 3 버전대에서는 Tuist/Signing 디렉토리에 인증서와 프로비저닝 프로필을 넣어놓으면 tuist generate를 할 때 자동으로 Signing이 되는 기능을 제공해주었습니다.&lt;br /&gt;하지만 Tuist 4 에서는 이 기능이 Drop되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 Fastlane이나 Xcode 자체 기능같이 커뮤니티 툴이 위 문제를 더 잘 해결하고 있기 때문입니다.&lt;br /&gt;Signing은 Tuist의 장기적인 목표이기에 핵심 기능에 집중하는 것이 낫다고 생각하여 Drop했다고 합니다.&lt;/p&gt;
&lt;h1&gt;Dependencies.swift(Package.swift)를 통한 Carthage 지원 중단&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/5740&quot;&gt;https://github.com/tuist/tuist/pull/5740&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist 4 이전에는 Dependencies.swift에서 Carthage를 통한 의존성을 정의하고 가져올 수 있었지만 Tuist 4 부터는 직접적으로 Tuist를 통한 관리는 되지 않습니다.&lt;/p&gt;
&lt;h5&gt;* Carthage 사용이 불가능한게 아닙니다.&lt;/h5&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 SPM이 의존성 관리 방법으로 선호될 미래를 고려할 때 Carthage는 장기적인 목표라고 생각하여 Drop했다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Carthage를 사용하기 위해서는 Carthage를 직접 사용하여 framework와 xcframework를 가져온 다음 TargetDependency.framework 또는 TargetDependency.xcframework로 바이너리를 참조해야한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한건 &lt;a href=&quot;https://docs.tuist.io/guide/project/dependencies#external-dependencies&quot;&gt;이 문서&lt;/a&gt;에서 알아보실 수 있습니다.&lt;br /&gt;Carthage만이 아닌 Cocoapods를 사용하여 서드파티 의존성을 가져올 수 있는 방법도 알려주네요.&lt;/p&gt;
&lt;h1&gt;TargetDependency.packagePlugin API Drop&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/5555&quot;&gt;https://github.com/tuist/tuist/pull/5555&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/5719&quot;&gt;https://github.com/tuist/tuist/pull/5719&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist 4 이전에는 TargetDependency.packagePlugin을 통해 패키지 플러그인을 의존성으로 정의할 수 있었지만 SPM이 새로운 패키지 유형을 도입한 것을 보고 유연하고 미래에 대비할 수 있는 방향으로 API를 변경했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TargetDependency.packagePlugin을 사용하고 있었다면 TargetDependency.package를 사용하고 argument에 패키지 타입을 전달하면된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g.&lt;br /&gt;TargetDependency.package(product: ..., type: .plugin)&lt;br /&gt;TargetDependency.package(product: ..., type: .macro)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 새로운 패키지 유형은 PR을 봤을 때 macro인듯하네요.&lt;/p&gt;
&lt;h1&gt;deprecated API Drop&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/5560&quot;&gt;https://github.com/tuist/tuist/pull/5560&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deprecate 처리 된 API들이 삭제되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 &lt;code&gt;DeploymentTarget&lt;/code&gt;이 &lt;code&gt;Destinations&lt;/code&gt;, &lt;code&gt;DeploymentTargets&lt;/code&gt;로 분리되며 deprecate된 &lt;code&gt;DeploymentTarget&lt;/code&gt;이 삭제된게 있겠네요.&lt;/p&gt;
&lt;h1&gt;References By&lt;/h1&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.tuist.io/guide/introduction/from-v3-to-v4.html&quot;&gt;https://docs.tuist.io/guide/introduction/from-v3-to-v4.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Tuist</category>
      <category>4.0</category>
      <category>4.0.0</category>
      <category>ios</category>
      <category>Module</category>
      <category>Swift</category>
      <category>Tuist</category>
      <category>tuist 4</category>
      <category>마이그레이션</category>
      <category>업그레이드</category>
      <category>업데이트</category>
      <author>baegteun</author>
      <guid isPermaLink="true">https://baegteun.tistory.com/16</guid>
      <comments>https://baegteun.tistory.com/16#entry16comment</comments>
      <pubDate>Sun, 21 Apr 2024 16:48:45 +0900</pubDate>
    </item>
    <item>
      <title>Tuist - Modular Architecture 개선하기</title>
      <link>https://baegteun.tistory.com/15</link>
      <description>&lt;p&gt;이 글은 Tuist 버전 3.19.0을 기준으로 작성되었습니다.&lt;/p&gt;
&lt;p&gt;이 글은 &lt;a href=&quot;https://baegteun.tistory.com/11&quot;&gt;https://baegteun.tistory.com/11&lt;/a&gt; 해당 글에서 이어집니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UserInterface 레이어 추가&lt;/li&gt;
&lt;li&gt;협업할 때 XCConfig 불편함 개선&lt;/li&gt;
&lt;li&gt;CI인지 여부를 Environment에서 관리하도록 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;UserInterface 레이어 추가&lt;/h1&gt;
&lt;h2&gt;기존 레이어의 문제점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Feature - Domain - Core - Shared 의 레이어를 가짐&lt;/li&gt;
&lt;li&gt;Core안에 DesignSystem 모듈이 있어 핵심적인 기능으로 쓰일 모듈이 있다는 의미에서 약간 애매해짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;-&amp;gt; UI에 관련한 모듈들을 넣을 레이어로 UserInterface 레이어를 새로 분리하였습니다.&lt;br&gt;해당 레이어에서 디자인 시스템, 다국어 적용 등의 처리를 하는 모듈들이 위치해있을겁니다.&lt;/p&gt;
&lt;h1&gt;협업할 때 XCConfig 불편함 개선&lt;/h1&gt;
&lt;p&gt;.gitignore에 XCConfig/ 를 등록해놓았기에 새 모듈이 생기면, 같이 협업하는 사람은 새 XCConfig를 추가로 만들어주거나 공유해줘야 했습니다.&lt;/p&gt;
&lt;p&gt;-&amp;gt; XCConfig/Shared.xcconfig 를 .gitignore에서 해재해놓고 기본적으로 모듈의 xcconfig 연결을 Shared에 연결해놓고, makeModule 생성자에서 configurations를 직접 커스텀하여 넣을 수 있게 해서 아무것도 없이 clone해도 바로 작업할 수 있도록 만들었습니다.&lt;/p&gt;
&lt;h1&gt;CI인지 여부를 Environment에서 관리하도록 변경&lt;/h1&gt;
&lt;p&gt;CI여부를 파악하려고 그때그떄 Process에서 값을 받아오는 것을 Environment struct에 추가하여 env.isCI로 간단하게 접근할 수 있게 했습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;바뀐 결과물은 확인할 수 있습니다.&lt;br&gt;&lt;a href=&quot;https://github.com/baekteun/Tuist_Modular_Template&quot;&gt;https://github.com/baekteun/Tuist_Modular_Template&lt;/a&gt; &lt;/p&gt;</description>
      <category>Tuist</category>
      <category>Architecture</category>
      <category>ios</category>
      <category>Micro Feature</category>
      <category>Modular Architecture</category>
      <category>Swift</category>
      <category>Tuist</category>
      <category>uFeature</category>
      <category>개선</category>
      <category>모듈화</category>
      <category>아키텍쳐</category>
      <author>baegteun</author>
      <guid isPermaLink="true">https://baegteun.tistory.com/15</guid>
      <comments>https://baegteun.tistory.com/15#entry15comment</comments>
      <pubDate>Mon, 1 May 2023 19:49:45 +0900</pubDate>
    </item>
    <item>
      <title>Tuist 사용법 - 9. Micro Feature, uFeature(The Modular Architecture,  TMA)로 확장성 높이기</title>
      <link>https://baegteun.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;이 글은 Tuist 버전 3.17.0 기준으로 작성되었습니다.&lt;br&gt;&lt;br&gt;2024년 5월 21일&lt;br&gt;MicroFeatures 아키텍쳐가 The Modular Architecture로 rename 되었습니다.&lt;br&gt;&lt;br&gt;document - &lt;a href=&quot;https://docs.tuist.io/guide/scale/tma-architecture&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://docs.tuist.io/guide/scale/tma-architecture&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;The Modular Architecture (TMA) | Tuist&quot; data-ke-align=&quot;alignCenter&quot; data-og-host=&quot;docs.tuist.io&quot; data-og-source-url=&quot;https://docs.tuist.io/guide/scale/tma-architecture&quot; data-og-url=&quot;https://docs.tuist.io/guide/scale/tma-architecture&quot;&gt;&lt;a href=&quot;https://docs.tuist.io/guide/scale/tma-architecture&quot; target=&quot;_blank&quot; data-source-url=&quot;https://docs.tuist.io/guide/scale/tma-architecture&quot;&gt;&lt;div class=&quot;og-image&quot;&gt;&lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;The Modular Architecture (TMA) | Tuist&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;docs.tuist.io&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;PR - &lt;a href=&quot;https://github.com/tuist/tuist/pull/6305&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/tuist/tuist/pull/6305&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;h1&gt;Micro Feature?&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&lt;br&gt;Micro Feature는 확장을 가능하게 하고, 빌드 및 테스트 주기를 최적화하며, 팀의 모범 사례를 보장하기 위해 Apple OS 애플리케이션을 구성하는 아키텍처 접근 방식입니다.&lt;br&gt;핵심 아이디어는 명확하고 간결한 API를 사용하여 상호 연결된 독립적인 기능을 구축하여 앱을 빌드하는 것입니다.&lt;br&gt;앱에는 여러 기능이 들어갈 수 있습니다. 로그인, 회원가입, 검색 등등 기능들이 모여서 사용자가 앱을 사용할 수 있게 합니다.&lt;br&gt;Micro Feature는 이런 작은 기능을 분리하고, 기본적으로 구현체를 interface 뒤에 숨긴 DIP의 형태를 띠게 됩니다.&lt;br&gt;각 feature는 블록이라 생각할 수 있습니다. 각 블록은 각자의 특정한 용도에만 사용되고 이 블록을 결합하면 온전한 기능을 갖춘 실제 앱이 구축되는 방식이라 생각할 수 있습니다.&lt;/p&gt;&lt;h1&gt;What?&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;Micro Feature는&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;Interface&lt;/li&gt;&lt;li&gt;Source&lt;/li&gt;&lt;li&gt;Testing&lt;/li&gt;&lt;li&gt;Tests&lt;/li&gt;&lt;li&gt;Example&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;로 5개의 타겟으로 조합됩니다.&lt;br&gt;각 Target에 대한 자세한 설명은&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;Interface : 해당 feature의 공개 interface랑 model이 포함된 Target입니다.&lt;/li&gt;&lt;li&gt;Source : 해당 feature의 공개 interface의 구현체가 포함된 Target입니다.&lt;/li&gt;&lt;li&gt;Tests : 해당 feature의 Unit Test, Integration Test등이 포함된 Target입니다.&lt;/li&gt;&lt;li&gt;Testing : Test나 Example에 사용할 수 있는 해당 feature의 테스트 더블 객체 등이 포함된 Target입니다.&lt;/li&gt;&lt;li&gt;Example : 해당 feature의 예제 앱을 만들어놓을 수 있는 Target입니다. 개발자가 특정 조건 (화면 사이즈, 언어) 등의 기능을 시험하기 위한 용도로도 사용합니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;또한 이 Target 간의 의존 관계는 아래와 같이 그릴 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u0HI9/btr6PBGIfNW/45fUugPzxGBzuhq4kx27HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u0HI9/btr6PBGIfNW/45fUugPzxGBzuhq4kx27HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u0HI9/btr6PBGIfNW/45fUugPzxGBzuhq4kx27HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu0HI9%2Fbtr6PBGIfNW%2F45fUugPzxGBzuhq4kx27HK%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; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h1&gt;Why?&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;왜 Micro Feature를 사용할까요? &lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;명확하고 간결한 API&lt;/li&gt;&lt;li&gt;작은 모듈&lt;/li&gt;&lt;li&gt;재사용성&lt;/li&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;명확하고 간결한 API&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;단일 Target으로 소스 코드를 작성하게 된다면 코드에 암시적으로 의존성을 구축하기가 매우 쉬워집니다.&lt;br&gt;모든 것이 강하게 결합되고, 상태를 예측할 수 없으며, 새로운 변경 사항이 도입하는 것은 악몽이 됩니다.&lt;br&gt;Micro Feature라면 공개용 interface모듈과 구현체 모듈을 분리해야 하기 때문에 무엇을 공개해야 하는지, 무엇을 비공개로 해야 하는지 생각해야 하기 때문에 feature가 기능을 사용하는 방식을 더 잘 제어하고 안전한 API를 설계할 수 있습니다.&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;작은 모듈&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;Divide and conqure.&lt;br&gt;작은 모듈에서 작업하면 feature에 더 많이 집중을 하고, 테스트하고, 기능을 독립적으로 시도할 수 있습니다.&lt;br&gt;그리고 기능을 작동시키는데 필요한 구성 요소만 컴파일하는 선택적인 컴파일을 할 수 있기 때문에 개발 주기를 훨씬 빠르게 진행할 수 있습니다.&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;재사용성&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;프레임워크 또는 라이브러리를 사용하여 앱과 Extension과 같은 다른 제품에서 코드를 재사용할 것을 권장합니다. Micro Feature를 구축하면 재사용이 매우 간단합니다.&lt;br&gt;기존 Micro Feature를 결합하고 필요한 경우 플랫폼별 UI 레이어를 추가하기만 하면 iMessage Extension, Today Extension 또는 watchOS 앱을 구축할 수 있습니다.&lt;/p&gt;&lt;h1&gt;Micro Feature의 목표&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;Micro Feature는 feature 전반에 걸쳐 수평적인 의존성 구조를 구축하는 것입니다. 어떠한 feature도 다른 feature의 구체적인 구현 세부 사항에 의존하지 않습니다. 단지 interface 모듈에 의존할 뿐입니다.&lt;br&gt;이렇게 하면 느슨하게 결합된 종속성이 생성되고, 또한 interface가 구현보다 변경될 가능성이 적고 이를 기반으로 캐시를 구축하여 빌드 시간을 단축할 수 있습니다.&lt;br&gt;큰 규모의 개별 기능 팀이 서로 독립적으로 작업할 수 있습니다. 모듈 A의 인터페이스가 정의되면 모듈 A에 의존하는 모든 팀이 모듈 A의 구현을 기다릴 필요 없이 인터페이스를 사용할 수 있으므로 팀의 작업 속도가 빨라지고 전체 제품 개발 속도가 빨라질 수 있습니다.&lt;/p&gt;&lt;h1&gt;적용 사례&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;실제로 제가 적용해 본 거로는&lt;br&gt;Tuist 템플릿을 만들 때 Micro Feature를 하도록 유도하였습니다.&lt;br&gt;&lt;a href=&quot;https://github.com/baekteun/Tuist_Modular_Template&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/baekteun/Tuist_Modular_Template&lt;/span&gt;&lt;/a&gt;&lt;br&gt;그리고 실제로 Dotori라는 프로젝트를 진행하며 적용해 놓고 개발하고 있습니다.&lt;br&gt;&lt;a href=&quot;https://github.com/Dotori-app/Dotori-iOS&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/Dotori-app/Dotori-iOS&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;h1&gt;References By&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://docs.tuist.io/building-at-scale/microfeatures/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://docs.tuist.io/building-at-scale/microfeatures/&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://alexanderweiss.dev/blog/2022-01-12-scale-up-your-app-with-microfeatures&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://alexanderweiss.dev/blog/2022-01-12-scale-up-your-app-with-microfeatures&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Scale up your app with Microfeatures&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;With an app becoming bigger in terms of people working on them as well as adding more features, the question arises of how to organize the growing complexity. Imagine multiple features that need to be implemented simultaneously by different teams. I...&quot; data-og-host=&quot;alexanderweiss.dev&quot; data-og-source-url=&quot;https://alexanderweiss.dev/blog/2022-01-12-scale-up-your-app-with-microfeatures&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/box0fZ/hyWlkNfuK0/Vu7Nlcw2dEy1EZd6PummA1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot; data-og-url=&quot;https://alexanderweiss.dev/blog/2022-01-12-scale-up-your-app-with-microfeatures&quot;&gt;&lt;a href=&quot;https://alexanderweiss.dev/blog/2022-01-12-scale-up-your-app-with-microfeatures&quot; target=&quot;_blank&quot; data-source-url=&quot;https://alexanderweiss.dev/blog/2022-01-12-scale-up-your-app-with-microfeatures&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/box0fZ/hyWlkNfuK0/Vu7Nlcw2dEy1EZd6PummA1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Scale up your app with Microfeatures&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;With an app becoming bigger in terms of people working on them as well as adding more features, the question arises of how to organize the growing complexity. Imagine multiple features that need to be implemented simultaneously by different teams. I...&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;alexanderweiss.dev&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/6305&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/tuist/tuist/pull/6305&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Some documentation adjustments by pepicrft · Pull Request #6305 · tuist/tuist&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;This PR introduces some documentation changes: In preparation for the consolidation of Tuist and Tuist Cloud, I renamed Projects to Tuist Projects, and merge Scale and Automation into that section...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/tuist/tuist/pull/6305&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdI4ef/hyWldUTzBm/K8IH4GkbUctRu4qtuafCb1/img.png?width=1200&amp;amp;height=600&amp;amp;face=1006_125_1049_173&quot; data-og-url=&quot;https://github.com/tuist/tuist/pull/6305&quot;&gt;&lt;a href=&quot;https://github.com/tuist/tuist/pull/6305&quot; target=&quot;_blank&quot; data-source-url=&quot;https://github.com/tuist/tuist/pull/6305&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdI4ef/hyWldUTzBm/K8IH4GkbUctRu4qtuafCb1/img.png?width=1200&amp;amp;height=600&amp;amp;face=1006_125_1049_173')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Some documentation adjustments by pepicrft · Pull Request #6305 · tuist/tuist&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;This PR introduces some documentation changes: In preparation for the consolidation of Tuist and Tuist Cloud, I renamed Projects to Tuist Projects, and merge Scale and Automation into that section...&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://docs.tuist.io/guide/scale/tma-architecture&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://docs.tuist.io/guide/scale/tma-architecture&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tuist</category>
      <category>Architecture</category>
      <category>Domain</category>
      <category>feature</category>
      <category>ios</category>
      <category>Micro Feature</category>
      <category>Swift</category>
      <category>Tuist</category>
      <author>baegteun</author>
      <guid isPermaLink="true">https://baegteun.tistory.com/14</guid>
      <comments>https://baegteun.tistory.com/14#entry14comment</comments>
      <pubDate>Mon, 3 Apr 2023 10:33:42 +0900</pubDate>
    </item>
    <item>
      <title>Swift로 웹사이트를 만드는 방법</title>
      <link>https://baegteun.tistory.com/13</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;Vapor + Leaf&lt;/li&gt;
 &lt;li&gt;Publish&lt;/li&gt;
 &lt;li&gt;SwifWeb&lt;/li&gt;
 &lt;li&gt;swift-web&lt;/li&gt;
 &lt;li&gt;SwiftWebUI&lt;/li&gt;
 &lt;li&gt;Tokamak&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Vapor + Leaf&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Server side 프레임워크인 vapor와 leaf를 사용하는 방법입니다.&lt;br&gt;leaf를 사용해 dynamic html 페이지를 렌더링할 수 있습니다.&lt;br&gt;&lt;a href=&quot;https://github.com/vapor/vapor&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/vapor/vapor&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://github.com/vapor/leaf&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/vapor/leaf&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://docs.vapor.codes/leaf/getting-started&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://docs.vapor.codes/leaf/getting-started&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Publish&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해외쪽에서 iOS 개발자 분들의 블로그들을 보다보면 가끔 만나볼 수 있는 프레임워크입니다.&lt;br&gt;Static site를 생성하고, 블로그쪽에서 유명한것같습니다.&lt;br&gt;디자인 퀄리티를 높이면 순수 Swift만 사용하여 만들기는 어렵고 css같은거를 만들고 건드려야합니다.&lt;br&gt;run을 하면 Output/ 에 html로 변환시킨 결과물이 나옵니다.&lt;br&gt;Publish로 만들어진 대표적인 블로그는 아래가 있습니다.&lt;br&gt;Publish를 만드신 장본인이기도 합니다.&lt;br&gt;&lt;a href=&quot;https://swiftbysundell.com&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://swiftbysundell.com&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://github.com/johnsundell/publish&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/johnsundell/publish&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SwifWeb&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유명하지는 않은데 Single Page Application(SPA)을 만들 수 있고, Progressive Web Apps(PWA)도 만들 수 있는 꽤나 흥미롭게 봤던 프레임워크입니다.&lt;br&gt;어쩌다보니 제 개인 &lt;a href=&quot;https://github.com/baekteun/Portfoilo&quot; target=&quot;_self&quot;&gt;&lt;span&gt;웹 포트폴리오&lt;/span&gt;&lt;/a&gt;를 이거로 만들어버렸습니다(?).&lt;br&gt;순수 Swift코드만을 작성하여 만들 수 있습니다.&lt;br&gt;단, 시작점의 로딩 애니메이션이나 트위터카드같은거를 바꾸기 위해서는 index.html을 건드려야합니다.&lt;br&gt;release를 하면 wasm로 변환시켜서 결과물이 나옵니다.&lt;br&gt;&lt;a href=&quot;https://github.com/swifweb/web&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/swifweb/web&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;swift-web&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The Composable Architecture를 만든 Pointfreeco에서 만든 프레임워크입니다.&lt;br&gt;뭔가 생긴게 신기하네요.&lt;br&gt;&lt;a href=&quot;https://github.com/pointfreeco/swift-web&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/pointfreeco/swift-web&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SwiftWebUI&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI 작성하듯 코드를 작성하여 웹페이지를 만들 수 있습니다.&lt;br&gt;실행하고 네트워크 탭을 보니 스타일 관련해서 뭔가 엄청나게 많이 요청이 갔던 기억이 있습니다.&lt;br&gt;&lt;a href=&quot;https://github.com/SwiftWebUI/SwiftWebUI&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/SwiftWebUI/SwiftWebUI&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Tokamak&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 SwiftWebUI와 같이 SwiftUI같이 코드를 작성하여 웹페이지를 만들 수 있습니다.&lt;br&gt;그리고 패키지로 있는 TokamakStaticHTML을 사용하여 &lt;a href=&quot;https://github.com/TokamakUI/TokamakVapor&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Vapor&lt;/span&gt;&lt;/a&gt;나 &lt;a href=&quot;https://github.com/TokamakUI/TokamakPublish&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Publish&lt;/span&gt;&lt;/a&gt;와 같은 프레임워크에 static 렌더링이 필요한 경우 사용할 수도 있다고 하더군요.&lt;br&gt;결과물은 wasm로 나옵니다.&lt;br&gt;&lt;a href=&quot;https://github.com/TokamakUI/Tokamak&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/TokamakUI/Tokamak&lt;/span&gt;&lt;/a&gt;&lt;br&gt;이 정도가 제가 알아본 Swift로 웹페이지를 만드는 방법들입니다.&lt;br&gt;혹시 이 글을 보는 지금, 한 번 Swift로 웹페이지를 만들어보는거 어떤가요?&lt;/p&gt;</description>
      <category>Swift</category>
      <category>leaf</category>
      <category>Publish</category>
      <category>Swift</category>
      <category>Swift-Web</category>
      <category>swiftwasm</category>
      <category>SwifWeb</category>
      <category>Vapor</category>
      <category>wasm</category>
      <category>web</category>
      <category>website</category>
      <author>baegteun</author>
      <guid isPermaLink="true">https://baegteun.tistory.com/13</guid>
      <comments>https://baegteun.tistory.com/13#entry13comment</comments>
      <pubDate>Tue, 21 Feb 2023 12:01:50 +0900</pubDate>
    </item>
    <item>
      <title>Tuist 사용법 - 8. Scaffold, Template를 사용하여 새로운 모듈 만들기</title>
      <link>https://baegteun.tistory.com/12</link>
      <description>&lt;p&gt;이 글은 Tuist 버전 3.16.0 기준으로 작성되었습니다.&lt;/p&gt;
&lt;h1&gt;Scaffold?&lt;/h1&gt;
&lt;p&gt;프로젝트에서 새로운 컴포넌트나 기능 만드는 것을 시작할 때 사용할 수 있는 기능입니다.&lt;br&gt;파일을 만들 때 &lt;code&gt;tuist scaffold&lt;/code&gt;를 사용하면 템플릿으로부터 파일을 만들 수 있습니다.&lt;br&gt;템플릿은 Tuist에서 기본으로 제공해주는 템플릿을 사용할 수도 있고, 직접 만들어 사용할 수도 있습니다.&lt;/p&gt;
&lt;h5&gt;&lt;a href=&quot;https://docs.tuist.io/commands/scaffold&quot;&gt;https://docs.tuist.io/commands/scaffold&lt;/a&gt;&lt;/h5&gt;
&lt;h1&gt;tuist scaffold 사용법&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;tuist scaffold &amp;lt;template&amp;gt; [--json] [--path &amp;lt;path&amp;gt;] &amp;lt;subcommand&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;사용 예시&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;tuist scaffold ribs --name Example&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이렇게만 본다면 단순히 Xcode Template 다른게 없거나 오히려 더 불편한거 아닌가? 싶을 수 있는데, scaffold는 이미 만들어진 모듈에 새로운 파일들을 만들 때 보다는 새로운 모듈을 만들 때에 더 적합한거같습니다.&lt;/p&gt;
&lt;p&gt;앞서 사용 예시의 상황을 더 구체화시켜보면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
- Features
    - AboutFeature
    - UserFeature
    - SettingsFeature
    - CompeteFeature
...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위와같이 Feature를 분리하는데, 이 기준을 RIB를 기준으로 분리한다 했을때. 위의 &lt;code&gt;tuist scaffold ribs --name Example&lt;/code&gt;을 하면 Project.swift의 내용물도 채우며 새로운 ExampleFeature 모듈을 만들것입니다.&lt;/p&gt;
&lt;h1&gt;템플릿 만들기&lt;/h1&gt;
&lt;p&gt;Tuist로 프로젝트를 하나 만들고, 그 안에 Template를 하나 만들어보겠습니다.&lt;/p&gt;
&lt;p&gt;먼저 &lt;code&gt;tuist init --platform ios&lt;/code&gt;으로 tuist 프로젝트 하나를 init하고 &lt;code&gt;tuist edit&lt;/code&gt;으로 edit을 시작하겠습니다.&lt;/p&gt;
&lt;p&gt;Plugin은 무시해주고 Manifests/Tuist 에 &amp;#39;Templates&amp;#39; 라는 이름의 Group을 하나 만듭니다. Templates는 Tuist에서 정해놓은 이름이기에 다르게 입력한다면 안에 템플릿이 있어도 인식되지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6FLGY/btrZg9LB1na/G8DAMw3zqHoD2MrbqJgpuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6FLGY/btrZg9LB1na/G8DAMw3zqHoD2MrbqJgpuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6FLGY/btrZg9LB1na/G8DAMw3zqHoD2MrbqJgpuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6FLGY%2FbtrZg9LB1na%2FG8DAMw3zqHoD2MrbqJgpuk%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;532&quot; height=&quot;362&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;새로운 템플릿을 만들 때는 Templates/&amp;lt;템플릿이름&amp;gt;/&amp;lt;템플릿이름&amp;gt;.swift 로 &amp;lt;템플릿이름&amp;gt;.swift에 해당 템플릿을 나타낼 수 있습니다.&lt;/p&gt;
&lt;p&gt;샘플을 하나 한 번 만들어보겠습니다.&lt;/p&gt;
&lt;p&gt;Templates/ 아래에 example/ 와 example.swift를 만들어줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHY4lO/btrZjJL1W8K/crWPkfYU6mt5yedQdARlIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHY4lO/btrZjJL1W8K/crWPkfYU6mt5yedQdARlIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHY4lO/btrZjJL1W8K/crWPkfYU6mt5yedQdARlIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHY4lO%2FbtrZjJL1W8K%2FcrWPkfYU6mt5yedQdARlIK%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;460&quot; height=&quot;232&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;Attribute&lt;/h2&gt;
&lt;p&gt;다름으로 example.swift안에 내용물을 채워주는데, 첫번째로는 Attribute라고 하는 해당 커맨드의 옵션을 받는 변수를 선언해줍니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// example.swift

import ProjectDescription

let nameAttribute = Template.Attribute.required(&amp;quot;name&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이렇게하면 scaffold를 사용해 템플릿을 불러올때 &lt;code&gt;tuist scaffold example --name Example&lt;/code&gt; 로 --&amp;lt;attribute명&amp;gt; &amp;lt;값&amp;gt; 으로 값을 전달 받을 수 있습니다.&lt;/p&gt;
&lt;p&gt;Attribute는 필수와 옵셔널 두 종류로 받을 수 있습니다.&lt;br&gt;옵셔널은 입력하지 않으면 default값을 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wo1w2/btrZkShrxmW/XM4S0mH5wF37ibREhBb5mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wo1w2/btrZkShrxmW/XM4S0mH5wF37ibREhBb5mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wo1w2/btrZkShrxmW/XM4S0mH5wF37ibREhBb5mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwo1w2%2FbtrZkShrxmW%2FXM4S0mH5wF37ibREhBb5mk%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;994&quot; height=&quot;106&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;106&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;Template&lt;/h2&gt;
&lt;p&gt;Tuist로 프로젝트나 플러그인을 만들 때처럼 템플릿도 정의합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// example.swift

import ProjectDescription

let nameAttribute = Template.Attribute.required(&amp;quot;name&amp;quot;)

let template = Template(
    description: &amp;lt;#String#&amp;gt;,
    attributes: &amp;lt;#[Template.Attribute]#&amp;gt;,
    items: &amp;lt;#[Template.Item]#&amp;gt;
)&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;description: 템플릿의 설명입니다. 평소에는 딱히 볼일은 없긴하지만 &lt;code&gt;tuist scaffold list&lt;/code&gt;를 하면 표시됩니다.&lt;/li&gt;
&lt;li&gt;attributes: scaffold 커맨드가 실행될때 받을 attribute리스트를 넣으면 됩니다.&lt;/li&gt;
&lt;li&gt;items: 여기에 넣어준 아이템을 기반으로 결과물이 나옵니다. 타입은 string, file, directory 3종류를 만들어 낼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Item&lt;/h2&gt;
&lt;p&gt;directory는 그냥 보면 디렉토리를 만드는건가, 싶지만 사실 디렉토리는 만드는 것이 아니라 sourcePath의 디렉토리를 내용물채로 복사해서 옮겨줍니다.&lt;br&gt;파일을 만드는것은 string과 file이 있는데 이 둘의 차이는 string은 직접 문자열 &amp;quot;&amp;quot; 안에 내용물을 작성하는 것이고, file은 &lt;a href=&quot;https://github.com/stencilproject/Stencil&quot;&gt;stencil&lt;/a&gt; 이라고하는 Swift를 위한 템플릿 언어를 사용하여 만드는 것입니다.&lt;/p&gt;
&lt;h3&gt;string&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JulyJ/btrZewUCwSh/z3Y4zQLKR7daUS0C16Pgc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JulyJ/btrZewUCwSh/z3Y4zQLKR7daUS0C16Pgc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JulyJ/btrZewUCwSh/z3Y4zQLKR7daUS0C16Pgc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJulyJ%2FbtrZewUCwSh%2Fz3Y4zQLKR7daUS0C16Pgc1%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;826&quot; height=&quot;168&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;파라미터가 받는 그대로, path안에 파일명까지 포함해서(ex. &lt;code&gt;Features/\(nameAttribute)Feature/Project.swift&lt;/code&gt;) 보내주시고, contents안에 해당 파일안의 내용물을 작성하시면 됩니다.&lt;/p&gt;
&lt;h3&gt;file&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W3OeE/btrZiP7nc7g/5zmboXRxh2aKCjfmMejPa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W3OeE/btrZiP7nc7g/5zmboXRxh2aKCjfmMejPa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W3OeE/btrZiP7nc7g/5zmboXRxh2aKCjfmMejPa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW3OeE%2FbtrZiP7nc7g%2F5zmboXRxh2aKCjfmMejPa1%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;884&quot; height=&quot;166&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위와 거의 동일하게 파일명까지 포함해서 path를 넘겨주고, templatePath를 받아 해당 경로에 있는 파일의 내용물을 넣습니다.&lt;br&gt;&lt;a href=&quot;https://github.com/tuist/tuist/blob/main/Sources/TuistScaffold/TemplateGenerator.swift#L122-L129&quot;&gt;코드&lt;/a&gt;를 뜯어보니 stencil만 따로 템플릿 렌더링 과정을 거치고 나머지 파일은 무슨 파일이든 읽힌 그대로 넘기는 듯 합니다.&lt;/p&gt;
&lt;p&gt;stencil에 대한 자세한건.. &lt;a href=&quot;https://github.com/stencilproject/Stencil&quot;&gt;stencil github&lt;/a&gt;에 여러모로 있으니 살펴봐주세요. Swift를 위한 Template 언어인 점을 제외하고는 뭔가 특별한 목적이 없으니 사실 별로 배울것도 없습니다. 변수, for, if 정도면 충분합니다.&lt;br&gt;여담으로 vscode extension에 stencil언어 extension도 있답니다.&lt;/p&gt;
&lt;h3&gt;directory&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mDbSy/btrZje6LwMY/onkK939HG1Gnm2gAj93JuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mDbSy/btrZje6LwMY/onkK939HG1Gnm2gAj93JuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mDbSy/btrZje6LwMY/onkK939HG1Gnm2gAj93JuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmDbSy%2FbtrZje6LwMY%2FonkK939HG1Gnm2gAj93JuK%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;906&quot; height=&quot;160&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;directory는 directory를 복사해서 옮겨줍니다. sourcePath에 받는 경로에 있는 directory를 path에 그대로 옮겨주는겁니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// example.swift
import ProjectDescription

let nameAttribute: Template.Attribute = .required(&amp;quot;name&amp;quot;)

let template = Template(
    description: &amp;quot;A template for new module&amp;quot;,
    attributes: [
        nameAttribute,
    ],
    items: [
        .string(
            path: &amp;quot;Source.swift&amp;quot;,
            contents: &amp;quot;// Source contents of name \(nameAttribute)&amp;quot;
        ),
        .file(
            path: &amp;quot;Project.swift&amp;quot;,
            templatePath: &amp;quot;project.stencil&amp;quot;
        ),
        .directory(
            path: &amp;quot;destination&amp;quot;,
            sourcePath: &amp;quot;source&amp;quot;
        )
    ]
)&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;{# project.stencil #}

import ProjectDescription
import ProjectDescriptionHelpers

let project = Project.makeModule(
    name: &amp;quot;{{ name }}&amp;quot;,
    product: .staticLibrary
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;우선 간단하게 만들어보았습니다. directory같은경우 Template/example/ 안에 source/ 를 만들고 그 안에 Second.swift에 대충 내용을 채워넣었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sjqKd/btrZkS9GBiN/rUJnoFwgJ0V2rvu01Imw30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sjqKd/btrZkS9GBiN/rUJnoFwgJ0V2rvu01Imw30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sjqKd/btrZkS9GBiN/rUJnoFwgJ0V2rvu01Imw30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsjqKd%2FbtrZkS9GBiN%2FrUJnoFwgJ0V2rvu01Imw30%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;960&quot; height=&quot;194&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이렇게 템플릿을 만들고 나서 &lt;code&gt;tuist edit&lt;/code&gt;을 종료해줍니다. 그후 &lt;code&gt;tuist scaffold list&lt;/code&gt;를 하고 아래처럼 나온다면 템플릿 생성에 성공한것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yViPx/btrZg8fdqov/rJei3CMjEMK6WSJkdpeggk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yViPx/btrZg8fdqov/rJei3CMjEMK6WSJkdpeggk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yViPx/btrZg8fdqov/rJei3CMjEMK6WSJkdpeggk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyViPx%2FbtrZg8fdqov%2FrJei3CMjEMK6WSJkdpeggk%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;618&quot; height=&quot;276&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;그리고 터미널에 &lt;code&gt;tuist scaffold example --name Test&lt;/code&gt;를 쳐봅니다! 아래와 같이 나온다면 성공한겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Colsj/btrZkRXhGal/CRIJ3OXMCVjWyqmTYz0ki0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Colsj/btrZkRXhGal/CRIJ3OXMCVjWyqmTYz0ki0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Colsj/btrZkRXhGal/CRIJ3OXMCVjWyqmTYz0ki0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FColsj%2FbtrZkRXhGal%2FCRIJ3OXMCVjWyqmTYz0ki0%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;706&quot; height=&quot;86&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;그리고 파인더를 슬쩍보면 위와같이 되어있을겁니다. (Project.swift는 내용이 덮어씌워졌습니다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/camoeP/btrZi7NAYz4/xhdR7LwWsFRQxLkw0zYlTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/camoeP/btrZi7NAYz4/xhdR7LwWsFRQxLkw0zYlTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/camoeP/btrZi7NAYz4/xhdR7LwWsFRQxLkw0zYlTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcamoeP%2FbtrZi7NAYz4%2FxhdR7LwWsFRQxLkw0zYlTK%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;450&quot; height=&quot;330&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;구조가 깔끔하지 않으니 겸사겸사 path를 사용해봅시다.&lt;br&gt;파인더에서 루트에 Projects/ 폴더를 하나 만들고 &lt;code&gt;tuist scaffold example --name Test --path Projects/&lt;/code&gt; 를 실행해봅시다. 그러면 Projects/ 아래에 만들어집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VtXud/btrZkypWp7G/PTNgskIVniGMGdAquJBUpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VtXud/btrZkypWp7G/PTNgskIVniGMGdAquJBUpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VtXud/btrZkypWp7G/PTNgskIVniGMGdAquJBUpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVtXud%2FbtrZkypWp7G%2FPTNgskIVniGMGdAquJBUpk%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;928&quot; height=&quot;276&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;그리고 이런 한 프로젝트안의 모듈용 템플릿이 아닌 글로벌로 사용하고 싶다면 $HOME/.tuist/Versions/\(Version)/Templates 안에 템플릿을 위치시켜두면 됩니다.&lt;br&gt;$HOME/.tuist/Versions/\(Version)/Templates/ 안에 swiftui용 프로젝트 템플릿도 있으니 한 번 슬쩍 뜯어보는것도 괜찮을거같습니다.&lt;/p&gt;
&lt;p&gt;해당 글에서 작성한 내용은 &lt;a href=&quot;https://github.com/baekteun/Scaffold-Sample&quot;&gt;깃허브&lt;/a&gt;에서도 볼 수 있습니다.&lt;/p&gt;
&lt;h2&gt;References By&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.tuist.io/commands/scaffold/&quot;&gt;https://docs.tuist.io/commands/scaffold/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tuist</category>
      <category>Module</category>
      <category>Scaffold</category>
      <category>Stencil</category>
      <category>Swift</category>
      <category>template</category>
      <category>Tuist</category>
      <category>자동화</category>
      <category>템플릿</category>
      <author>baegteun</author>
      <guid isPermaLink="true">https://baegteun.tistory.com/12</guid>
      <comments>https://baegteun.tistory.com/12#entry12comment</comments>
      <pubDate>Wed, 15 Feb 2023 00:16:25 +0900</pubDate>
    </item>
    <item>
      <title>Tuist 모듈화하기 - Modular Architecture 설계하기</title>
      <link>https://baegteun.tistory.com/11</link>
      <description>&lt;p&gt;이 글은 Tuist 버전 3.15.0을 기준으로 작성되었습니다.&lt;/p&gt;
&lt;p&gt;전체 소스코드는 &lt;a href=&quot;https://github.com/baekteun/Tuist_Modular_Template&quot;&gt;https://github.com/baekteun/Tuist_Modular_Template&lt;/a&gt; 에 공개 + 템플릿 으로 사용가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;스타 한번씩 눌러주시면 매우 감사합니다..!&lt;/del&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;레이어&lt;/li&gt;
&lt;li&gt;모듈화&lt;ul&gt;
&lt;li&gt;Micro Feature&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Tuist 설계&lt;ul&gt;
&lt;li&gt;Plugin&lt;/li&gt;
&lt;li&gt;Project 생성&lt;/li&gt;
&lt;li&gt;자동화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;레이어&lt;/h1&gt;
&lt;p&gt;모듈보다 더 큰 관점에서 레이러를 먼저 분리해보도록 하겠습니다.&lt;br&gt;Feature, Domain, Core, Shared로 아래와 같은 기준으로 분리하였습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Feature&lt;ul&gt;
&lt;li&gt;Presentation 부분&lt;/li&gt;
&lt;li&gt;ex) AuthFeature, ProfileFeature&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Domain&lt;ul&gt;
&lt;li&gt;Business Logic 부분&lt;/li&gt;
&lt;li&gt;ex) AuthDomain, ProfileDomain&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Core&lt;ul&gt;
&lt;li&gt;앱의 비즈니스를 포함하지 않고 순수 기능성 모듈이 위치한 레이어&lt;/li&gt;
&lt;li&gt;ex) NetworkingModule, DatabaseModule&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Shared&lt;ul&gt;
&lt;li&gt;모든 계층에서 사용 가능한 모듈&lt;/li&gt;
&lt;li&gt;더 넓은 의미의 공통적&lt;/li&gt;
&lt;li&gt;ex) UtilityModule, LoggingModule&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3iOBt/btrVk5dH7Vx/66Zj7FLkk8H3CJu9YNXFX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3iOBt/btrVk5dH7Vx/66Zj7FLkk8H3CJu9YNXFX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3iOBt/btrVk5dH7Vx/66Zj7FLkk8H3CJu9YNXFX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3iOBt%2FbtrVk5dH7Vx%2F66Zj7FLkk8H3CJu9YNXFX1%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;1354&quot; height=&quot;212&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;모듈화&lt;/h1&gt;
&lt;p&gt;모듈을 분리하는 기준은 가장 크게 &lt;code&gt;도메인 관점&lt;/code&gt;에 따른 분리와 &lt;code&gt;재사용 관점&lt;/code&gt;에 따라 분리할 수 있습니다.&lt;/p&gt;
&lt;p&gt;Feature와 Domain은 도메인 관점에 따라 분리하고, Core와 Shared는 재사용 관점에 따라 분리하였습니다.&lt;/p&gt;
&lt;h3&gt;Micro Feature&lt;/h3&gt;
&lt;p&gt;Tuist의 공식 문서에는 &lt;a href=&quot;https://docs.tuist.io/building-at-scale/microfeatures/#what-is-a-%C2%B5feature&quot;&gt;Micro Feature&lt;/a&gt;에 대한 문서가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw73Sm/btrVeU6tsBs/dnN2scs5QblmhE38KWfxQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw73Sm/btrVeU6tsBs/dnN2scs5QblmhE38KWfxQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw73Sm/btrVeU6tsBs/dnN2scs5QblmhE38KWfxQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw73Sm%2FbtrVeU6tsBs%2FdnN2scs5QblmhE38KWfxQ1%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;1102&quot; height=&quot;838&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h5&gt;&lt;a href=&quot;https://docs.tuist.io/building-at-scale/microfeatures/#what-is-a-%C2%B5feature&quot;&gt;https://docs.tuist.io/building-at-scale/microfeatures/#what-is-a-µfeature&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Micro Feature는 확장을 가능하게 하고, 빌드 및 테스트 주기를 최적화하며, 팀의 모범 사례를 보장하기 위해 Apple OS 애플리케이션을 구성하는 아키텍처 접근 방식입니다.&lt;br&gt;이 방식을 따라서 각 모듈을 구성할 것입니다.&lt;/p&gt;
&lt;h1&gt;Tuist 설계&lt;/h1&gt;
&lt;h3&gt;Plguin&lt;/h3&gt;
&lt;p&gt;Tuist관리도 용이하게 하기 위해서 Plugin도 분리해주겠습니다.&lt;br&gt;Plugin은&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configurtaion 관리를 위한 ConfigurationPlugin&lt;/li&gt;
&lt;li&gt;디펜던시 관리를 위한 DependencyPlugin&lt;/li&gt;
&lt;li&gt;프로젝트 환경을 관리하기 위한 EnvironmentPlugin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;으로 3종류의 Plugin을 만들겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vxivb/btrVo4e4SeH/T1un2hNSYWqbo1lITRJJgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vxivb/btrVo4e4SeH/T1un2hNSYWqbo1lITRJJgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vxivb/btrVo4e4SeH/T1un2hNSYWqbo1lITRJJgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvxivb%2FbtrVo4e4SeH%2FT1un2hNSYWqbo1lITRJJgk%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;522&quot; height=&quot;570&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/baekteun/Tuist_Modular_Template/tree/master/Plugin&quot;&gt;여기&lt;/a&gt;에서 더 자세한 내용을 볼 수 있습니다!&lt;/p&gt;
&lt;h3&gt;Project 생성&lt;/h3&gt;
&lt;p&gt;Project.swift에서 모듈을 만들때 쓸 함수를 만들겠습니다.&lt;br&gt;우선 CI일때를 처리 하기 위해&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let isCI = (ProcessInfo.processInfo.environment[&amp;quot;TUIST_CI&amp;quot;] ?? &amp;quot;0&amp;quot;) == &amp;quot;1&amp;quot; ? true : false&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;를 위에 선언해놓겠습니다.&lt;br&gt;기본적으로 빌드는하면 SwiftLint를 돌리게 했는데, CI/CD를 할때는 SwiftLint를 돌릴 필요가 없기에 이후에 분기점을 둬서 CI일때는 실행하지 않도록 하기 위해서입니다.&lt;br&gt;또한 앞서 말했듯 Micro Feature를 적용할 것인데 이를 편하게 하기 위해&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public enum MicroFeatureTarget {
    case interface
    case testing
    case unitTest
    case uiTest
    case demo
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;를 선언해놓겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static func makeModule(
    name: String,
    platform: Platform = env.platform,
    product: Product,
    targets: Set&amp;lt;MicroFeatureTarget&amp;gt;,
    packages: [Package] = [],
    externalDependencies: [TargetDependency] = [],
    internalDependencies: [TargetDependency] = [],
    interfaceDependencies: [TargetDependency] = [],
    testingDependencies: [TargetDependency] = [],
    unitTestDependencies: [TargetDependency] = [],
    uiTestDependencies: [TargetDependency] = [],
    demoDependencies: [TargetDependency] = [],
    sources: SourceFilesList = .sources,
    resources: ResourceFileElements? = nil,
    settings: SettingsDictionary = [:],
    additionalPlistRows: [String: ProjectDescription.InfoPlist.Value] = [:],
    additionalFiles: [FileElement] = []
) -&amp;gt; Project&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Project의 extension으로 makeModule 함수를 만들겠습니다.&lt;br&gt;각 파라미터의 의미는&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;name: 모듈 이름&lt;/li&gt;
&lt;li&gt;platform: 플랫폼&lt;/li&gt;
&lt;li&gt;product: 모듈의 product type (framework, staticFramework 등)&lt;/li&gt;
&lt;li&gt;targets: 사용할 Micro Feature (Interface, testing, unitTests, uiTests, demo)&lt;/li&gt;
&lt;li&gt;packages: 패키지들&lt;/li&gt;
&lt;li&gt;externalDependencies: 외부 디펜던시&lt;/li&gt;
&lt;li&gt;internalDependencies: 내부 디펜던시&lt;/li&gt;
&lt;li&gt;interfaceDependencies: interface Target모듈의 디펜던시&lt;/li&gt;
&lt;li&gt;testingDependencies: testing Target모듈의 디펜던시&lt;/li&gt;
&lt;li&gt;unitTestDependencies: unitTests Target모듈의 디펜던시&lt;/li&gt;
&lt;li&gt;uiTestDependencies: uiTests Target모듈의 디펜던시&lt;/li&gt;
&lt;li&gt;demoDependencies: demo Target 모듈의 디펜던시&lt;/li&gt;
&lt;li&gt;sources: 소스폴더의 경로&lt;/li&gt;
&lt;li&gt;resources: 리소스폴더의 경로&lt;/li&gt;
&lt;li&gt;settings: 모듈의 빌드 세팅&lt;/li&gt;
&lt;li&gt;additionalPlistRows: info plist의 추가 Row&lt;/li&gt;
&lt;li&gt;additionalFiles: Xcode에서 추가적으로 인식할 파일(ex README.md)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;입니다.&lt;br&gt;이후부터 Project를 구성할것입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let scripts: [TargetScript] = isCI ? [] : [.swiftLint]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;앞서서 했던 isCI를 사용해서 scripts를 정의해줍니다. CI라면 없이, CI가 아니라면 SwiftLint를 포함시켜거 스크립트를 돌립니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let ldFlagsSettings: SettingsDictionary = product == .framework ?
[&amp;quot;OTHER_LDFLAGS&amp;quot;: .string(&amp;quot;$(inherited) -all_load&amp;quot;)] :
[&amp;quot;OTHER_LDFLAGS&amp;quot;: .string(&amp;quot;$(inherited)&amp;quot;)]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;product가 framework (dynamic framework)라면 staticLibrary에 작성된 모든 멤버를 적재하도록 링커에 -all_load 옵션을 달아줍니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import ProjectDescription

public extension SettingsDictionary {
    static let codeSign = SettingsDictionary()
        .codeSignIdentityAppleDevelopment()
        .automaticCodeSigning(devTeam: &amp;quot;\(Apple Team Identifier)&amp;quot;)
}&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;let settings: Settings = .settings(
    base: env.baseSetting
        .merging(.codeSign)
        .merging(settings)
        .merging(ldFlagsSettings),
    configurations: [
        .debug(name: .dev, xcconfig: .relativeToXCConfig(type: .dev, name: name)),
        .debug(name: .stage, xcconfig: .relativeToXCConfig(type: .stage, name: name)),
        .release(name: .prod, xcconfig: .relativeToXCConfig(type: .prod, name: name))
    ],
    defaultSettings: .recommended
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;프로젝트의 세팅에 사용할 변수입니다.&lt;br&gt;baseSetting에 파라미터로 받은 settigns와 방금 위에만든 ldFlagsSettings, 그리고 따로 extension으로 뺴놓은 codeSign merging 해줍니다.&lt;br&gt;그리고 configuration은 dev, stage, prod로 분리해놓고, defaultSettings는 recommended로 해놓았습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var allTargets: [Target] = []
var dependencies = internalDependencies + externalDependencies

// MARK: - Interface
if targets.contains(.interface) {
    dependencies.append(.target(name: &amp;quot;\(name)Interface&amp;quot;))
    allTargets.append(
        Target(
            name: &amp;quot;\(name)Interface&amp;quot;,
            platform: platform,
            product: .framework,
            bundleId: &amp;quot;\(env.organizationName).\(name)Interface&amp;quot;,
            deploymentTarget: env.deploymentTarget,
            infoPlist: .default,
            sources: .interface,
            scripts: scripts,
            dependencies: interfaceDependencies,
            additionalFiles: additionalFiles
        )
    )
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이후부터는 Micro Feature를 위한 Target을 설정하는 건데 위와 거의 비슷한 흐름을 가집니다.&lt;br&gt;모든 Feature를 거치고나면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let schemes: [Scheme] = targets.contains(.demo) ?
[.makeScheme(target: .dev, name: name), .makeDemoScheme(target: .dev, name: name)] :
[.makeScheme(target: .dev, name: name)]

...

extension Scheme {
    static func makeScheme(target: ConfigurationName, name: String) -&amp;gt; Scheme {
        return Scheme(
            name: name,
            shared: true,
            buildAction: .buildAction(targets: [&amp;quot;\(name)&amp;quot;]),
            testAction: .targets(
                [&amp;quot;\(name)Tests&amp;quot;],
                configuration: target,
                options: .options(coverage: true, codeCoverageTargets: [&amp;quot;\(name)&amp;quot;])
            ),
            runAction: .runAction(configuration: target),
            archiveAction: .archiveAction(configuration: target),
            profileAction: .profileAction(configuration: target),
            analyzeAction: .analyzeAction(configuration: target)
        )
    }
    static func makeDemoScheme(target: ConfigurationName, name: String) -&amp;gt; Scheme {
        return Scheme(
            name: name,
            shared: true,
            buildAction: .buildAction(targets: [&amp;quot;\(name)DemoApp&amp;quot;]),
            testAction: .targets(
                [&amp;quot;\(name)Tests&amp;quot;],
                configuration: target,
                options: .options(coverage: true, codeCoverageTargets: [&amp;quot;\(name)DemoApp&amp;quot;])
            ),
            runAction: .runAction(configuration: target),
            archiveAction: .archiveAction(configuration: target),
            profileAction: .profileAction(configuration: target),
            analyzeAction: .analyzeAction(configuration: target)
        )
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;scheme을 만듭니다.&lt;br&gt;그리고 마지막으로&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;return Project(
    name: name,
    organizationName: env.organizationName,
    packages: packages,
    settings: settings,
    targets: allTargets,
    schemes: schemes
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Project를 만들어서 리턴합니다.&lt;/p&gt;
&lt;p&gt;모듈을 만들때 &lt;code&gt;Project.makeModule&lt;/code&gt; 을 사용해서 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;다만, App 모듈을 만들때는 따로 Target을 만들지 않고, Scheme만 dev, stage, prod 3개 만들어주겠습니다.&lt;/p&gt;
&lt;h3&gt;자동화&lt;/h3&gt;
&lt;p&gt;프로젝트를 만드는 방법은 만들었지만 실제로 만들게 된다면 모듈 하나를 복사하고 이름을 바꾸고 필요없는 Target은 지우고 DependencyPlugin에 경로 넣어주고 하는 등 귀찮은 과정들이 좀 있습니다.&lt;br&gt;이런 반복작업같은거 조금 자동화를 시켜보려합니다.&lt;br&gt;우선 모듈 생성을 자동화해야할 때 입력해야 하는 것은 레이어(Feature, Domain, Core, Shared), 모듈 이름, Micro Feature 중 포함하는 Target을 선택하는 것을 입력받는 것이 필요합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;enum LayerType: String {
    case features = &amp;quot;Features&amp;quot;
    case services = &amp;quot;Services&amp;quot;
    case core = &amp;quot;Core&amp;quot;
    case shared = &amp;quot;Shared&amp;quot;
}

print(&amp;quot;Enter layer name\n(Features | Services | Core | Shared)&amp;quot;, terminator: &amp;quot; : &amp;quot;)
let layerInput = readLine()
guard 
    let layerInput, 
    !layerInput.isEmpty ,
    let layerUnwrapping = LayerType(rawValue: layerInput)
else {
    print(&amp;quot;Layer is empty or invalid&amp;quot;)
    exit(1)
}
let layer = layerUnwrapping
print(&amp;quot;Layer: \(layer.rawValue)\n&amp;quot;)

print(&amp;quot;Enter module name&amp;quot;, terminator: &amp;quot; : &amp;quot;)
let moduleInput = readLine()
guard let moduleNameUnwrapping = moduleInput, !moduleNameUnwrapping.isEmpty else {
    print(&amp;quot;Module name is empty&amp;quot;)
    exit(1)
}
var moduleName = moduleNameUnwrapping
print(&amp;quot;Module name: \(moduleName)\n&amp;quot;)

print(&amp;quot;This module has a &amp;#39;Interface&amp;#39; Target? (y\\n, default = n)&amp;quot;, terminator: &amp;quot; : &amp;quot;)
let hasInterface = readLine()?.lowercased() == &amp;quot;y&amp;quot;

print(&amp;quot;This module has a &amp;#39;Testing&amp;#39; Target? (y\\n, default = n)&amp;quot;, terminator: &amp;quot; : &amp;quot;)
let hasTesting = readLine()?.lowercased() == &amp;quot;y&amp;quot;

print(&amp;quot;This module has a &amp;#39;UnitTests&amp;#39; Target? (y\\n, default = n)&amp;quot;, terminator: &amp;quot; : &amp;quot;)
let hasUnitTests = readLine()?.lowercased() == &amp;quot;y&amp;quot;

print(&amp;quot;This module has a &amp;#39;UITests&amp;#39; Target? (y\\n, default = n)&amp;quot;, terminator: &amp;quot; : &amp;quot;)
let hasUITests = readLine()?.lowercased() == &amp;quot;y&amp;quot;

print(&amp;quot;This module has a &amp;#39;Demo&amp;#39; Target? (y\\n, default = n)&amp;quot;, terminator: &amp;quot; : &amp;quot;)
let hasDemo = readLine()?.lowercased() == &amp;quot;y&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;시간을 들여서 만든건 아니라서 지금 보기에는 상당히.. 더러운거같습니다만. 일단은 넘어,,갑시다..!&lt;br&gt;이후에 enum ModulePaths에 새로운 모듈의 경로를 추가해주고, xcconfig를 만들고, 각 Micro Feature들의 모듈을 경로에 등록 및 파일을 만들어줍니다. 이후 마지막으로 Project.swift파일을 만들어줄것입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func registerModuleDependency() {
    registerModulePaths()
    makeProjectDirectory()
    registerXCConfig()
    registerMicroTarget(target: .sources)
    var targetString = &amp;quot;[&amp;quot;
    if hasInterface {
        registerMicroTarget(target: .interface)
        makeScaffold(target: .interface)
        targetString += &amp;quot;.\(MicroTargetType.interface), &amp;quot;
    }
    if hasTesting {
        registerMicroTarget(target: .testing)
        makeScaffold(target: .testing)
        targetString += &amp;quot;.\(MicroTargetType.testing), &amp;quot;
    }
    if hasUnitTests {
        makeScaffold(target: .unitTest)
        targetString += &amp;quot;.\(MicroTargetType.unitTest), &amp;quot;
    }
    if hasUITests {
        makeScaffold(target: .uiTest)
        targetString += &amp;quot;.\(MicroTargetType.uiTest), &amp;quot;
    }
    if hasDemo {
        makeScaffold(target: .demo)
        targetString += &amp;quot;.\(MicroTargetType.demo), &amp;quot;
    }
    if targetString.hasSuffix(&amp;quot;, &amp;quot;) {
        targetString.removeLast(2)
    }
    targetString += &amp;quot;]&amp;quot;
    makeProjectSwift(targetString: targetString)
    makeProjectScaffold(targetString: targetString)
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;디자인 패턴을 쓰고 싶었지만,, 단일 파일이라 미묘하게 부담스러워서 하드코딩을 저질러버렸다.&lt;br&gt;전체 코드는 &lt;a href=&quot;https://github.com/baekteun/Tuist_Modular_Template/blob/master/Scripts/GenerateModule.swift&quot;&gt;여기&lt;/a&gt;에서 볼 수 있습니다!&lt;br&gt;+ 모듈의 Target파일을 생성할 때 tuist scaffold를 사용했는데, 이에 관한건 나중에 작성해보겠습니다..!&lt;/p&gt;
&lt;p&gt;그리고 이 파일을 실행하는 것은 프로젝트 Root에 &lt;code&gt;Makefile&lt;/code&gt;을 만들어서 명령어를 만들어놓겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...


module:
    swift Scripts/GenerateModule.swift

...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;으로 &lt;code&gt;make module&lt;/code&gt; 을 치면 모듈을 만들 수 있게 해줍니다.&lt;/p&gt;
&lt;p&gt;우선 여기까지 마치고, 더 자세한 내용은 &lt;a href=&quot;https://github.com/baekteun/Tuist_Modular_Template&quot;&gt;https://github.com/baekteun/Tuist_Modular_Template&lt;/a&gt; 에서 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;+ 해당 Repository의 코드는 현재 포스트와 일부 달라졌습니다&lt;br&gt;+ &lt;a href=&quot;https://baegteun.tistory.com/15&quot;&gt;https://baegteun.tistory.com/15&lt;/a&gt; 에서 조금 더 개선한 버전을 볼 수 있습니다&lt;/p&gt;
&lt;p&gt;References By&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://minsone.github.io&quot;&gt;https://minsone.github.io&lt;/a&gt;&lt;br&gt;SyncSwift 2022 - Modular Architecture 시작하기&lt;br&gt;Let&amp;#39;Swift 2022 - Modular Architecture /w Tuist&lt;br&gt;&lt;a href=&quot;https://docs.tuist.io/building-at-scale/microfeatures/&quot;&gt;https://docs.tuist.io/building-at-scale/microfeatures/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tuist</category>
      <category>Architecture</category>
      <category>ios</category>
      <category>Micro Feature</category>
      <category>Modular Architecture</category>
      <category>Swift</category>
      <category>Tuist</category>
      <category>Tuist 모듈화</category>
      <category>uFeature</category>
      <category>모듈화</category>
      <category>아키텍쳐</category>
      <author>baegteun</author>
      <guid isPermaLink="true">https://baegteun.tistory.com/11</guid>
      <comments>https://baegteun.tistory.com/11#entry11comment</comments>
      <pubDate>Thu, 5 Jan 2023 01:12:53 +0900</pubDate>
    </item>
    <item>
      <title>Tuist 사용법 - 7. Configuration + XCConfig</title>
      <link>https://baegteun.tistory.com/10</link>
      <description>&lt;p&gt;이 글은 Tuist 버전 3.14.0 기준으로 작성되었습니다.&lt;/p&gt;
&lt;h1&gt;Configuration?&lt;/h1&gt;
&lt;p&gt;개발, 스테이지, 릴리즈 등의 배포환경을 분리할 때 Build Configuration을 활용합니다.&lt;/p&gt;
&lt;h1&gt;XCConfig?&lt;/h1&gt;
&lt;p&gt;Xcode 프로젝트 빌드의 설정을 구성하기 위한 파일입니다.&lt;/p&gt;
&lt;h1&gt;Configuration 설정법&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;Settings&lt;/code&gt; 를 만들 때 base, configurations, defaultSettings 에서&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wH9NH/btrUAczbWH5/MJKb0aYTHTQLWAEOGuTF01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wH9NH/btrUAczbWH5/MJKb0aYTHTQLWAEOGuTF01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wH9NH/btrUAczbWH5/MJKb0aYTHTQLWAEOGuTF01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwH9NH%2FbtrUAczbWH5%2FMJKb0aYTHTQLWAEOGuTF01%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;688&quot; height=&quot;268&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;configurations에 값을 넣어줘서 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnH39K/btrUyCdYGmW/5EaXqakBaAK9gAxZluNQg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnH39K/btrUyCdYGmW/5EaXqakBaAK9gAxZluNQg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnH39K/btrUyCdYGmW/5EaXqakBaAK9gAxZluNQg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnH39K%2FbtrUyCdYGmW%2F5EaXqakBaAK9gAxZluNQg0%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;574&quot; height=&quot;198&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;configuration은 debug, release중 하나로 만들 수 있고 configuration별 이름을 정해줄 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ky4zU/btrUEwxhtb6/bXHZrtDCvMjByBGCkpEA2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ky4zU/btrUEwxhtb6/bXHZrtDCvMjByBGCkpEA2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ky4zU/btrUEwxhtb6/bXHZrtDCvMjByBGCkpEA2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fky4zU%2FbtrUEwxhtb6%2FbXHZrtDCvMjByBGCkpEA2k%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;1392&quot; height=&quot;60&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;configuration을 만들 때 settings와 xcconfig(경로)를 지정해줄 수 있습니다.&lt;/p&gt;
&lt;h1&gt;활용&lt;/h1&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;configuration을 설정할 때 이름을 정해줄 수 있는데, &lt;code&gt;ProjectDeployTarget&lt;/code&gt; 이라는 enum을 만들고 배포환경별로 case를 만들어줍니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public enum ProjectDeployTarget: String {
    case dev = &amp;quot;DEV&amp;quot;
    case stage = &amp;quot;STAGE&amp;quot;
    case prod = &amp;quot;PROD&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;configuration을 설정할때 &lt;code&gt;name&lt;/code&gt;은 ConfigurationName 타입입니다. 그렇기에 ConfigurationName에 extension으로 각 배포환경을 선언해놓습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public extension ConfigurationName {
    static var dev: ConfigurationName { configuration(ProjectDeployTarget.dev.rawValue) }
    static var stage: ConfigurationName { configuration(ProjectDeployTarget.stage.rawValue) }
    static var prod: ConfigurationName { configuration(ProjectDeployTarget.prod.rawValue) }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이렇게 해놓으면 configuration을 설정할 때 아래처럼 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5V4Ph/btrUI0RJoCH/9YScsfFlGKGAyYkDxNLKwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5V4Ph/btrUI0RJoCH/9YScsfFlGKGAyYkDxNLKwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5V4Ph/btrUI0RJoCH/9YScsfFlGKGAyYkDxNLKwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5V4Ph%2FbtrUI0RJoCH%2F9YScsfFlGKGAyYkDxNLKwK%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;522&quot; height=&quot;252&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;XCConfig&lt;/h2&gt;
&lt;p&gt;configuration마다 다른 빌드 설정을 주기위해 XCConfig를 사용해보겠습니다.&lt;/p&gt;
&lt;p&gt;저는 프로젝트의 루트 디렉토리에 XCConfig/ 디렉토리를 만들고, 이 안에 (모듈이름)/(configuration).xcconfig 파일들을 넣어주겠습니다.&lt;/p&gt;
&lt;p&gt;그리고 XCConfig/ 바로 아래에 &lt;code&gt;Shared.xcconfig&lt;/code&gt; 파일을 만들어주겠습니다. 이 파일 안에는&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;OTHER_SWIFT_FLAGS[config=DEV][sdk=*] = $(inherited) -DDEV
OTHER_SWIFT_FLAGS[config=STAGE][sdk=*] = $(inherited) -DSTAGE
OTHER_SWIFT_FLAGS[config=PROD][sdk=*] = $(inherited) -DPROD&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;로 내용을 넣어주겠습니다.&lt;/p&gt;
&lt;p&gt;그리고 만들어질 모든 .xcconfig 에는&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;quot;../Shared.xcconfig&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;으로 Shared를 include 하도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;만드는 이유는 각 환경에 맞게 &lt;code&gt;OTHER_SWIFT_FLAGS&lt;/code&gt; 값을 추가하여&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#if STAGE&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;같이 MACRO로 분기할 수 있도록 하기 위해서입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxRAmX/btrUCjMtxW5/t2bKLQV6T533fuJ0I4GAEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxRAmX/btrUCjMtxW5/t2bKLQV6T533fuJ0I4GAEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxRAmX/btrUCjMtxW5/t2bKLQV6T533fuJ0I4GAEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxRAmX%2FbtrUCjMtxW5%2Ft2bKLQV6T533fuJ0I4GAEk%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;1424&quot; height=&quot;152&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;xcconfig 파일들을 만들고 나서는 configuration을 설정할 때 &lt;code&gt;xcconfig:&lt;/code&gt; 에 xcconfig 파일의 경로를 넘겨줘서 xcconfig 설정을 할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;Additional&lt;/h2&gt;
&lt;p&gt;Configuration 작업을 한 이후에 Dependencies.swift에 디펜던시를 추가하고 generate를 하게 되면&lt;br&gt;&lt;code&gt;Warning: The project &amp;#39;Lottie&amp;#39; has missing or mismatching configurations. It has [Debug (debug), Release (release)], other projects have [DEV (debug), STATE (debug), PROD (release)]&lt;/code&gt;&lt;br&gt;라는 메시지가 출력될겁니다. 이는 외부에서 불러온 라이브러리의 Configuration은 저희가 구성한 Configuration과 달리 Debug와 Release로 구성되어있기 때문입니다. 이때는 Dependencies.swift에 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import ProjectDescription
import ProjectDescriptionHelpers

let dependencies = Dependencies(
    swiftPackageManager: SwiftPackageManagerDependencies(
        [
            .remote(url: &amp;quot;https://github.com/airbnb/lottie-ios.git&amp;quot;, requirement: .upToNextMajor(from: &amp;quot;3.5.0&amp;quot;)),
            .remote(url: &amp;quot;https://github.com/uber/needle.git&amp;quot;, requirement: .upToNextMajor(from: &amp;quot;0.19.0&amp;quot;)),
            .remote(url: &amp;quot;https://github.com/Moya/Moya.git&amp;quot;, requirement: .upToNextMajor(from: &amp;quot;15.0.3&amp;quot;)),
            .remote(url: &amp;quot;https://github.com/Quick/Nimble.git&amp;quot;, requirement: .upToNextMajor(from: &amp;quot;10.0.0&amp;quot;)),
            .remote(url: &amp;quot;https://github.com/Quick/Quick.git&amp;quot;, requirement: .upToNextMajor(from: &amp;quot;5.0.0&amp;quot;))
        ],
        baseSettings: .settings(
            configurations: [
                .debug(name: .dev),
                .debug(name: .stage),
                .release(name: .prod)
            ]
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 baseSettings에 configuration을 설정해주면 됩니다.&lt;/p&gt;
&lt;h2&gt;References By&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://minsone.github.io/mac/ios/ios-project-generate-with-tuist-2&quot;&gt;https://minsone.github.io/mac/ios/ios-project-generate-with-tuist-2&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://github.com/mephrine/TuistRxTemplate&quot;&gt;https://github.com/mephrine/TuistRxTemplate&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tuist</category>
      <category>configuration</category>
      <category>Debug</category>
      <category>dev</category>
      <category>ios</category>
      <category>Prod</category>
      <category>release</category>
      <category>Stage</category>
      <category>Swift</category>
      <category>Tuist</category>
      <category>XCConfig</category>
      <author>baegteun</author>
      <guid isPermaLink="true">https://baegteun.tistory.com/10</guid>
      <comments>https://baegteun.tistory.com/10#entry10comment</comments>
      <pubDate>Tue, 27 Dec 2022 14:54:09 +0900</pubDate>
    </item>
    <item>
      <title>[iOS - Swift] Swift의 메모리 구조</title>
      <link>https://baegteun.tistory.com/9</link>
      <description>&lt;p&gt;프로그램이 실행되면 OS가 메모리 공간을 할당해주는데 Code, Data, Heap, Stack 4개로 나뉜다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74440939/205225291-03abc2a2-c474-4f69-ba2c-a7dc3aec3e09.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Code&lt;/h1&gt;
&lt;p&gt;우리가 작성한 소스코드가 저장되는 영역이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;소스코드를 &lt;strong&gt;기계어 형태 (0, 1)&lt;/strong&gt; 로 변경하여 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;컴파일 타임&lt;/strong&gt;에 결정되고, 중간에 코드가 변경되면 안되니 &lt;strong&gt;Read-Only&lt;/strong&gt;로 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Data&lt;/h1&gt;
&lt;p&gt;이름 그대로 데이터를 저장하는 영역이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;전역 변수, static 변수&lt;/strong&gt;가 저장된다.&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum Constant {
  static let apiKey = &amp;quot;&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;var name = &amp;quot;baegteun&amp;quot;&lt;br&gt;var number = 18&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
- 프로그램의 시작과 동시에 할당되고, 프로그램이 **종료되어야 해제**된다.
- 실행 도중 값이 변경될 수도 있으니 **Read-Write**로 저장된다.


# Heap
데이터 크기가 확실하지 않은 **가변적인 데이터 타입**들이 저장되는 영역이다.

- 4개의 영역중 유일하게 **런타임**에 결정된다.
- Heap영역에는 임의의 주소값에 해당 메모리를 할당한다.
  - 임의의 값에 해당하기 때문에 주소를 찾아가는 것에 대한 오버헤드가 크다.
- 메모리 할당, 해제가 하나의 명령어로 처리되지 않기 때문에 관리가 어렵다.
- 메모리가 올라갔는지 아닌지 계속 추적하는 **Reference Counting**이 필요하다.
  - **ARC**에 의해 Reference Counting이 관리된다.
- **스레드들이 공유**하는 영역이기 때문에 **thread-safety**하지 않다.
  - 이를 관리하기 위해서는 Lock같은 자원이 필요하고 이것이 오버헤드로 이어진다.

class의 인스턴스, closure, 가변적인 String, Array같은 타입들이 Heap영역에 저장된다.


# Stack
함수의 호출과 관련된 **지역 변수, 매개변수**들이 저장되는 영역이다.

- Stack은 선형의 단순한 구조이면 시간 복잡도가 O(1)이다. 즉, 할당과 해제가 **빠르고 효율적**이다.
  - CPU에 의해 관리되고 최적화 돼서 속도가 매우 빠르다.
- 함수가 종료되면 저장된 메모리도 해제된다.
- **컴파일 타임**에 결정되기 때문에 무한히 할당할 수 없다.
- 컴파일 단계에서 언제 생성되고 해제되는지 알 수 있는 struct와 같은 값들이 저장된다.
  - 단, struct가 protocol을 채택하는 경우에는 **Heap영**역에 저장된다.
- 스레드 별로 독립적인 Stack 영역을 가지고 있기 때문에 **thread-safety** 하다.


## Heap과 Stack의 관계
Heap과 Stack은 같은 메모리 공간을 사용한다.

Heap 영역은 메모리의 낮은 주소 -&amp;gt; 높은 주소의 방향으로 할당된다.

Stack 영역은 메모리의 높은 주소 -&amp;gt; 낮은 주소의 방향으로 할당된다.


# 정리
메모리구조
- Code : 소스코드(기계어), ReadOnly
- Data : 전역변수, 정적변수
- Heap : 동적 할당/해제, 유일하게 런타임에서 결정, ARC, Thread-Safety X
- Stack : 매개변수, 지역변수, Thread-Safety O&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Swift</category>
      <category>CODE</category>
      <category>data</category>
      <category>heap</category>
      <category>ios</category>
      <category>Stack</category>
      <category>Swift</category>
      <category>메모리 구조</category>
      <author>baegteun</author>
      <guid isPermaLink="true">https://baegteun.tistory.com/9</guid>
      <comments>https://baegteun.tistory.com/9#entry9comment</comments>
      <pubDate>Sun, 4 Dec 2022 14:55:18 +0900</pubDate>
    </item>
    <item>
      <title>[iOS - Swift] rethrows</title>
      <link>https://baegteun.tistory.com/8</link>
      <description>&lt;h1&gt;rethrows&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파라미터로 전달받는 함수(Closure)가 Error를 던질 때 사용&lt;/li&gt;
&lt;li&gt;argument 내부에서 에러가 발생하면, 해당 argument에서 받은 Error를 다시 던질 수 있도록 명시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;enum SomeError: Error {
    case someError
}

func someFunc(callback: () throws -&amp;gt; Void) {

}

func someThrowableFunc() throws {
    throw SomeError.someError
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 함수 정의할 시 callback()을 사용하려 하면&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;enum SomeError: Error {
    case someError
}

func someFunc(callback: () throws -&amp;gt; Void) {
    do {
        try callback()
    } catch {
        print(error)
    }
}

func someThrowableFunc() throws {
    throw SomeError.someError
}

someFunc(callback: someThrowableFunc)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;someFunc 내부 에서 Error를 처리해야 한다.&lt;br /&gt;하지만 someFunc가 rethrows를 이용하면&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;enum SomeError: Error {
    case someError
}

func someFunc(callback: () throws -&amp;gt; Void) rethrows {
    try callback()
}

func someThrowableFunc() throws {
    throw SomeError.someError
}

do {
    try someFunc(callback: someThrowableFunc)
} catch {
    print(error)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;someFunc를 사용하는 곳에서 Error를 처리할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;throws로 하면 안되나?&lt;/h2&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;enum SomeError: Error {
    case someError
}

func someFunc(callback: () throws -&amp;gt; Void) throws {
    try callback()
}

func someThrowableFunc() throws {
    throw SomeError.someError
}

do {
    try someFunc(callback: someThrowableFunc)
} catch {
    print(error)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;someFunc를 throws로 처리해도 someFunc를 사용하는 곳에서 Error를 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 throws와 rethrows의 차이는 무엇인가?&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import Foundation

enum SomeError: Error {
    case someError
}

func someFunc(callback: () throws -&amp;gt; Void) rethrows {
    try callback()
}

func someThrowableFunc() throws {
    throw SomeError.someError
}

func someNonThrowableFunc() {}

someFunc(callback: someNonThrowableFunc)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rethrows를 가진 함수에 throws가 아닌 함수를 전달하면 Error처리를 하지 않는다.&lt;br /&gt;하지만 throws를 가진 함수에 throws가 아닌 함수를 전달하면 Error처리를 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 throws를 가진 함수는 내부에서 스스로 throw를 할 수 있고, rethrows를 가진 함수는 내부에서 스스로 throw를 할 수 없는 특성이 있기 때문인거같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그 외&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 클래스의 throws는 자식클래스에서 rethrows로 override할 수 있다.&lt;br /&gt;반대로 부모 클래스의 rethrows는 자식클래스에서 throws로 override할 수 없다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜 함수에 throws가 있다면 rethrows로 구현할 수 있다.&lt;br /&gt;반대로 프로토콜 함수에 rethrows가 있다면 throws로 구현할 수 없다.&lt;/p&gt;</description>
      <category>Swift</category>
      <category>Closure</category>
      <category>error</category>
      <category>function</category>
      <category>ios</category>
      <category>rethrows</category>
      <category>Swift</category>
      <category>throw</category>
      <category>throws</category>
      <author>baegteun</author>
      <guid isPermaLink="true">https://baegteun.tistory.com/8</guid>
      <comments>https://baegteun.tistory.com/8#entry8comment</comments>
      <pubDate>Mon, 7 Nov 2022 17:43:52 +0900</pubDate>
    </item>
    <item>
      <title>Tuist 사용법 - 6. 버전 고정</title>
      <link>https://baegteun.tistory.com/7</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist를 사용하면서 다른 사람과 협업할 때, 각자 설치해놓은 버전이 다를 수 있습니다. 또한 Tuist의 업데이트 주기가 짧은 편이기에 더욱 이런 사태가 발생할 가능성이 크죠.&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;프로젝트의 root에서 &lt;code&gt;.tuist-version&lt;/code&gt; 파일을 만들고 그 안에 고정해놓을 Tuist 버전을 작성해놓으면 됩니다. (ex. 3.10.0)&lt;/p&gt;
&lt;h1&gt;References By&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.tuist.io/guides/version-management/#local&quot;&gt;https://docs.tuist.io/guides/version-management/#local&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tuist</category>
      <category>ios</category>
      <category>Swift</category>
      <category>Tuist</category>
      <category>version</category>
      <author>baegteun</author>
      <guid isPermaLink="true">https://baegteun.tistory.com/7</guid>
      <comments>https://baegteun.tistory.com/7#entry7comment</comments>
      <pubDate>Mon, 19 Sep 2022 12:44:00 +0900</pubDate>
    </item>
  </channel>
</rss>