<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>호댕의 iOS 개발</title>
    <link>https://ho8487.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 16 Jul 2026 04:41:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>호르댕댕댕</managingEditor>
    <image>
      <title>호댕의 iOS 개발</title>
      <url>https://tistory1.daumcdn.net/tistory/4911536/attach/dbd8e219b8b045719676f807392aac85</url>
      <link>https://ho8487.tistory.com</link>
    </image>
    <item>
      <title>[Widget] 위젯 사용자화 틴트 처리됨(tinted) 대응하기 (위젯이 흰 화면으로만 보인다...)</title>
      <link>https://ho8487.tistory.com/151</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;틴트 처리됨이 뭐지?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 18에는 홈화면 편집에서 사용자화라는 부분이 새롭게 등장했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 앱 아이콘 및 폴더의 색상을 변경할 수 있는 &lt;b&gt;틴트 처리됨&lt;/b&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;&gt;&lt;a href=&quot;https://support.apple.com/ko-kr/guide/iphone/iph385473442/ios&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://support.apple.com/ko-kr/guide/iphone/iph385473442/ios&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741681309598&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;iPhone 홈 화면에서 앱 및 위젯 사용자화하기&quot; data-og-description=&quot;iPhone의 홈 화면에서 앱 및 위젯의 색상 및 크기를 변경할 수 있습니다.&quot; data-og-host=&quot;support.apple.com&quot; data-og-source-url=&quot;https://support.apple.com/ko-kr/guide/iphone/iph385473442/ios&quot; data-og-url=&quot;https://support.apple.com/ko-kr/guide/iphone/iph385473442/ios&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BrJFW/hyYmIOk2Xt/o7ocyNlCFeInv2ApgMeNqk/img.png?width=489&amp;amp;height=1008&amp;amp;face=0_0_489_1008&quot;&gt;&lt;a href=&quot;https://support.apple.com/ko-kr/guide/iphone/iph385473442/ios&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://support.apple.com/ko-kr/guide/iphone/iph385473442/ios&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BrJFW/hyYmIOk2Xt/o7ocyNlCFeInv2ApgMeNqk/img.png?width=489&amp;amp;height=1008&amp;amp;face=0_0_489_1008');&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;iPhone 홈 화면에서 앱 및 위젯 사용자화하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;iPhone의 홈 화면에서 앱 및 위젯의 색상 및 크기를 변경할 수 있습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;support.apple.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;안드로이드에선 이전부터 되던 기능들이 이제서야 되는 것이었지만 새로운 기능을 제공하는 것은 좋은 것이지라고 WWDC를 보며 생각을 했었는데...&lt;/p&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;b&gt;문제 상황&lt;/b&gt;&lt;/h2&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;IMG_7356.PNG&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOHRLC/btsMHRO9BRc/vCMwqDBkopRa9V8k5BvMcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOHRLC/btsMHRO9BRc/vCMwqDBkopRa9V8k5BvMcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOHRLC/btsMHRO9BRc/vCMwqDBkopRa9V8k5BvMcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOHRLC%2FbtsMHRO9BRc%2FvCMwqDBkopRa9V8k5BvMcK%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;286&quot; height=&quot;619&quot; data-filename=&quot;IMG_7356.PNG&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&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;p data-ke-size=&quot;size16&quot;&gt;T맵의 위젯처럼 아무 콘텐츠도 보이지 않는데 웃긴건 탭 이벤트는 또 제대로 동작한다. 정말 보여지는 뷰만 문제가 있는 것이다  &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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결방법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;widgetAccentable()&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플에선 iOS 16버전부터 적용이 가능한 해결책을 제공해주고 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/widgetaccentable(_:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/swiftui/view/widgetaccentable(_:)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741681595857&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;widgetAccentable(_:) | Apple Developer Documentation&quot; data-og-description=&quot;Adds the view and all of its subviews to the accented group.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/swiftui/view/widgetaccentable(_:)&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/swiftui/view/widgetaccentable(_:)&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cJDEYl/hyYmKSV0Rx/nIjZI8P0vzXDlnkyff6pUK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/duzgC2/hyYr0MVLgE/BpifcQwrF7hOgB2IYFHFO1/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/widgetaccentable(_:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/swiftui/view/widgetaccentable(_:)&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cJDEYl/hyYmKSV0Rx/nIjZI8P0vzXDlnkyff6pUK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/duzgC2/hyYr0MVLgE/BpifcQwrF7hOgB2IYFHFO1/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;widgetAccentable(_:) | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Adds the view and all of its subviews to the accented group.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 WidgetKit의 &lt;span style=&quot;background-color: #ffffff; color: #1d1d1f; text-align: left;&quot;&gt;Widget&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1d1f; text-align: left;&quot;&gt;Rendering&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1d1f; text-align: left;&quot;&gt;Mode가 기본인 경우(&lt;b&gt;fullColor&lt;/b&gt;) / 틴트 처리된 경우(&lt;b&gt;accented&lt;/b&gt;)로 나눠서 다른 색상을 적용해 뷰를 그리게 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1d1f; text-align: left;&quot;&gt;이를 상위 뷰에 적용하게 되면 하위 뷰까지 전부 적용된다.&amp;nbsp;&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;&lt;span style=&quot;background-color: #ffffff; color: #1d1d1f; text-align: left;&quot;&gt;여기서 주의할 점은 한 번 &lt;span style=&quot;background-color: #dddddd; color: #ee2323;&quot;&gt;widgetAccentable(true)&lt;/span&gt;를 적용하게 되면 추후 &lt;span style=&quot;background-color: #dddddd; color: #ee2323;&quot;&gt;widgetAccentable(false)&lt;/span&gt;를 적용하더라도 accented를 위한 그룹에서 해당 뷰를 포함한 하위 뷰가 빠지지 않게 된다.&amp;nbsp;&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;&lt;span style=&quot;background-color: #ffffff; color: #1d1d1f; text-align: left;&quot;&gt;일단은 이걸 적용하게 되면 일차원적인 해결은 가능하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1d1f; text-align: left;&quot;&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;span style=&quot;background-color: #ffffff; color: #1d1d1f; text-align: left;&quot;&gt;widgetAccentable()을 사용하며 발생했던 문제&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1d1f; text-align: left;&quot;&gt;뷰가 만약 아래 코드처럼 구성이 되어 있다고 해보자&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741682060482&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZStack {
    Color.black
    CustomView()
    	.foregroundColor(Color.white)
}&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;&gt;아주 간단하게 검은 배경에 흰 배경을 가진 커스텀 뷰가 있는 화면이다. 커스텀 뷰에는 간단한 리스트가 있었다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 여기서 widgetAccentable()을 ZStack에 먹인다면?&lt;/p&gt;
&lt;pre id=&quot;code_1741682143518&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZStack {
    Color.black
    CustomView()
    	.foregroundColor(Color.white)
}
.widgetAcceptable()&lt;/code&gt;&lt;/pre&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;622&quot; data-origin-height=&quot;957&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZVfaB/btsMFKqbQEa/b75TJui5kVAPkBzgkmL4P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZVfaB/btsMFKqbQEa/b75TJui5kVAPkBzgkmL4P1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZVfaB/btsMFKqbQEa/b75TJui5kVAPkBzgkmL4P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZVfaB%2FbtsMFKqbQEa%2Fb75TJui5kVAPkBzgkmL4P1%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;263&quot; height=&quot;405&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;957&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 미리보기 화면처럼 컨텐츠가 보이지 않고 CustomView 영역 전체가 tint color로 바뀌게 된다.&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;그래서 이 경우 ZStack을 따로 사용하지 않고, CustomView에 background Color를 주는 방식으로 수정을 했다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741682882459&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CustomView() // 내부에서 backgroundColor 변경
    .foregroundColor(Color.white)
    .widgetAcceptable()&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ZStack 전체에 걸어주게 되면 하위 뷰 전체에 accent color가 적용되면서 배경에도 accent color가 적용되어 이런 문제가 발생했던 것 같다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;1018&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sXQc7/btsMIDQdSpz/bhu6gl2ViKX4isFQz9468k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sXQc7/btsMIDQdSpz/bhu6gl2ViKX4isFQz9468k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sXQc7/btsMIDQdSpz/bhu6gl2ViKX4isFQz9468k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsXQc7%2FbtsMIDQdSpz%2Fbhu6gl2ViKX4isFQz9468k%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;484&quot; height=&quot;501&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;1018&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;만약 앱이 iOS 16 미만의 Deployment Target을 가지고 있다면?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했듯 widgetAcceptable()의 경우 iOS 16이상부터 지원을 하고 있다.&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;span style=&quot;background-color: #dddddd; color: #ee2323;&quot;&gt;if #available(iOS 16, *)&lt;/span&gt; 이렇게 분기 코드를 만들어 뷰를 만들면 복잡도가 너무 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 나는 커스텀 ViewModifier를 활용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741683131397&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct CustomWidgetRenderingModeModifier: ViewModifier {
    func body(content: Content) -&amp;gt; some View {
        if #available(iOS 16.0, *) {
            content
                .widgetAccentable()
        } else {
            content
        }
    }
}&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;&gt;코드는 간단하다. 내부 body 함수에서 버전 분기를 해서 16 미만인 경우 그대로 원래 뷰를 반환하고, 16 이상인 경우 .widgetAccentable()을 적용하는 코드이다.&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;틴트 처리됨은 iOS 18부터 제공하는 기능이기 때문에 16부터 버전 분기를 하지 않고 iOS 18을 기준으로 분기를 해줘도 무방할 것 같다.&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;그럼 .widgetAccentable()을 사용했던 것과 유사하게 사용이 가능하며 버전 분기도 매번 필요하지 않게 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741683295901&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CustomView()
    .foregroundColor(Color.white)
    .modifier(CustomWidgetRenderingModeModifier())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;widgetRenderingMode 환경변수&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Text에 background를 주는 경우 Text의 영역만큼 TintColor로 채워지는 문제&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1057&quot; data-origin-height=&quot;643&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfaXGy/btsMGfKkfHM/A4tEjgoHs9NwUJoZqmYkYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfaXGy/btsMGfKkfHM/A4tEjgoHs9NwUJoZqmYkYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfaXGy/btsMGfKkfHM/A4tEjgoHs9NwUJoZqmYkYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfaXGy%2FbtsMGfKkfHM%2FA4tEjgoHs9NwUJoZqmYkYK%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;546&quot; height=&quot;332&quot; data-origin-width=&quot;1057&quot; data-origin-height=&quot;643&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;601&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ12xS/btsMFH1uo7T/LaQ05pf8OQjKDkJobYlKB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ12xS/btsMFH1uo7T/LaQ05pf8OQjKDkJobYlKB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ12xS/btsMFH1uo7T/LaQ05pf8OQjKDkJobYlKB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ12xS%2FbtsMFH1uo7T%2FLaQ05pf8OQjKDkJobYlKB0%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;546&quot; height=&quot;305&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;601&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;p data-ke-size=&quot;size16&quot;&gt;기존에는 Text가 있고 Background에 색이 칠해져있는 뷰이다.&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;&gt;문제의 원인은 이전 ZStack 전체에 .widgetAccentable()을 적용했을 때와 동일한 원인이라고 판단했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이걸 해결하기 위해선 내가 직접 accented / fullColor에 따라 각기 다른 뷰를 반환할 필요가 있었다.&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;그래서 widgetRenderingMode를 사용해야 했다.&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;WidgetRenderingMode 또한 iOS 16부터 등장했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/widgetrenderingmode&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/widgetkit/widgetrenderingmode&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741684039620&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;WidgetRenderingMode | Apple Developer Documentation&quot; data-og-description=&quot;Constants that indicate the rendering mode for a widget.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/widgetkit/widgetrenderingmode&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/widgetkit/widgetrenderingmode&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dR90RE/hyYrYhgz0q/b4Mrna99SGrLro1ZWL0FS0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/blttor/hyYnbXffGi/aycTM9IFqRiVkGQlR9cPyK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/widgetrenderingmode&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/widgetkit/widgetrenderingmode&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dR90RE/hyYrYhgz0q/b4Mrna99SGrLro1ZWL0FS0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/blttor/hyYnbXffGi/aycTM9IFqRiVkGQlR9cPyK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;WidgetRenderingMode | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Constants that indicate the rendering mode for a widget.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.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;pre id=&quot;code_1741684073500&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Environment(\.widgetRenderingMode) var widgetRenderingMode&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;&gt;이는 3가지 Case가 존재한다.&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;.accented&lt;/b&gt; : 틴트 적용됨을 선택한 상태&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.fullColor&lt;/b&gt; : 일반적인 다크모드 / 일반모드를 선택한 상태&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.vibrant&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;그렇다면 우리는 widgetRenderingMode가 .accented일 때와 아닐 때로 구분해서 서로 다른 뷰를 전달시키면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(accented일 때에는 Text에 background Color가 없고, 아닐 때에는 있도록)&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;일단 우리는 홈화면에 띄우는 일반적인 위젯을 보고 있기 때문에 vibrant는 논외로 보자.&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 16 미만의 Deployment Target을 가지고 있다...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 처음에는 이런 계산 프로퍼티를 활용하면 되지 않을까란 생각을 했었다.&lt;/p&gt;
&lt;pre id=&quot;code_1741684515584&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(iOS 16, *)
private var widgetRenderingMode: WidgetRenderingMode {
    @Environment(\.widgetRenderingMode) var widgetRenderingMode
    return widgetRenderingMode
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흠.. 그런데 이렇게 하는 경우 항상 widgetRenderingMode가 .fullColor로 반환이 된다...&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;그래서 특정 버전 위에서 사용할 수 있는 커스텀 뷰와 특정 버전 이하에서 사용할 커스텀 뷰를 각각 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741684959182&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(iOS 16, *)
struct CustomTextOveriOS18: View {
	@Environment(\.widgetRenderingMode) var widgetRenderingMode

	var body: some View {
    	if widgetRenderingMode == .accented {
        	Text(&quot;Hello&quot;)
                .font(.largeTitle)
                .foregroundColor(.white)
                .background(
                    RoundedRectangle(cornerRadius: 4)
                        .strokeBorder(Color.white, lineWidth: 1) // Color는 tint가 먹혀서 크게 중요 X
                )
        } else {
        	Text(&quot;Hello&quot;)
                .font(.largeTitle)
                .foregroundColor(.white)
                .background(
                    Color.orange
                )
        }
    }
}

struct CustomText: View {
    var body: some View {
    	Text(&quot;Hello&quot;)
                .font(.largeTitle)
                .foregroundColor(.white)
                .background(
                    Color.orange
                )
    }
}&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;&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;1022&quot; data-origin-height=&quot;980&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcsLJO/btsMIs9bGh5/49akAjiRXMxJCmZ0dbKEVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcsLJO/btsMIs9bGh5/49akAjiRXMxJCmZ0dbKEVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcsLJO/btsMIs9bGh5/49akAjiRXMxJCmZ0dbKEVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcsLJO%2FbtsMIs9bGh5%2F49akAjiRXMxJCmZ0dbKEVk%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;362&quot; height=&quot;347&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;980&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;p data-ke-size=&quot;size16&quot;&gt;그러면 이런 식으로 Text도 잘 보이면서 틴트도 제대로 먹일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흠... 그런데 만약 정말 background Color가 꼭 필요한 상황이라면..?&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;아예 해당 부분을 이미지로 대체하거나, background Color에 opacity를 줘야 할 것 같은데 opacity를 주는 방법은 직접 테스트를 해보진 못했다.&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;&lt;b&gt;widgetAccentedRenderingMode&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이미지가 제대로 tintColor가 적용되지 않는 문제&lt;/h4&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;이 경우 사용할 수 있는 것이 바로 widgetAccentedRenderingMode이다. 이는 Image의 인스턴스 메서드로 이미지에서만 사용이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 iOS 16부터 사용할 수 있는 다른 것들과 다르게 iOS 18 이상부터 사용할 수 있는 따끈따끈한 함수/구조체이다.&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 style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;만약 Image의 상위 뷰에 .widgetAccentable()을 적용했다면 widgetAccentedRenderingMode 적용이 제대로 되지 않을 수 있다.&amp;nbsp;&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/widgetaccentedrenderingmode&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/widgetkit/widgetaccentedrenderingmode&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741685431007&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;WidgetAccentedRenderingMode | Apple Developer Documentation&quot; data-og-description=&quot;Constants that indicate the rendering mode for an in when displayed in a widget in mode.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/widgetkit/widgetaccentedrenderingmode&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/widgetkit/widgetaccentedrenderingmode&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qcrE1/hyYqKRgepc/HeBpjD8QcAmSLc0c1UAsU1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bjMSHH/hyYqQ4Z6gv/3mmmFCZnZT9QJDjxBOpxK0/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/widgetaccentedrenderingmode&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/widgetkit/widgetaccentedrenderingmode&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qcrE1/hyYqKRgepc/HeBpjD8QcAmSLc0c1UAsU1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bjMSHH/hyYqQ4Z6gv/3mmmFCZnZT9QJDjxBOpxK0/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;WidgetAccentedRenderingMode | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Constants that indicate the rendering mode for an in when displayed in a widget in mode.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/image/widgetaccentedrenderingmode(_:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/swiftui/image/widgetaccentedrenderingmode(_:)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741685459833&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;widgetAccentedRenderingMode(_:) | Apple Developer Documentation&quot; data-og-description=&quot;Specifies the how to render an when using the mode.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/swiftui/image/widgetaccentedrenderingmode(_:)&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/swiftui/image/widgetaccentedrenderingmode(_:)&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cyBJyt/hyYmNB5yeX/brJleeFVpVXIl7suTyZXEk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/fHP5d/hyYmMJW4YZ/UYQP6ehsBlElIxK4hkpeqK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/image/widgetaccentedrenderingmode(_:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/swiftui/image/widgetaccentedrenderingmode(_:)&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cyBJyt/hyYmNB5yeX/brJleeFVpVXIl7suTyZXEk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/fHP5d/hyYmMJW4YZ/UYQP6ehsBlElIxK4hkpeqK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;widgetAccentedRenderingMode(_:) | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Specifies the how to render an when using the mode.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.accented : 기본 상태로 이미지 내부 디테일 없이 tint color가 적용된다.&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;386&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xh580/btsMHcTSiee/rOzrO4BVmLqDwQKL35jky1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xh580/btsMHcTSiee/rOzrO4BVmLqDwQKL35jky1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xh580/btsMHcTSiee/rOzrO4BVmLqDwQKL35jky1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxh580%2FbtsMHcTSiee%2FrOzrO4BVmLqDwQKL35jky1%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;222&quot; height=&quot;102&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.accentedDeceturated : tint color는 적용되지만, 이미지의 디테일은 유지된다.&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;397&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cy6fa7/btsMFLvYfgf/BbvOG1GhQvUDy1C96ObVzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cy6fa7/btsMFLvYfgf/BbvOG1GhQvUDy1C96ObVzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cy6fa7/btsMFLvYfgf/BbvOG1GhQvUDy1C96ObVzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcy6fa7%2FbtsMFLvYfgf%2FBbvOG1GhQvUDy1C96ObVzK%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;224&quot; height=&quot;96&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.deceturated : 원래 이미지 컬러와 유사하지만 채도가 약간 빠진 것처럼 보이게 된다.&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;344&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX7gA6/btsMG9ix5ik/vPViukLbkPka6BwYRKAtR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX7gA6/btsMG9ix5ik/vPViukLbkPka6BwYRKAtR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX7gA6/btsMG9ix5ik/vPViukLbkPka6BwYRKAtR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX7gA6%2FbtsMG9ix5ik%2FvPViukLbkPka6BwYRKAtR1%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;209&quot; height=&quot;109&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.fullColor : 원래 이미지 컬러 그대로를 보여준다.&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;340&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn3uAB/btsMG9Xb7Zf/vX89sMPWggbffeOAWbaVeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn3uAB/btsMG9Xb7Zf/vX89sMPWggbffeOAWbaVeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn3uAB/btsMG9Xb7Zf/vX89sMPWggbffeOAWbaVeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn3uAB%2FbtsMG9Xb7Zf%2FvX89sMPWggbffeOAWbaVeK%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;213&quot; height=&quot;109&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;174&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틴트 컬러는 적용이 되는 것이 사용자 설정 상태에 맞다고 판단했고 나는 &lt;span style=&quot;background-color: #dddddd; color: #ee2323;&quot;&gt;.accentedDeceturated&lt;/span&gt; 옵션을 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741686188150&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ImageView: View {
    var body: some View {
        if #available(iOS 18, *) {
            Image(systemName: &quot;star&quot;)
                .widgetAccentedRenderingMode(.accentedDesaturated)
        } else {
            Image(systemName: &quot;star&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;&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;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;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://nemecek.be/blog/206/how-to-support-tinted-home-screen-widgets-in-ios-18&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nemecek.be/blog/206/how-to-support-tinted-home-screen-widgets-in-ios-18&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741681476296&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How to support tinted home screen widgets in iOS 18&quot; data-og-description=&quot;Marking views to be rendered with colors, handling images and more.&quot; data-og-host=&quot;nemecek.be&quot; data-og-source-url=&quot;https://nemecek.be/blog/206/how-to-support-tinted-home-screen-widgets-in-ios-18&quot; data-og-url=&quot;https://nemecek.be/blog/206/how-to-support-tinted-home-screen-widgets-in-ios-18&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JQf0E/hyYmM4ftlg/kD52YCNJXuyTtk2VtOC3Y0/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800&quot;&gt;&lt;a href=&quot;https://nemecek.be/blog/206/how-to-support-tinted-home-screen-widgets-in-ios-18&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nemecek.be/blog/206/how-to-support-tinted-home-screen-widgets-in-ios-18&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JQf0E/hyYmM4ftlg/kD52YCNJXuyTtk2VtOC3Y0/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800');&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;How to support tinted home screen widgets in iOS 18&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Marking views to be rendered with colors, handling images and more.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nemecek.be&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.createwithswift.com/adapting-widgets-for-tint-mode-and-dark-mode-in-swiftui/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.createwithswift.com/adapting-widgets-for-tint-mode-and-dark-mode-in-swiftui/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741681517783&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;Adapting widgets for tint mode and dark mode in SwiftUI&quot; data-og-description=&quot;Explore the multiple rendering modes of widgets for different device settings with SwiftUI.&quot; data-og-host=&quot;www.createwithswift.com&quot; data-og-source-url=&quot;https://www.createwithswift.com/adapting-widgets-for-tint-mode-and-dark-mode-in-swiftui/&quot; data-og-url=&quot;https://www.createwithswift.com/adapting-widgets-for-tint-mode-and-dark-mode-in-swiftui/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bFISdz/hyYrO6NBZ6/LAf57pKQQ3sjdZaeafj3j0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/iiOCP/hyYrPkj5ch/gYRuVeC2QJSFE79oDlPQy1/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/b733pg/hyYqOsyO2m/PpnHSpmWkTbxXnJouOOUj1/img.png?width=1920&amp;amp;height=1080&amp;amp;face=783_372_855_450&quot;&gt;&lt;a href=&quot;https://www.createwithswift.com/adapting-widgets-for-tint-mode-and-dark-mode-in-swiftui/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.createwithswift.com/adapting-widgets-for-tint-mode-and-dark-mode-in-swiftui/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bFISdz/hyYrO6NBZ6/LAf57pKQQ3sjdZaeafj3j0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/iiOCP/hyYrPkj5ch/gYRuVeC2QJSFE79oDlPQy1/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/b733pg/hyYqOsyO2m/PpnHSpmWkTbxXnJouOOUj1/img.png?width=1920&amp;amp;height=1080&amp;amp;face=783_372_855_450');&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;Adapting widgets for tint mode and dark mode in SwiftUI&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Explore the multiple rendering modes of widgets for different device settings with SwiftUI.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.createwithswift.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;</description>
      <category>Software Engineering/iOS</category>
      <author>호르댕댕댕</author>
      <guid isPermaLink="true">https://ho8487.tistory.com/151</guid>
      <comments>https://ho8487.tistory.com/151#entry151comment</comments>
      <pubDate>Tue, 11 Mar 2025 18:44:40 +0900</pubDate>
    </item>
    <item>
      <title>[Appsflyer Push] Safari에서 앱이 설치되고도 앱으로 redirect되지 않는 문제</title>
      <link>https://ho8487.tistory.com/150</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Safari에 한정해서 앱이 설치되고도 앱으로 redirect되지 않고 항상 앱스토어로 넘어가는 문제가 있었음.&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://support.appsflyer.com/hc/ko/articles/207447163-%EC%96%B4%ED%8A%B8%EB%A6%AC%EB%B7%B0%EC%85%98-%EB%A7%81%ED%81%AC-%EA%B5%AC%EC%A1%B0%EC%99%80-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0#af_force_deeplink&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Appsflyer OneLink 공식문서&lt;/a&gt;에 보면 af_force_deeplink라는 Attribution link parameters가 존재합니다.&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는 URI Scheme으로만 앱을 여는데 제한이 있어 해당 파라미터 값을 true로 주면 Safari에서도 앱을 연다는 Alert가 나온 후 앱이 열리게 됩니다.&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;</description>
      <category>Software Engineering/iOS</category>
      <author>호르댕댕댕</author>
      <guid isPermaLink="true">https://ho8487.tistory.com/150</guid>
      <comments>https://ho8487.tistory.com/150#entry150comment</comments>
      <pubDate>Wed, 10 Jul 2024 12:03:44 +0900</pubDate>
    </item>
    <item>
      <title>[Fastlane] Fastlane Match 도입해보기 (계속된 삽질)</title>
      <link>https://ho8487.tistory.com/149</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요_-001 (7).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddXTZ8/btsGMKIfTN4/FRgLNwucneCBYc71r578tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddXTZ8/btsGMKIfTN4/FRgLNwucneCBYc71r578tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddXTZ8/btsGMKIfTN4/FRgLNwucneCBYc71r578tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddXTZ8%2FbtsGMKIfTN4%2FFRgLNwucneCBYc71r578tk%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;360&quot; height=&quot;360&quot; data-filename=&quot;제목을 입력해주세요_-001 (7).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&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;p data-ke-size=&quot;size16&quot;&gt;iOS에서 앱을 배포하기 위해선 Code Signing이 필수적이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 어떤 사람이 해당 앱에 서명을 했는지 식별하고, &lt;b&gt;서명이 된 이후에는 앱에 변경 사항이 없다는 것을 보증&lt;/b&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;앱 빌드 &amp;amp; 아카이빙 시&lt;/b&gt; 됩니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://help.apple.com/xcode/mac/current/#/dev3a05256b8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://help.apple.com/xcode/mac/current/#/dev3a05256b8&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713508553484&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;https://help.apple.com/xcode/mac/current/#/dev3a05256b8&quot; data-og-description=&quot;To see this page, you must enable JavaScript. Pour afficher cette page, vous devez activer JavaScript. Zur Anzeige dieser Seite m&amp;uuml;ssen Sie JavaScript aktivieren. このページを表示するには、JavaScript を有効にする必要があります。 &quot; data-og-host=&quot;help.apple.com&quot; data-og-source-url=&quot;https://help.apple.com/xcode/mac/current/#/dev3a05256b8&quot; data-og-url=&quot;https://help.apple.com/xcode/mac/current/#/dev3a05256b8&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://help.apple.com/xcode/mac/current/#/dev3a05256b8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://help.apple.com/xcode/mac/current/#/dev3a05256b8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;https://help.apple.com/xcode/mac/current/#/dev3a05256b8&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;To see this page, you must enable JavaScript. Pour afficher cette page, vous devez activer JavaScript. Zur Anzeige dieser Seite m&amp;uuml;ssen Sie JavaScript aktivieren. このページを表示するには、JavaScript を有効にする必要があります。&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;help.apple.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;즉, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;앱을 배포했다면 해당 앱은 해당 버전에선 더 이상 변경 사항이 없다는 것을 보증&lt;/b&gt;&lt;/span&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;이를 위해선 Signing Certificate가 사용된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-19 오후 3.33.29.png&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o4pbG/btsGKWww6Db/6DWetodFFIztnn5tfXSCoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o4pbG/btsGKWww6Db/6DWetodFFIztnn5tfXSCoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o4pbG/btsGKWww6Db/6DWetodFFIztnn5tfXSCoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo4pbG%2FbtsGKWww6Db%2F6DWetodFFIztnn5tfXSCoK%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;1386&quot; height=&quot;122&quot; data-filename=&quot;스크린샷 2024-04-19 오후 3.33.29.png&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;122&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;p data-ke-size=&quot;size16&quot;&gt;이는 Xcode Project의 Target에서 있는 Signing &amp;amp; Capabilities 탭에서 확인해볼 수 있다.&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;/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;하지만 이런 인증서는 배포의 경우 최대 3개까지만 사용이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-19 오후 3.37.57.png&quot; data-origin-width=&quot;1466&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EXFfl/btsGM3gwUwU/cg5FtfgbEF77duvK1SSr8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EXFfl/btsGM3gwUwU/cg5FtfgbEF77duvK1SSr8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EXFfl/btsGM3gwUwU/cg5FtfgbEF77duvK1SSr8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEXFfl%2FbtsGM3gwUwU%2Fcg5FtfgbEF77duvK1SSr8K%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;1466&quot; height=&quot;216&quot; data-filename=&quot;스크린샷 2024-04-19 오후 3.37.57.png&quot; data-origin-width=&quot;1466&quot; data-origin-height=&quot;216&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;p data-ke-size=&quot;size16&quot;&gt;즉 프로젝트를 진행하는 인원이 3명이 넘게 되면 이제 인증서에 대한 관리가 빡세지는 것이다.&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;&gt;물론 갱신 같은 작업은 엄청 간단하기 때문에 엄청나게 부담은 아니다. 다만 팀이 커지게 되며 Certificate / Provisioning Profiles를 개별적으로 관리하는 것은 분명한 한계를 지닌다.&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;h2 data-ke-size=&quot;size26&quot;&gt;이런 상황에서 Fastlane을 사용한다면?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fastlane match를 사용하면 일단 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;1개의 certificate와 provisioning profile을 공유해서 사용&lt;/b&gt;&lt;/span&gt;할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 팀 전체가 Certificate와 Provisioning Profile의 싱크를 맞출 수 있고, 여기서 발생할 수 있는 문제를 최소화할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;s&gt;다만 나는 설정하는데 꽤나 많은 시간이 걸려서 문제가 최소화된 건지는 잘 모르겠다 ㅎㅎ...&lt;/s&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;Fastlane에선 certificate와 provisioning profile을 저장하는 공간을 3가지로 제공하고 있다.&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;Google Cloud&lt;/li&gt;
&lt;li&gt;Amazon A3&lt;/li&gt;
&lt;li&gt;Git Repository&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;여기서 나는 가장 많이 사용되는 Git Repository를 활용했다.&amp;nbsp;&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;스크린샷 2024-01-30 오후 2.32.00.png&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;1342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNUU9M/btsGMg1T2Cb/dM8QupiCuQ6BOqS2xo6nkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNUU9M/btsGMg1T2Cb/dM8QupiCuQ6BOqS2xo6nkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNUU9M/btsGMg1T2Cb/dM8QupiCuQ6BOqS2xo6nkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNUU9M%2FbtsGMg1T2Cb%2FdM8QupiCuQ6BOqS2xo6nkK%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;1612&quot; height=&quot;1342&quot; data-filename=&quot;스크린샷 2024-01-30 오후 2.32.00.png&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;1342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림으로 보면 이런 식으로 Git Repository를 통해 팀이 사용하는 Certificate와 Provisioning Profile을 관리할 수 있는 것이다.&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;a href=&quot;https://codesigning.guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codesigning.guide/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713509718092&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Code Signing Guide for Teams&quot; data-og-description=&quot;A new approach to code signing A best practices guide on how to manage certificates and provisioning profiles in your development team. Wait, what is code signing? Code signing is required on iOS when distributing your app to customers. It assures that you&quot; data-og-host=&quot;codesigning.guide&quot; data-og-source-url=&quot;https://codesigning.guide/&quot; data-og-url=&quot;https://codesigning.guide/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cVJEBS/hyVS5wJYdQ/142nsgC1pKtMeFalr7toM0/img.png?width=812&amp;amp;height=681&amp;amp;face=0_0_812_681,https://scrap.kakaocdn.net/dn/bfXjlu/hyVPWnU0eh/ia5okLSCpsKxjqgDeXJQM1/img.png?width=992&amp;amp;height=247&amp;amp;face=0_0_992_247&quot;&gt;&lt;a href=&quot;https://codesigning.guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codesigning.guide/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cVJEBS/hyVS5wJYdQ/142nsgC1pKtMeFalr7toM0/img.png?width=812&amp;amp;height=681&amp;amp;face=0_0_812_681,https://scrap.kakaocdn.net/dn/bfXjlu/hyVPWnU0eh/ia5okLSCpsKxjqgDeXJQM1/img.png?width=992&amp;amp;height=247&amp;amp;face=0_0_992_247');&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;Code Signing Guide for Teams&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A new approach to code signing A best practices guide on how to manage certificates and provisioning profiles in your development team. Wait, what is code signing? Code signing is required on iOS when distributing your app to customers. It assures that you&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codesigning.guide&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;Fastlane에선 아래와 같은 기능을 사용할 수 있다고 말하고 있다.&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; &amp;nbsp;Git을 사용해서 모든 팀 멤버들에게 iOS / macOS 키와 profile의 싱크를 맞출 수 있음&lt;/li&gt;
&lt;li&gt; &amp;nbsp;인증서와 profile을 생성하고 저장하는 모든 어려운 작업을 처리&lt;/li&gt;
&lt;li&gt; &amp;nbsp;새로운 기기에서도 빠르게 Code signing을 설정할 수 있음&lt;/li&gt;
&lt;li&gt; &amp;nbsp;여러 target과 bundle identifier가 있는 앱을 작동시킴&lt;/li&gt;
&lt;li&gt; &amp;nbsp;다른 서드파티 서비스 없이 file과 Git repo를 관리할 수 있음&lt;/li&gt;
&lt;li&gt;✨&amp;nbsp;Provisioning profile은 항상 일치하는 certificate와 매치가 됨&lt;/li&gt;
&lt;li&gt; &amp;nbsp;만료되거나 유효하지 않은 경우 존재하는 profile / 인증서(certificate)를 쉽게 리셋시킬 수 있음&lt;/li&gt;
&lt;li&gt;♻️&amp;nbsp;--force 옵션을 사용해 모든 장치에서 provisioning profile을 자동으로 갱신&lt;/li&gt;
&lt;li&gt; &amp;nbsp;다수의 Apple 계정과 팀을 지원할 수 있음&lt;/li&gt;
&lt;/ul&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;지금까지 왜 Fastlane match를 활용해야 하는지와 사용했을 때 얻을 수 있는 이점에 대해 살펴봤다.&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;&gt;요 작업을 하면서 대부분의 사람들은 docs에 나온대로 뚝딱뚝딱 잘 만 하던데 나는 기존 프로젝트에 도입을 해서 그런지, 내가 이런 인프라 설정이 약한 것인지는 모르겠지만... 정말 많은 시행착오를 겪었다.&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;기본적인 Fastlane 설치 방법은 &lt;a href=&quot;https://docs.fastlane.tools/getting-started/ios/setup/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;fastlane Docs&lt;/a&gt;에 잘 나와있긴 하다.&lt;/p&gt;
&lt;figure id=&quot;og_1713510149663&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Setup - fastlane docs&quot; data-og-description=&quot;Getting started with fastlane for iOS Setup Xcode for fastlane xcode-select --install Installing fastlane fastlane can be installed in multiple ways. The preferred method is with Bundler. fastlane can also be installed directly through Homebrew (if on macO&quot; data-og-host=&quot;docs.fastlane.tools&quot; data-og-source-url=&quot;https://docs.fastlane.tools/getting-started/ios/setup/&quot; data-og-url=&quot;https://docs.fastlane.tools/getting-started/ios/setup/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dwoU5F/hyVS1Vp06o/22XIMs5y3yledVUH7XABiK/img.png?width=1764&amp;amp;height=1096&amp;amp;face=0_0_1764_1096,https://scrap.kakaocdn.net/dn/orxKY/hyVS3eCdIL/YKEkoPFkKbPLmbLP9oDLfk/img.png?width=871&amp;amp;height=304&amp;amp;face=0_0_871_304&quot;&gt;&lt;a href=&quot;https://docs.fastlane.tools/getting-started/ios/setup/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.fastlane.tools/getting-started/ios/setup/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dwoU5F/hyVS1Vp06o/22XIMs5y3yledVUH7XABiK/img.png?width=1764&amp;amp;height=1096&amp;amp;face=0_0_1764_1096,https://scrap.kakaocdn.net/dn/orxKY/hyVS3eCdIL/YKEkoPFkKbPLmbLP9oDLfk/img.png?width=871&amp;amp;height=304&amp;amp;face=0_0_871_304');&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;Setup - fastlane docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Getting started with fastlane for iOS Setup Xcode for fastlane xcode-select --install Installing fastlane fastlane can be installed in multiple ways. The preferred method is with Bundler. fastlane can also be installed directly through Homebrew (if on macO&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.fastlane.tools&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;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 기본적으로 fastlane에 대한 기본 셋팅은 이전 프로젝트에 되어 있어서 GemFile / FastFile, AppFile, MatchFile은 설정이 되어 있었다.&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;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기본적인 과정은 공식 문서를 참고해주세요. &lt;br /&gt;아래는 굵직하게 이슈가 있거나 정리해봄직 한 부분을 정리했습니다&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;새롭게 공용 Certificate &amp;amp; Provisioning Profile을 생성&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Fastlane 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fastlane 설치는 아래 명령어로 간단하게 진행했다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713510368244&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install fastlane&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;&gt;그리고 기분좋게 fastlane match init을 해서 match 설정을 시작하려 하자마자 에러 발생  &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;2. 터미널에 &lt;span style=&quot;color: #ee2323; background-color: #dddddd;&quot;&gt;Fastlane match init&lt;/span&gt; 명령어 호출&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 match를 시작하기 위해 요 명령어를 호출했다.&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;h4 data-ke-size=&quot;size20&quot;&gt;  fastlane match init 시 발생 에러&amp;nbsp; (Unable to locate Xcode. Please make sure to have Xcode installed on your machine)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-18 오전 10.06.17.png&quot; data-origin-width=&quot;5086&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CfSAS/btsGMLAtTVc/HYkXqntwz2duJCll8domn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CfSAS/btsGMLAtTVc/HYkXqntwz2duJCll8domn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CfSAS/btsGMLAtTVc/HYkXqntwz2duJCll8domn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCfSAS%2FbtsGMLAtTVc%2FHYkXqntwz2duJCll8domn0%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;5086&quot; height=&quot;170&quot; data-filename=&quot;스크린샷 2024-04-18 오전 10.06.17.png&quot; data-origin-width=&quot;5086&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode를 현재 2개 사용 중이라 발생했던 에러였다.&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;15.3에선 Archive로 ipa 파일 추출할 때 강제 종료되는 버그가 있어서 15.2로 다운그레이드 후 다른 프로젝트와 Swift Tool 버전이 맞지 않아 15.3도 따로 사용하고 있었는데 요것 때문에 발생한 에러였다.&amp;nbsp;&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;Unable to locate Xcode. Please make sure to have Xcode installed on your machine 해결 방안&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode &amp;gt; Preference &amp;gt; Location에서 현재 프로젝트에서 사용 중인 Xcode로 변경해주면 에러 해결&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-18 오전 10.07.37.png&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4C8qi/btsGL5famPQ/3FmMLiqkeqjjO7G9XSspf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4C8qi/btsGL5famPQ/3FmMLiqkeqjjO7G9XSspf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4C8qi/btsGL5famPQ/3FmMLiqkeqjjO7G9XSspf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4C8qi%2FbtsGL5famPQ%2F3FmMLiqkeqjjO7G9XSspf1%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;1542&quot; height=&quot;498&quot; data-filename=&quot;스크린샷 2024-04-18 오전 10.07.37.png&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Command Line Tools를 현재 사용 중인 버전에 맞게 골라주면 된다.&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;여기가 15.3 버전 Xcode로 되어 있어 문제가 발생한 것이었고 이렇게 수정해주고 나면 문제없이 명령어 실행이 됐다.&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;h3 data-ke-size=&quot;size23&quot;&gt;3. AppFile 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appfile에선 아래와 같은 내용들을 작성한다.&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;app_identifier : 현재 앱에서 사용 중인 Bundle Identifier&lt;/li&gt;
&lt;li&gt;apple_id : 현재 공용으로 생성한 Apple 계정&lt;/li&gt;
&lt;li&gt;team_id : Apple Developer에 로그인 후 밑으로 쭉 내리면 멤버십 세부 사항에 있는 팀 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-19 오후 4.17.05.png&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vkyhd/btsGL4ggxAL/onGDS1nEgmqJsKSEVSAfnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vkyhd/btsGL4ggxAL/onGDS1nEgmqJsKSEVSAfnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vkyhd/btsGL4ggxAL/onGDS1nEgmqJsKSEVSAfnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvkyhd%2FbtsGL4ggxAL%2FonGDS1nEgmqJsKSEVSAfnk%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;2118&quot; height=&quot;672&quot; data-filename=&quot;스크린샷 2024-04-19 오후 4.17.05.png&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;itc_team_id : AppStoreConnect에 로그인 후 (&lt;a href=&quot;https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/user/detail&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;)를 누르면 확인이 가능합니다. 이렇게 하면 JSON 형식의 데이터를 확인할 수 있는데 여기서 &lt;b&gt;&lt;span style=&quot;background-color: #f0f0f0; color: #880000; text-align: left;&quot;&gt;contentProviderId&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #880000; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 확인하면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 기존 Provisioning Profile 삭제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 3개까지만 사용이 가능하고 이제 더 이상 개인 계정이 아닌 공용 계정을 사용할 예정이기 때문에 기존 Provisioning Profile을 제거하는 것을 권장하고 있습니다.&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;pre id=&quot;code_1713511384788&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fastlane match nuke development // 개발용
fastlane match nuke appstore // 배포용&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;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;username&lt;/b&gt;&lt;/span&gt;을 묻는데 요건 공용으로 만든 개발자 계정을 입력해주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 사용할 passphrase(비밀번호)를 묻는다. 요건 추후 팀원들이 인증서와 Provisioning Profile을 불러올 때에도 사용이 되니 꼭 잘 기억해놓자!!!&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-18 오전 11.05.23.png&quot; data-origin-width=&quot;1980&quot; data-origin-height=&quot;1224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E0A8a/btsGMLm1I9S/sMHcpd2wbQQRkTL0JICFc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E0A8a/btsGMLm1I9S/sMHcpd2wbQQRkTL0JICFc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E0A8a/btsGMLm1I9S/sMHcpd2wbQQRkTL0JICFc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE0A8a%2FbtsGMLm1I9S%2FsMHcpd2wbQQRkTL0JICFc0%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;1980&quot; height=&quot;1224&quot; data-filename=&quot;스크린샷 2024-04-18 오전 11.05.23.png&quot; data-origin-width=&quot;1980&quot; data-origin-height=&quot;1224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;bad decrypt&lt;br /&gt;Couldn't decrypt the repo, please make sure you enter the right password!&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;아니 이름만 보면 무슨 비밀번호가 잘못 됐다는 것인데 아무리 해봐도 안된다.&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;span style=&quot;color: #000000; text-align: left;&quot;&gt;  Couldn't decrypt the repo, please make sure you enter the right password! 해결 방안&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 한 번 fastlane match를 도입하다가 계속 안돼서 방치하던 레포를 그대로 사용했더니 발생한 에러였다. 이유는 모르겠지만 저번과 동일한 비밀번호를 사용하면서 이 문자열을 입력했는데 계속 저런 에러가 발생했고 &lt;b&gt;아예 레포를 지우고 다시 동일한 이름으로 레포를 생성&lt;/b&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;이렇게 기존 Provisioning Profile도 삭제 완료!&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;5. 여러 app identifier 사용 중인 경우 이에 대한 대응 (+ multi target)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 앱은 OneSignal Extension / Widget을 사용하고 있는 Multi target 앱이었고, 거기다가 Debug / Release 따로 app identifier를 쓰고 있다보니 조금 더 복잡했다.&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;여기서 target / identifier에 맞게 인증서를 생성해야 했다.&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://docs.fastlane.tools/actions/match/#handle-multiple-targets:~:text=Handle%20multiple%20targets&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.fastlane.tools/actions/match/#handle-multiple-targets:~:text=Handle%20multiple%20targets&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713512133491&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;match - fastlane docs&quot; data-og-description=&quot;type Define the profile type, can be appstore, adhoc, development, enterprise, developer_id, mac_installer_distribution, developer_id_installer development&quot; data-og-host=&quot;docs.fastlane.tools&quot; data-og-source-url=&quot;https://docs.fastlane.tools/actions/match/#handle-multiple-targets:~:text=Handle%20multiple%20targets&quot; data-og-url=&quot;https://docs.fastlane.tools/actions/match/#handle-multiple-targets:~:text=Handle%20multiple%20targets&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wpyoZ/hyVPUQ9AP4/pAzaMgyy4pT6MSh0npT760/img.png?width=1872&amp;amp;height=654&amp;amp;face=0_0_1872_654,https://scrap.kakaocdn.net/dn/ESLmH/hyVSWzOv8m/kLXtBuDEFtgtbOKfKKZn80/img.png?width=1000&amp;amp;height=773&amp;amp;face=0_0_1000_773,https://scrap.kakaocdn.net/dn/bqscRS/hyVPR7W8KS/ZouAcARK0iNOV0c7kkk73K/img.png?width=871&amp;amp;height=304&amp;amp;face=0_0_871_304&quot;&gt;&lt;a href=&quot;https://docs.fastlane.tools/actions/match/#handle-multiple-targets:~:text=Handle%20multiple%20targets&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.fastlane.tools/actions/match/#handle-multiple-targets:~:text=Handle%20multiple%20targets&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wpyoZ/hyVPUQ9AP4/pAzaMgyy4pT6MSh0npT760/img.png?width=1872&amp;amp;height=654&amp;amp;face=0_0_1872_654,https://scrap.kakaocdn.net/dn/ESLmH/hyVSWzOv8m/kLXtBuDEFtgtbOKfKKZn80/img.png?width=1000&amp;amp;height=773&amp;amp;face=0_0_1000_773,https://scrap.kakaocdn.net/dn/bqscRS/hyVPR7W8KS/ZouAcARK0iNOV0c7kkk73K/img.png?width=871&amp;amp;height=304&amp;amp;face=0_0_871_304');&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;match - fastlane docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;type Define the profile type, can be appstore, adhoc, development, enterprise, developer_id, mac_installer_distribution, developer_id_installer development&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.fastlane.tools&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서를 생성하는 명령어는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713512244745&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fastlane match development // Debug
fastlane match appstore // Release&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이렇게 하면 AppFile에 등록한 Bundle Identifier에 해당하는 인증서만 생성된다.&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;즉 현재 앱에선 Debug와 Release의 Bundle Identifier가 다른데 동일하게 인증서가 생성되는 것이다.&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;pre id=&quot;code_1713512355228&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fastlane match appstore -a tools.fastlane.app&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 땐 요런 식으로 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;-a&lt;/b&gt;&lt;/span&gt;를 붙이고 해당하는 Bundle Identifier를 적어주면 된다.&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;이렇게 하면 일단 새로운 공용 계정 Certificate와 Provisioning Profile을 등록할 수 있다.&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;6. Xcode 설정 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글 초반에 말했듯 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Xcode Project의 Target에서 있는 Signing &amp;amp; Capabilities 탭&lt;/b&gt;&lt;/span&gt;에서&lt;span&gt; Certificate와 Provisioning Profile에 대한 설정을 할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-19 오후 4.45.42.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNSBTM/btsGNehYer4/0Qz9r3njpaDQ9tbiRu7WM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNSBTM/btsGNehYer4/0Qz9r3njpaDQ9tbiRu7WM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNSBTM/btsGNehYer4/0Qz9r3njpaDQ9tbiRu7WM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNSBTM%2FbtsGNehYer4%2F0Qz9r3njpaDQ9tbiRu7WM1%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;1238&quot; height=&quot;512&quot; data-filename=&quot;스크린샷 2024-04-19 오후 4.45.42.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;512&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Automatically manage signing 체크 해제&lt;/li&gt;
&lt;li&gt;적합한 Provisioning Profile 선택&lt;/li&gt;
&lt;/ol&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 style=&quot;color: #333333; text-align: start;&quot;&gt;Xcode 설정 변경도 추가로 해줘야 하는데 이는 새로운 컴퓨터에서 추가한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Certificate와 Provisioning Profile을 받을 때 좀 더 문제가 생겼고 개선한 부분이 있어 뒤에 조금 더 추가적인 내용이 존재한다.&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;/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;다른 컴퓨터에서 인증서를 받고 이에 대한 처리&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Certificate &amp;amp; Provisioning Profile을 Git 레포에서 불러오기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불러오는 명령어 자체는 간단하다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713513179868&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fastlane match development --readonly // Debug
fastlane match appstore --readonly // Release&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;--readonly&lt;/span&gt;&lt;/b&gt;를 붙이지 않으면 새로운 인증서를 다시 생성하기 때문에 이미 생성이 됐다면 read only로 불러와야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;  'Error Cloning certificates repo, please make sure you have read access to the repository you want to use' issue 에러 발생&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-22 오전 11.02.44.png&quot; data-origin-width=&quot;1900&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkWOpp/btsGN1RFT5R/wO6iUrHX0eK3nAHRcmYmm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkWOpp/btsGN1RFT5R/wO6iUrHX0eK3nAHRcmYmm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkWOpp/btsGN1RFT5R/wO6iUrHX0eK3nAHRcmYmm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkWOpp%2FbtsGN1RFT5R%2FwO6iUrHX0eK3nAHRcmYmm1%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;1900&quot; height=&quot;196&quot; data-filename=&quot;스크린샷 2024-04-22 오전 11.02.44.png&quot; data-origin-width=&quot;1900&quot; data-origin-height=&quot;196&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파란색으로 나온 부분처럼 다시 레포를 clone한 뒤 username(이전에 설정한 공용 ID를 사용)와 passphrase(Github 상에서 token 재생성 후 확인)을 적어주니 정상적으로 Git Repository에서 Certificate와 Provisioning profile을 받아올 수 있었다.&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;span data-token-index=&quot;0&quot;&gt;  'wrong final block length' and 'password has been deleted' issue 에러 발생&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;항상 발생하는 에러는 아닌 것 같았다. 2개의 다른 컴퓨터에서 시도를 했는데 1개는 정상적으로 넘어갔고, 다른 1개만 문제가 발생했다.&amp;nbsp;&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;&lt;span data-token-index=&quot;0&quot;&gt;MatchFile에 환경 변수를 설정하여 아예 비밀번호 관련 입력을 하지 않도록 했다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713513361799&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;username(&quot;개발자 계정 입력&quot;)

ENV['MATCH_PASSWORD'] = &quot;이전에 입력했던 passphrase 입력&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해놓으면 MATCH_KEYCHAIN_PASSWORD라는 키값으로 passphrase가 입력된다.&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;그리고 ENV의 키 값을 넣을 때 작은 따옴표를 써야 했다.&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;일단 요렇게 MatchFile을 통해 문제는 해결!&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;공식문서에서는 암호화를 권장하긴 했는데 일단 올라가는 Github 레포지토리가 애초에 private으로 되어 있어서 크게 문제가 없을 것으로 판단했고 일단 저렇게 작업을 해서 해결을 했다.&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;&gt;&lt;a href=&quot;https://ios-development.tistory.com/1164&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ios-development.tistory.com/1164&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713513551366&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;[fastlane] fastlane 환경변수 사용 방법 (.env.default 사용하여 가장 간단한 match 사용방법, Certificate 갱&quot; data-og-description=&quot;Fastlane match란? Certificate, Provisioning Profile을 특정 git 레포에 저장해놓고 개발자들 사이 or ci/cd 때 쉽게 관리할 수 있는 기능 mathc명령어를 통해 terminal에서 Apple 개발자 사이트에 접속하여 Certificate &quot; data-og-host=&quot;ios-development.tistory.com&quot; data-og-source-url=&quot;https://ios-development.tistory.com/1164&quot; data-og-url=&quot;https://ios-development.tistory.com/1164&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bBegC9/hyVPLGHr4s/IjraG0VbP6XElN7bgCG4k1/img.png?width=800&amp;amp;height=291&amp;amp;face=0_0_800_291,https://scrap.kakaocdn.net/dn/czhMpl/hyVSWNleog/LolqrVU7DcaUpQ1gucnf61/img.png?width=800&amp;amp;height=291&amp;amp;face=0_0_800_291,https://scrap.kakaocdn.net/dn/eZXkL/hyVPNEu2af/t7hBgL60vK2BQHXf2T3Lok/img.png?width=1638&amp;amp;height=998&amp;amp;face=0_0_1638_998&quot;&gt;&lt;a href=&quot;https://ios-development.tistory.com/1164&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ios-development.tistory.com/1164&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bBegC9/hyVPLGHr4s/IjraG0VbP6XElN7bgCG4k1/img.png?width=800&amp;amp;height=291&amp;amp;face=0_0_800_291,https://scrap.kakaocdn.net/dn/czhMpl/hyVSWNleog/LolqrVU7DcaUpQ1gucnf61/img.png?width=800&amp;amp;height=291&amp;amp;face=0_0_800_291,https://scrap.kakaocdn.net/dn/eZXkL/hyVPNEu2af/t7hBgL60vK2BQHXf2T3Lok/img.png?width=1638&amp;amp;height=998&amp;amp;face=0_0_1638_998');&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;[fastlane] fastlane 환경변수 사용 방법 (.env.default 사용하여 가장 간단한 match 사용방법, Certificate 갱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Fastlane match란? Certificate, Provisioning Profile을 특정 git 레포에 저장해놓고 개발자들 사이 or ci/cd 때 쉽게 관리할 수 있는 기능 mathc명령어를 통해 terminal에서 Apple 개발자 사이트에 접속하여 Certificate&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ios-development.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VA3Lm/btsGOu0j6cA/sfzmFTNGiUieLyyMR2xtO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VA3Lm/btsGOu0j6cA/sfzmFTNGiUieLyyMR2xtO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VA3Lm/btsGOu0j6cA/sfzmFTNGiUieLyyMR2xtO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVA3Lm%2FbtsGOu0j6cA%2FsfzmFTNGiUieLyyMR2xtO0%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;1084&quot; height=&quot;138&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;  'wrong final block length' and 'Couldn't decrypt the repo, please make sure you enter the right password!' issue 에러 발생&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&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;&lt;span data-token-index=&quot;0&quot;&gt;이때 MatchFile에서 환경변수 설정한 부분에서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;키값의 작은 따옴표를 다시 작성해줬더니 잘 동작&lt;/b&gt;&lt;/span&gt;했다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;흠... 작은 따옴표의 인코딩 에러로 인해 문제가 발생했던 것 같다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;  'No matching provisioning profiles found for {Provisioning Profile name}' issue 에러 발생&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-22 오후 12.22.06.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cz9YLg/btsGNaV3NRg/Pi2dJqiqCQcRK9SXv21TLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cz9YLg/btsGNaV3NRg/Pi2dJqiqCQcRK9SXv21TLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cz9YLg/btsGNaV3NRg/Pi2dJqiqCQcRK9SXv21TLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcz9YLg%2FbtsGNaV3NRg%2FPi2dJqiqCQcRK9SXv21TLK%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;1792&quot; height=&quot;274&quot; data-filename=&quot;스크린샷 2024-04-22 오후 12.22.06.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;274&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;p data-ke-size=&quot;size16&quot;&gt;이 경우는 -a로 app identifier를 지정해주면 된다. AppID로 지정해놓은 것과 받아오는 인증서 간 차이가 있는 경우 발생하는 문제이다.&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;&gt;그래도 말한대로&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713765384324&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fastlane match appstore --readonly -a tools.fastlane.app&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 올바른 identifier를 지정해주면 해당 에러는 사라지게 된다.&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;2. &lt;b&gt;Xcode Project의 Target에서 있는 Signing &amp;amp; Capabilities 탭&lt;/b&gt;에서 Provisioning profile 다운로드&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;  No matching provisioning profiles found and can not create a new one because you enabled readonly. Check the output above for more information. 에러 발생&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode &amp;gt; Target &amp;gt; 프로젝트 &amp;gt; Build Setting &amp;gt; Signing &amp;gt; Code Signing Identity에서 Debug / Release 모두 각각 지정해줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(기본 설정은 Auto로 되어 있음)&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;제대로 provisioning profile을 지정하지 못하는 것 같아서 수동으로 지정을 해놓았더니 해당 문제는 해결이 되었다.&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;요것도 1 곳에선 제대로 해결이 되었는데 1 곳에선 제대로 해결이 되지 않아 추가 검토는 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;  &lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;Provisioning profile &quot;{Bundle ID}&quot; doesn't include the currently selected device &quot;{기기 이름}&quot; (identifier {UUID}). &lt;/span&gt;에러 발생&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서를 Fastlane으로 관리한 이후 추가된 기기의 경우 위 에러가 발생하며 빌드가 되지 않는 문제가 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서에 등록된 기기 리스트에선 현재 빌드를 하려는 기기의 UUID가 존재하지 않기 때문에 에러가 발생하는 것이다.&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;1. Apple Developer에서 Device 등록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 개발용 / 배포용 등에 대해 인증서를 갱신한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718689939140&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fastlane match development  --force_for_new_devices true -a {Bundle ID}&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;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 완전히 해결을 하진 못했지만 그래도 1곳에선 새로운 컴퓨터에서 생성한 인증서를 받는 것까지 완료해서 오류에 대한 기억이 가물가물해지기 전에 작성해봤다.&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;&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;그래도 직접 해보며 왜 fastlane을 써야 하고 어떻게 써야 하는지 좀 더 감이 온 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 Fastfile을 능숙히 설정하여 CI / CD까지 적용하진 못했지만 말이다...&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;</description>
      <category>Software Engineering/iOS</category>
      <author>호르댕댕댕</author>
      <guid isPermaLink="true">https://ho8487.tistory.com/149</guid>
      <comments>https://ho8487.tistory.com/149#entry149comment</comments>
      <pubDate>Fri, 19 Apr 2024 17:08:17 +0900</pubDate>
    </item>
    <item>
      <title>[Firebase Crashlytics] dSYM 누락 - Crashlytics에서 --버전의 dSYM 누락을 감지했습니다.</title>
      <link>https://ho8487.tistory.com/148</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요_-001 (7).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pMiuo/btsGHanhQuK/004gHIFctM6m8UpWbv9kZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pMiuo/btsGHanhQuK/004gHIFctM6m8UpWbv9kZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pMiuo/btsGHanhQuK/004gHIFctM6m8UpWbv9kZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpMiuo%2FbtsGHanhQuK%2F004gHIFctM6m8UpWbv9kZK%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;360&quot; height=&quot;360&quot; data-filename=&quot;제목을 입력해주세요_-001 (7).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&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;p data-ke-size=&quot;size16&quot;&gt;크래시리틱스를 사용하다보면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;dSYM 누락을 감지했습니다.&lt;/b&gt;&lt;/span&gt; 라는 경고를 마주칠 수 있다. Firebase에선 이 파일을 기반으로 어떤 코드에서 에러가 발생했는지 확인하기 때문에 누락이 발생했으면 해당 버전에선 아예 어디서 에러가 발생했는지 확인할 방법이 없다.&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;그래서 이런 경고가 발생하면 반드시 dSYM을 추가해줘야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-17 오후 2.47.02.png&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1quh9/btsGGc7bp0n/mr3In2jk4796EhKZNjDsA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1quh9/btsGGc7bp0n/mr3In2jk4796EhKZNjDsA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1quh9/btsGGc7bp0n/mr3In2jk4796EhKZNjDsA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1quh9%2FbtsGGc7bp0n%2Fmr3In2jk4796EhKZNjDsA1%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;1864&quot; height=&quot;610&quot; data-filename=&quot;스크린샷 2024-04-17 오후 2.47.02.png&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;610&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;h2 data-ke-size=&quot;size26&quot;&gt;dSYM 파일은 뭘까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 컴파일러가 우리가 짠 소스 코드를 기계어로 변환할 때 생성이 되며(앱 Archiving 시 생성), 기계어와 소스코드 라인을 매핑하는 정보를 가지고 있다.&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;&gt;이는 각각 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;고유한 UUID로 구분&lt;/b&gt;&lt;/span&gt;이 되어 있다.&amp;nbsp;&lt;/p&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;div style=&quot;list-style-type: unset; background-color: #000000; color: #000000; text-align: unset;&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?hl=ko&amp;amp;platform=ios#check-input-files-setup&quot;&gt;https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?hl=ko&amp;amp;platform=ios#check-input-files-setup&lt;/a&gt;
&lt;figure id=&quot;og_1713335249114&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Crashlytics 대시보드에서 읽을 수 있는 비정상 종료 보고서 받기 &amp;nbsp;|&amp;nbsp; Firebase Crashlytics&quot; data-og-description=&quot;5월 14일, Google I/O에서 Firebase를 다시 만나보세요. 지금 등록하기 의견 보내기 Crashlytics 대시보드에서 읽을 수 있는 비정상 종료 보고서 받기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 &quot; data-og-host=&quot;firebase.google.com&quot; data-og-source-url=&quot;https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?hl=ko&amp;amp;platform=ios#check-input-files-setup&quot; data-og-url=&quot;https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?hl=ko&amp;amp;platform=ios#check-input-files-setup&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?hl=ko&amp;amp;platform=ios#check-input-files-setup&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Crashlytics 대시보드에서 읽을 수 있는 비정상 종료 보고서 받기 &amp;nbsp;|&amp;nbsp; Firebase Crashlytics&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;5월 14일, Google I/O에서 Firebase를 다시 만나보세요. 지금 등록하기 의견 보내기 Crashlytics 대시보드에서 읽을 수 있는 비정상 종료 보고서 받기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;firebase.google.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;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode 15부터 구성이 최신 상태가 아니라면 아래 오류가 발생할 수 있다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;error: Info.plist Error Unable to process Info.plist at path ...&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그럼 어떻게 dSYM 파일을 받을 수 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 AppStoreConnect에서 빌드 파일의 메타 데이터 쪽에 dSYM을 다운로드할 수 있도록 되어 있었지만 현재는 사라진 상태이다.&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;&gt;일단 dSYM 파일을 받기 위해선 Xcode &amp;gt; Window &amp;gt; Organizer에 들어가서 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-17 오후 2.53.02.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHqrcG/btsGHdRUnmv/5vcL5ZsPqnqKhoNkpqKAak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHqrcG/btsGHdRUnmv/5vcL5ZsPqnqKhoNkpqKAak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHqrcG/btsGHdRUnmv/5vcL5ZsPqnqKhoNkpqKAak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHqrcG%2FbtsGHdRUnmv%2F5vcL5ZsPqnqKhoNkpqKAak%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;2004&quot; height=&quot;1228&quot; data-filename=&quot;스크린샷 2024-04-17 오후 2.53.02.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1228&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;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;그럼 저기 보이는 Download Debug Symbols 누르면 되겠네? 라는 마음이 샘솟는다.&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;&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;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sRHIn/btsGIhe7H4p/bS3GZINKS4aoFtl7Khh8TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sRHIn/btsGIhe7H4p/bS3GZINKS4aoFtl7Khh8TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sRHIn/btsGIhe7H4p/bS3GZINKS4aoFtl7Khh8TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsRHIn%2FbtsGIhe7H4p%2FbS3GZINKS4aoFtl7Khh8TK%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;419&quot; height=&quot;290&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;332&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;p data-ke-size=&quot;size16&quot;&gt;혹시 아카이빙 문제인가 해서 다른 아카이브된 버전도 테스트해봤으나 역시 동일한 Alert가 나왔다.&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;/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;스크린샷 2024-04-17 오후 2.56.46.png&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diayt9/btsGGNTD0gp/q8RXZDhjJ4yVpwpYEtCYPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diayt9/btsGGNTD0gp/q8RXZDhjJ4yVpwpYEtCYPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diayt9/btsGGNTD0gp/q8RXZDhjJ4yVpwpYEtCYPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdiayt9%2FbtsGGNTD0gp%2Fq8RXZDhjJ4yVpwpYEtCYPK%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;470&quot; height=&quot;224&quot; data-filename=&quot;스크린샷 2024-04-17 오후 2.56.46.png&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 받고자 하는 아카이빙된 곳을 우클릭해서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Show in Finder&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-17 오후 2.58.01.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9tafx/btsGH1cmWfy/5kdKu42RkbxJSaqVk40om1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9tafx/btsGH1cmWfy/5kdKu42RkbxJSaqVk40om1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9tafx/btsGH1cmWfy/5kdKu42RkbxJSaqVk40om1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9tafx%2FbtsGH1cmWfy%2F5kdKu42RkbxJSaqVk40om1%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;1428&quot; height=&quot;846&quot; data-filename=&quot;스크린샷 2024-04-17 오후 2.58.01.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;846&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;p data-ke-size=&quot;size16&quot;&gt;그러면 이런 식으로 날짜 별로 아카이빙한 기록이 나오고 xcarchive 파일을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-17 오후 2.59.35.png&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/54yLG/btsGI6K0YTz/zvFkRS9WZUKqk9FXiVQ1jK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/54yLG/btsGI6K0YTz/zvFkRS9WZUKqk9FXiVQ1jK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/54yLG/btsGI6K0YTz/zvFkRS9WZUKqk9FXiVQ1jK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F54yLG%2FbtsGI6K0YTz%2FzvFkRS9WZUKqk9FXiVQ1jK%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;660&quot; height=&quot;154&quot; data-filename=&quot;스크린샷 2024-04-17 오후 2.59.35.png&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기서 또 우클릭 후 패키지 내용 보기를 선택하면 dSYM 파일을 확인할 수 있다.&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;여기서 있는 dSYM 파일을 압축해서 Firebase Crashlytics에 올려주면 된다.&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;dSYM 파일들은 각각 고유한 UUID를 가지고 있기 때문에 반드시 배포했을 때 아카이빙했던 파일을 올려줘야 한다&lt;/b&gt;&lt;/span&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID가 다를 경우 Firebase에서 누락 경고가 사라지지 않는다.&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;(다만 여기서 UUID를 미리 확인하는 방법은 찾지 못했다... 혹시 아시는 분 계신다면 알려주세요  )&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <author>호르댕댕댕</author>
      <guid isPermaLink="true">https://ho8487.tistory.com/148</guid>
      <comments>https://ho8487.tistory.com/148#entry148comment</comments>
      <pubDate>Wed, 17 Apr 2024 15:02:05 +0900</pubDate>
    </item>
    <item>
      <title>[유데미 강의 후기] 개발자 영어</title>
      <link>https://ho8487.tistory.com/147</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y17Pg/btsGCVQs5Lg/K1Gp1dgMz40obEY5PENQkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y17Pg/btsGCVQs5Lg/K1Gp1dgMz40obEY5PENQkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y17Pg/btsGCVQs5Lg/K1Gp1dgMz40obEY5PENQkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY17Pg%2FbtsGCVQs5Lg%2FK1Gp1dgMz40obEY5PENQkk%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;720&quot; height=&quot;312&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 9기로 참여 중인 글또에서 유데미 강의를 지원해주셔서 내가 수강했던 강의, 개발자 영어에 대한 후기를 남긴다.&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.udemy.com/course/devenglish/?couponCode=24T3FS41524&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.udemy.com/course/devenglish/?couponCode=24T3FS41524&lt;/a&gt;&lt;/p&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;ChatGPT 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;직접 개발을 할 때 (네이밍, 주석 등등)&lt;/li&gt;
&lt;li&gt;이미 개발된 오픈 소스 코드를 확인할 때&amp;nbsp;&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;심지어 ChatGPT를 쓸 때에도 한글로 검색할 때보단 영어로 질문하고 답변을 받는 것이 좀 더 좋은 답변을 받는 경우도 왕왕 있었다.&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;&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;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 ChatGPT를 어떻게 사용하면 좋은지 RestfulAPI에서 네이밍을 어떻게 하는 것이 좋을지 등 조금 더 실용적인 부분에 대해서도 설명을 해주신다.&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;&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://news.ycombinator.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://news.ycombinator.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713146733858&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Hacker News&quot; data-og-description=&quot;&quot; data-og-host=&quot;news.ycombinator.com&quot; data-og-source-url=&quot;https://news.ycombinator.com/&quot; data-og-url=&quot;https://news.ycombinator.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://news.ycombinator.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://news.ycombinator.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Hacker News&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;news.ycombinator.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.reddit.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.reddit.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713146749625&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;reddit&quot; data-og-description=&quot;Scan this QR code to download the app now Or check it out in the app stores&quot; data-og-host=&quot;www.reddit.com&quot; data-og-source-url=&quot;https://www.reddit.com/&quot; data-og-url=&quot;https://www.reddit.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qSFk2/hyVPI9RKL6/5rZs07mychOKFD3FvL9TA0/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192,https://scrap.kakaocdn.net/dn/wxl6P/hyVPReCxyP/I2aGwSJN2QOwiTzGMceX3k/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192&quot;&gt;&lt;a href=&quot;https://www.reddit.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.reddit.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qSFk2/hyVPI9RKL6/5rZs07mychOKFD3FvL9TA0/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192,https://scrap.kakaocdn.net/dn/wxl6P/hyVPReCxyP/I2aGwSJN2QOwiTzGMceX3k/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192');&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;reddit&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Scan this QR code to download the app now Or check it out in the app stores&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.reddit.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://justfive.news/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://justfive.news/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713146760909&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Just Five News &amp;middot; Latest News&quot; data-og-description=&quot;Just 5 latest headlines of the world you need right now.&quot; data-og-host=&quot;justfive.news&quot; data-og-source-url=&quot;https://justfive.news/&quot; data-og-url=&quot;https://justfive.news/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gFsii/hyVPM5tJwE/ImLSChhezzhsJkbSPKexP0/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/SLd2x/hyVPUCp70o/llbsp4s76E3MngtX3kaDt1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/rzjzQ/hyVPVH6WAE/AV6OAzxfqpL5cTPfga9Gx0/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512&quot;&gt;&lt;a href=&quot;https://justfive.news/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://justfive.news/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gFsii/hyVPM5tJwE/ImLSChhezzhsJkbSPKexP0/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/SLd2x/hyVPUCp70o/llbsp4s76E3MngtX3kaDt1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/rzjzQ/hyVPVH6WAE/AV6OAzxfqpL5cTPfga9Gx0/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512');&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;Just Five News &amp;middot; Latest News&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Just 5 latest headlines of the world you need right now.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;justfive.news&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;레딧은 검색할 때 종종 볼 수 있는 곳이라 이미 알고 있었지만 Hacker News나 Just Five는 처음 접했다.&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;회사에선 GeekNews에 글이 올라오면 슬랙으로 알림을 주고 있어 여기만 확인을 했었는데 역시 다양한 곳들이 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://news.hada.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://news.hada.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713146870265&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;GeekNews - 개발/기술/스타트업 뉴스 서비스&quot; data-og-description=&quot;개발 뉴스, 기술 관련 새소식, 스타트업 정보와 노하우, 세상의 재미난 것들을 좋아하는 사람들을 위한 뉴스 사이트. 이메일 뉴스레터/트위터/슬랙 봇으로 구독 가능&quot; data-og-host=&quot;news.hada.io&quot; data-og-source-url=&quot;https://news.hada.io/&quot; data-og-url=&quot;https://news.hada.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VIXxo/hyVPTcsbne/iCF5wcG0Mfrsd6UqLj6Ik1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://news.hada.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://news.hada.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VIXxo/hyVPTcsbne/iCF5wcG0Mfrsd6UqLj6Ik1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;GeekNews - 개발/기술/스타트업 뉴스 서비스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개발 뉴스, 기술 관련 새소식, 스타트업 정보와 노하우, 세상의 재미난 것들을 좋아하는 사람들을 위한 뉴스 사이트. 이메일 뉴스레터/트위터/슬랙 봇으로 구독 가능&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;news.hada.io&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;</description>
      <category>Software Engineering</category>
      <author>호르댕댕댕</author>
      <guid isPermaLink="true">https://ho8487.tistory.com/147</guid>
      <comments>https://ho8487.tistory.com/147#entry147comment</comments>
      <pubDate>Mon, 15 Apr 2024 17:11:03 +0900</pubDate>
    </item>
    <item>
      <title>글또 프론트엔드 / 모바일 반상회를 다녀오고</title>
      <link>https://ho8487.tistory.com/146</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCjQv0/btsGnzucOYL/sKKLPe7Dn26UKR6ksiVA51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCjQv0/btsGnzucOYL/sKKLPe7Dn26UKR6ksiVA51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCjQv0/btsGnzucOYL/sKKLPe7Dn26UKR6ksiVA51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCjQv0%2FbtsGnzucOYL%2FsKKLPe7Dn26UKR6ksiVA51%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;380&quot; height=&quot;380&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&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;p data-ke-size=&quot;size16&quot;&gt;4월 4일, 어제 글또에서 진행하는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;프론트엔드 / 모바일 반상회&lt;/b&gt;&lt;/span&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPW7PX/btsGmyCygcF/ioRiDA1pWpC48zZnVOjOq1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPW7PX/btsGmyCygcF/ioRiDA1pWpC48zZnVOjOq1/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;IMG_6709.JPG&quot; width=&quot;260&quot; height=&quot;347&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPW7PX/btsGmyCygcF/ioRiDA1pWpC48zZnVOjOq1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPW7PX%2FbtsGmyCygcF%2FioRiDA1pWpC48zZnVOjOq1%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CyNOA/btsGnCYzD3Q/rQgYCYqAuZXkRLK9sFfKG1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CyNOA/btsGnCYzD3Q/rQgYCYqAuZXkRLK9sFfKG1/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-filename=&quot;IMG_6708.JPG&quot; width=&quot;260&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CyNOA/btsGnCYzD3Q/rQgYCYqAuZXkRLK9sFfKG1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCyNOA%2FbtsGnCYzD3Q%2FrQgYCYqAuZXkRLK9sFfKG1%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&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;b&gt;레이아웃은 FlexLayout / PinLayout / Then&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;1부 발표.&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;View를 대하는 클라이언트 개발자의 자세 (신해나라님)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 개발자라면 User Application (프론트엔드 / 모바일 + Flutter, React Native) 개발을 할 것이고 그렇다면 대부분 뷰를 개발하고 있을 것이다.&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;뷰&lt;/b&gt;&lt;/span&gt;`라는 것 자체에 고민을 크게 해본 적은 없었는데, 개발하면서 놓치기 쉬운 접근성 같은 것들을 언급해주시며 `&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;뷰&lt;/b&gt;&lt;/span&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;일단 View는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;어플리케이션과 사용자가 만나는 인터페이스&lt;/b&gt;&lt;/span&gt;로 사용자에게 정보를 제공하고, 사용자에게 정보(User Interaction)를 다시 받는다.&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;신해나라님은 크게 2가지 관점으로 뷰를 말씀해주셨다.&amp;nbsp;&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;b&gt;사용자 관점에서의 뷰&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰 만큼이나 빈번하게 사용하는 말이 바로 UI / UX인데 여기서 UX는 단순히 사용자가 느끼는 경험 정도로만 생각했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;User Experience의 줄인 말이니 말이다. 물론 틀린 말은 아니지만 좀 더 세부적으로 UX에 대해 설명을 해주셨다.&amp;nbsp;&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;1024&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUcj3y/btsGoR1LO2C/bh9VYu3NQW05WUO5XqKbv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUcj3y/btsGoR1LO2C/bh9VYu3NQW05WUO5XqKbv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUcj3y/btsGoR1LO2C/bh9VYu3NQW05WUO5XqKbv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUcj3y%2FbtsGoR1LO2C%2Fbh9VYu3NQW05WUO5XqKbv1%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;729&quot; height=&quot;555&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;780&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;p data-ke-size=&quot;size16&quot;&gt;UX를 말할 때 한 번쯤은 언급되는 이미지라고 하셨는데... 부끄럽지만 어제 처음 봤다  &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 UX를 말할 때 다양하게 고려해야할 부분을 잘 설명해주는 이미지라고 생각해서 가져왔다.&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;신해나라님은 아래 4가지 측면에서 UX 적으로 고려할 수 있다고 말씀주셨다.&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;어떤 사용자가&amp;nbsp;&lt;/li&gt;
&lt;li&gt;어떤 환경에서&lt;/li&gt;
&lt;li&gt;가장 필요한 것을&lt;/li&gt;
&lt;li&gt;가장 좋은 방법으로&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요런 가치들을 잘 제공하고 있으면 좋은 UI / UX라고 볼 수 있을 것이다.&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 style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;클라이언트 개발자가 기여할 수 있는 부분은 여전히 Surface 단, 즉 뷰를 개발하는 것&lt;/b&gt;&lt;/span&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;그렇기 때문에 할 수 있는 것들 2가지를 말씀주셨다.&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;size18&quot;&gt;&lt;b&gt;사용성 개선하기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;37&quot; data-origin-height=&quot;8&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccCkC1/btsGnNlorOV/6aXbBakRUsFQT7pTioSqpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccCkC1/btsGnNlorOV/6aXbBakRUsFQT7pTioSqpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccCkC1/btsGnNlorOV/6aXbBakRUsFQT7pTioSqpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccCkC1%2FbtsGnNlorOV%2F6aXbBakRUsFQT7pTioSqpk%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;37&quot; height=&quot;8&quot; data-origin-width=&quot;37&quot; data-origin-height=&quot;8&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 옆에 이미지와 같은 더보기 버튼이 존재한다고 생각을 해보자. (SF Symbol ellipsis 이미지)&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;그래서 여기에 패딩을 넣어서 탭 이벤트 영역을 넓힌다면 사용성이 좀 더 개선이 될 수 있을 것이다.&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;접근성 개선하기&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;&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;/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;&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;따라서 요런 부분까지 신경을 써줄 때 조금 더 나은 UX가 될 수 있을 것이다.&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;b&gt;개발자 관점에서의 뷰&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;요즘 안드로이드, iOS 가릴 것 없이 핫한 선언형 UI부터 클린 아키텍쳐 등 말이다.&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;&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;&gt;&amp;nbsp;&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;이었다.&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;&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;/ul&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;b&gt;미래의 클라이언트 개발자&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면서 요새 유튜브 주제로도 많이 올라오는 AI 시대에 클라이언트 개발자에 대해 말씀해주셨다.&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;다른 직종들의 경우도 오히려 반복 작업의 경우 아직 사람들이 하는 경우가 많고 오히려 창의적인 것을 요구하는 예술, 음악 쪽에서 AI가 활약하고 있는 경우도 많기 떄문에 오히려 클라이언트 개발자는 수명이 더 길수도 있지 않을까 말씀해주셨다.&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;그리고 Vision OS도 등장하면서 새로운 형태의 것을 요구하는 클라이언트 개발자가 생길 수도 있고 말이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vision OS는 나도 관심이 있고, 최근 컨퍼런스에서 종종 시도해보시는 분들도 계시던데... 역시 SwiftUI를 좀 더 공부해야 하나 싶다 ㅎㅎ&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;/li&gt;
&lt;li&gt;뷰 이슈 대응&lt;/li&gt;
&lt;li&gt;기종별 호환성 검사&lt;/li&gt;
&lt;li&gt;새로운 View 프레임워크가 나온 경우 이를 AI에 데이터 학습 시킴&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;항상 내가 하는 일과 맞닿아 있고, 알고는 있었지만 깊게 신경쓰진 못했던 부분을 다시 한 번 듣고 상기할 수 있다는 측면에서 되게 좋았다! 그리고 특정 플랫폼에 묶이지 않고 모두가 잘 들을 수 있는 발표를 너무 잘 준비해주셔서 좋았다.&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;&lt;b&gt;기술 블로그를 위한 SEO (정종윤님)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wormwlrm.github.io/2023/05/07/SEO-for-Technical-Blog.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wormwlrm.github.io/2023/05/07/SEO-for-Technical-Blog.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712277287334&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;기술 블로그를 위한 SEO - 재그지그의 개발 블로그&quot; data-og-description=&quot;개인 기술 블로그에 적용한 SEO 방법들을 소개하고, 그 결과물을 공유합니다.&quot; data-og-host=&quot;wormwlrm.github.io&quot; data-og-source-url=&quot;https://wormwlrm.github.io/2023/05/07/SEO-for-Technical-Blog.html&quot; data-og-url=&quot;https://wormwlrm.github.io/2023/05/07/SEO-for-Technical-Blog.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bGPEOT/hyVJX7o5JN/TSGlIv5LaCZOXhEMtgktK1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/qEV9e/hyVJZc59W0/n2DK9bLtlf8D1fY95UUuyk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bbFGCc/hyVJUQmN0i/NXksSMMwxQgekIeCsYsX81/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://wormwlrm.github.io/2023/05/07/SEO-for-Technical-Blog.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wormwlrm.github.io/2023/05/07/SEO-for-Technical-Blog.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bGPEOT/hyVJX7o5JN/TSGlIv5LaCZOXhEMtgktK1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/qEV9e/hyVJZc59W0/n2DK9bLtlf8D1fY95UUuyk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bbFGCc/hyVJUQmN0i/NXksSMMwxQgekIeCsYsX81/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;기술 블로그를 위한 SEO - 재그지그의 개발 블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개인 기술 블로그에 적용한 SEO 방법들을 소개하고, 그 결과물을 공유합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wormwlrm.github.io&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;요건 발표 제목만 보고 정말 관심이 갔던 부분이었다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그에 140개 정도 글을 올렸지만 아직 조회수가 너무 미비했기 때문이다...&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-05 오전 9.44.14.png&quot; data-origin-width=&quot;1788&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XCrNu/btsGoVbZA4W/6mO87gLt9qiDyJwdWcLwqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XCrNu/btsGoVbZA4W/6mO87gLt9qiDyJwdWcLwqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XCrNu/btsGoVbZA4W/6mO87gLt9qiDyJwdWcLwqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXCrNu%2FbtsGoVbZA4W%2F6mO87gLt9qiDyJwdWcLwqK%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;1788&quot; height=&quot;670&quot; data-filename=&quot;스크린샷 2024-04-05 오전 9.44.14.png&quot; data-origin-width=&quot;1788&quot; data-origin-height=&quot;670&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;그래서 종윤님은 어떻게 SEO 작업을 하셨는지 더욱 궁금해졌다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEO는 &lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&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;&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;여기서 개선할 수 있는 부분을 3가지로 나눠서 말씀해주셨다.&lt;/span&gt;&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;온 페이지 SEO : 콘텐츠의 구성요소를 개선
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/li&gt;
&lt;li&gt;분량은 콘텐츠 품질에 직접적인 영향을 주진 않음&lt;/li&gt;
&lt;li&gt;다루고자 하는 주제를 나의 관점에서 바라본 콘텐츠로 제작&lt;/li&gt;
&lt;li&gt;HTML 태그 수정 (타이틀 태그 / 메타 태그 / OG 태그 - 미리보기)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테크니컬 SEO : 크롤링이 잘되게 하고 인덱스를 생성하기까지 과정 개선
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사이트 맵 (웹사이트 구조가 담긴 XML 파일) &amp;rarr; 티스토리는 기본 제공&lt;/li&gt;
&lt;li&gt;RSS : 최근 발행한 컨텐츠 정보 &amp;rarr; 티스토리도 제공&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;/li&gt;
&lt;li&gt;오프 페이지 SEO
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백링크(다른 사람들이 내 글의 링크를 자신의 콘텐츠에 넣었을 때)가 많을수록 평판이 좋다고 평가 (SNS에 올리는 것이 효과 좋음)&lt;/li&gt;
&lt;li&gt;기술 블로그는 평일 오전 9~10시 오후 2~4시에 가장 트래픽이 많음&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;size16&quot;&gt;여기서 가장 효과가 좋았다고 느끼시는 부분은 SNS 공유라고 하셨다!&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;사이트맵 / RSS는 일단 기본적으로 제공되는 기능을 사용하고 있고 구글 서치 콘솔과 네이버 서치 어드바이저의 경우 이미 등록을 해놔서 요 부분에선 크게 개선할 부분은 많지 않다고 생각이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(사이트맵, RSS는 다시 한 번 점검해보긴 해야겠다)&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;h2 data-ke-size=&quot;size26&quot;&gt;2부 네트워킹 + 레크리에이션&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;진짜 이거 어떻게 아나 싶은 문제들이었는데 다른 팀들은 엄청 잘 맞추신... ㅋㅋㅋ 대단하시다는 생각이 들었다.&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;레크리에이션이 끝나고는 네트워킹을 진행했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 속했던 팀에는 따로 iOS 분은 안 계셔서 그 부분은 아주 조금 아쉬었지만 그래도 평소에 이야기를 잘 해보지 못했던 백엔드, Flutter, 프론트엔드 개발자 분들과 이야기를 해볼 수 있어서 좋았다.&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;/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;주 100시간 연봉 50억, 주 20시간 연봉 5천&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 커리어는 사실 아직 잘 모르겠다... 나도 아직 만 1년 반 정도된 주니어 그 자체이고...&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;커리어를 너무 편협한 시각으로 보고 있었나라는 생각도 들었지만, 현재 내 연차에선 지금의 고민이 맞는게 아닐까 싶기도 했다.&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;&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;/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;주 100시간 연봉 50억, 주 20시간 연봉 5천, 이 주제는 앞선 주제보다는 좀 더 가벼운 주제였다 ㅎㅎㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 주 100시간까지 할 체력은 안된다는 생각에 후자지만... 전자는 폭발적인 성장이 가능할 것 같기도 하다. 물론 바쁘다고 좋은 코드를 잘 짜고 있다는 아니지만 말이다.&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;style6&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;iOS 개발자가 아닌 개발자 분들과 이야기를 나눈 것은 회사를 제외하곤 없었는데 좀 더 다양한 관점을 배울 수 있었던 좋은 기회였다.&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;/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;</description>
      <category>Software Engineering</category>
      <author>호르댕댕댕</author>
      <guid isPermaLink="true">https://ho8487.tistory.com/146</guid>
      <comments>https://ho8487.tistory.com/146#entry146comment</comments>
      <pubDate>Fri, 5 Apr 2024 17:29:15 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Privacy Manifests 대응</title>
      <link>https://ho8487.tistory.com/145</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요_-001 (7).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3TYlZ/btsGeAMT3c9/1QVzgtS6kk4DFbD5RYPdzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3TYlZ/btsGeAMT3c9/1QVzgtS6kk4DFbD5RYPdzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3TYlZ/btsGeAMT3c9/1QVzgtS6kk4DFbD5RYPdzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3TYlZ%2FbtsGeAMT3c9%2F1QVzgtS6kk4DFbD5RYPdzk%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;360&quot; height=&quot;360&quot; data-filename=&quot;제목을 입력해주세요_-001 (7).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&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;h2 data-ke-size=&quot;size26&quot;&gt;갑자기 이런 메일을 받았다면...?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24년 3월 13일 이후로 배포 시 프로덕트에 Privacy Manifests가 누락되어 있다면 애플에서 메일을 보내게 된다.&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;Hello, We noticed one or more issues with a recent submission for App Store review for the following app: {앱 이름}&lt;br /&gt;&lt;br /&gt;Version {현재 배포 버전}&lt;br /&gt;Build {등록해놓은 빌드 버전}&lt;br /&gt;&lt;br /&gt;Although submission for App Store review was successful, you may want to correct the following issues in your next submission for App Store review. Once you&amp;rsquo;ve corrected the issues, upload a new binary to App Store Connect.&lt;br /&gt;&lt;br /&gt;ITMS-91053: Missing API declaration - Your app&amp;rsquo;s code in the&lt;br /&gt;{어디에 어떤 API에 대한 Privacy Manifest가 누락되었는지 설명}&lt;br /&gt;&lt;br /&gt;May 1, 2024, when you upload a new app or app update, you must include a NSPrivacyAccessedAPITypes array in your app&amp;rsquo;s privacy manifest to provide approved reasons for these APIs used by your app&amp;rsquo;s code. For more details about this policy, including a list of required reason APIs and approved reasons for usage, visit: &lt;br /&gt;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api&lt;br /&gt;. Apple Developer Relations&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;24년 5월 1일까지 이에 대한 대응을 반드시 해야 한다고 하고 있기 때문에 대응은 필수적이다.&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;이에 대한 내용은 이미 23년 WWDC Get Started with privacy manifests에서 다룬 적이 있어 해당 내용을 정리하면서 어떤 식으로 대응하고 있는지를 기록하고자 한다.&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;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10060&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10060&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711711711543&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Get started with privacy manifests - WWDC23 - Videos - Apple Developer&quot; data-og-description=&quot;Meet privacy manifests: a new tool that helps you accurately identify the privacy practices of your app's dependencies. Find out how...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10060&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10060&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zLgfG/hyVGJHPIQe/84JxQui5NrApkubeTdGWW1/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10060&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10060&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zLgfG/hyVGJHPIQe/84JxQui5NrApkubeTdGWW1/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&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;Get started with privacy manifests - WWDC23 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Meet privacy manifests: a new tool that helps you accurately identify the privacy practices of your app's dependencies. Find out how...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.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;h2 data-ke-size=&quot;size26&quot;&gt;Get Started with privacy manifests&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 정보 보호는 개인의 권리로써 잘 보장되어야 하기 때문에 Apple에선 이를 좀 더 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;투명하고 잘 관리하기 위해 Privacy Nutrition Label과 App Tracking Transparency를 잘 관리하기 위한 Privacy Manifests를 도입&lt;/b&gt;&lt;/span&gt;했다.&amp;nbsp;&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;IMG_1C0C4F286162-1.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NKCik/btsGe6R78OQ/i2XZiAW9qKitDWF6RUTQwK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NKCik/btsGe6R78OQ/i2XZiAW9qKitDWF6RUTQwK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NKCik/btsGe6R78OQ/i2XZiAW9qKitDWF6RUTQwK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNKCik%2FbtsGe6R78OQ%2Fi2XZiAW9qKitDWF6RUTQwK%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;230&quot; height=&quot;498&quot; data-filename=&quot;IMG_1C0C4F286162-1.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 기존에도 앱 스토어 상에 Privacy Nutrition Labels를 제공하고 있었고 App Tracking Transparency를 사용해 데이터를 추적하는 것을 사용자가 제어할 수 있도록 했다.&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;AppTrackingTransparency의 경우 AppTrackingTransparency를 import하여&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711712232318&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ATTrackingManager.requestTrackingAuthorization&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요 함수를 통해 사용자에게 제어할 수 있는 Alert를 보여준다.&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;그런데 요걸 좀 더 투명하게 관리하기 위해 Privacy Manifests가 등장한 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 개인 정보를 써드파티 라이브러리를 통해 관리하는 경우가 꽤 있는데 이제는 써드파티 라이브러리에서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;App Privacy 파일&lt;/b&gt;&lt;/span&gt;을 통해 어떤 데이터를 수집하고 있는지 명확히 밝히도록 해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-29 오후 8.41.06.png&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OdXXA/btsGc5tGIT0/0g1GcMOFu95fzuDt5I2tHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OdXXA/btsGc5tGIT0/0g1GcMOFu95fzuDt5I2tHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OdXXA/btsGc5tGIT0/0g1GcMOFu95fzuDt5I2tHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOdXXA%2FbtsGc5tGIT0%2F0g1GcMOFu95fzuDt5I2tHk%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;270&quot; height=&quot;276&quot; data-filename=&quot;스크린샷 2024-03-29 오후 8.41.06.png&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-29 오후 8.40.30.png&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BoB1M/btsGeR8Gosh/QFjj4eOW8lMRvxamfHFhTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BoB1M/btsGeR8Gosh/QFjj4eOW8lMRvxamfHFhTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BoB1M/btsGeR8Gosh/QFjj4eOW8lMRvxamfHFhTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBoB1M%2FbtsGeR8Gosh%2FQFjj4eOW8lMRvxamfHFhTk%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;326&quot; height=&quot;508&quot; data-filename=&quot;스크린샷 2024-03-29 오후 8.40.30.png&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 사용하는 데이터 타입 / 데이터 타입이 사용되는 방식 / 사용자에게 연결되어 있는 데이터인지 / App Tracking Transparency 정책에 정의된 대로 데이터를 추적하고 있는지 등을 기재해야 한다.&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-20 오후 3.25.45.png&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;1298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X9jVc/btsGdZT3jwN/P6NV2IRfPBWniMkt19ynrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X9jVc/btsGdZT3jwN/P6NV2IRfPBWniMkt19ynrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X9jVc/btsGdZT3jwN/P6NV2IRfPBWniMkt19ynrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX9jVc%2FbtsGdZT3jwN%2FP6NV2IRfPBWniMkt19ynrk%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;1488&quot; height=&quot;1298&quot; data-filename=&quot;스크린샷 2024-03-20 오후 3.25.45.png&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;1298&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;p data-ke-size=&quot;size16&quot;&gt;이러한 정보는 Xcode15부터 아카이빙을 하고 난 이후 Generate Privacy Report를 통해 한 눈에 확인할 수 있다.&amp;nbsp;&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;스크린샷 2024-03-20 오후 3.31.55.png&quot; data-origin-width=&quot;2128&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O4BI1/btsGd0rRhl3/clmbv0byjTlmBjkJqznlCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O4BI1/btsGd0rRhl3/clmbv0byjTlmBjkJqznlCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O4BI1/btsGd0rRhl3/clmbv0byjTlmBjkJqznlCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO4BI1%2FbtsGd0rRhl3%2Fclmbv0byjTlmBjkJqznlCk%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;2128&quot; height=&quot;590&quot; data-filename=&quot;스크린샷 2024-03-20 오후 3.31.55.png&quot; data-origin-width=&quot;2128&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 앱 전체적으로 어떤 Privacy Nutrition Label을 사용했는지 한 번에 PDF 형식으로 report를 만들어서 보여준다.&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;Privacy Nutrition Label의 경우 아래 문서에서 리스트를 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711713490453&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Describing data use in privacy manifests | Apple Developer Documentation&quot; data-og-description=&quot;Declare the data collected by your app or by third-party SDKs.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/s8qg2/hyVGEfmTgE/YmoBaIOXRxdkzuaF7yLHx0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/qCpVC/hyVGHpDFph/HT8PbRbbSTr4dCPj2qFQwk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/s8qg2/hyVGEfmTgE/YmoBaIOXRxdkzuaF7yLHx0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/qCpVC/hyVGHpDFph/HT8PbRbbSTr4dCPj2qFQwk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;Describing data use in privacy manifests | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Declare the data collected by your app or by third-party SDKs.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.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;AppStore Connnect에서 앱의 개인 정보 보호 세부 정보를 제공할 때 해당 보고서를 참고하면 써드파티에서 사용 중인 개인정보까지 누락없이 작성하는데 도움이 될 것이다.&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;그리고 일부 써드파티 SDK의 경우 사용자 추적 권한을 부여하지 않더라도 개인정보를 추적할 수 있다. 이때 수동으로 앱 추적을 비활성화해야 하는데 iOS 17부턴 요런 도메인을 Privacy Manifest에 등록해놓으면 AppTrackingTransparency를 거절한 경우 자동으로 해당 도메인에 대한 연결을 차단하여 이런 엣지 케이스를 방지할 수도 있다.&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;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Xcode Instrument의 Network에서 Points of Interest&lt;/b&gt;&lt;/span&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;a href=&quot;https://developer.apple.com/documentation/xcode/detecting-when-your-app-contacts-domains-that-may-be-profiling-users&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/xcode/detecting-when-your-app-contacts-domains-that-may-be-profiling-users&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711713081291&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Detecting when your app contacts domains that may be profiling users | Apple Developer Documentation&quot; data-og-description=&quot;Use Instruments to assess whether your app or its third-party SDKs connect to domains that may profile users.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/xcode/detecting-when-your-app-contacts-domains-that-may-be-profiling-users&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/xcode/detecting-when-your-app-contacts-domains-that-may-be-profiling-users&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/byCwgY/hyVGOPWojU/M6G1p3k0MIKmXsuFCWUJmK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b2fzD8/hyVGQfR04G/Dkst1a5HsvBBky70gE5STK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xcode/detecting-when-your-app-contacts-domains-that-may-be-profiling-users&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/xcode/detecting-when-your-app-contacts-domains-that-may-be-profiling-users&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/byCwgY/hyVGOPWojU/M6G1p3k0MIKmXsuFCWUJmK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b2fzD8/hyVGQfR04G/Dkst1a5HsvBBky70gE5STK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;Detecting when your app contacts domains that may be profiling users | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Use Instruments to assess whether your app or its third-party SDKs connect to domains that may profile users.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.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;(내가 확인했을 때에는 우리 앱에선 발견하지 못했다. 그래서 정확히 어떤 상황일 때, Network instrument에 잡히는지는 확인하지 못했다)&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;그리고 Apple에선 필수로 사용 이유를 밝혀야 하는 API 목록에 대해서도 언급을 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711713221410&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Describing use of required reason API | Apple Developer Documentation&quot; data-og-description=&quot;Ensure your use of covered API is consistent with policy.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ckpl0e/hyVGLr4u0c/Vfz4uT116kmdTQyr6cfNu1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/owNKm/hyVGNXGC5f/BLTbPiqVnyTK3jzC09zh8K/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ckpl0e/hyVGLr4u0c/Vfz4uT116kmdTQyr6cfNu1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/owNKm/hyVGNXGC5f/BLTbPiqVnyTK3jzC09zh8K/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;Describing use of required reason API | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Ensure your use of covered API is consistent with policy.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.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;위 문서에선 아래 API 리스트를 언급하고 있다.&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;File timestamp APIs&lt;/li&gt;
&lt;li&gt;System boot time APIs&lt;/li&gt;
&lt;li&gt;Disk space APIs&lt;/li&gt;
&lt;li&gt;Active keyboard APIs&lt;/li&gt;
&lt;li&gt;User defaults APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 API를 사용했다면 반드시 App Privacy를 만들어서 어떤 API를 사용했는지 밝히고 사용한 이유를 기재해야 한다.&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;그런데 여기서 이유가 다양했는데 실제로 Xcode 상에선 해당 이유가 전부 있진 않았다. 최대한 사용한 이유에 부합하게 작성을 해놓긴 했는데 만약 리젝을 당한다면 다음 콘텐츠에 이를 작성해보고자 한다.&amp;nbsp;&lt;/p&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;&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;&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;써드파티 SDK에 privacy manifests 요청&lt;/li&gt;
&lt;li&gt;App Store Connnect에서 Xcode Privacy Report를 통해 정확한 Privacy Nutrition Labels 제공&lt;/li&gt;
&lt;li&gt;tracking domain을 선언하고 required reason API를 사용하고 있는지 확인 후 사용한다면 이를 Privacy Manifest에 기재&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;써드파티 SDK에 privacy manifests 요청&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;&lt;a href=&quot;https://developer.apple.com/support/third-party-SDK-requirements/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/support/third-party-SDK-requirements/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711713760491&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Upcoming third-party SDK requirements - Support - Apple Developer&quot; data-og-description=&quot;Feedback Assistant Submit feedback, report bugs, and request enhancements to APIs and developer tools. Send us feedback&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/support/third-party-SDK-requirements/&quot; data-og-url=&quot;https://developer.apple.com/support/third-party-SDK-requirements/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/support/third-party-SDK-requirements/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/support/third-party-SDK-requirements/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Upcoming third-party SDK requirements - Support - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Feedback Assistant Submit feedback, report bugs, and request enhancements to APIs and developer tools. Send us feedback&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.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;RxSwift, Snapkit, Alamofire, Firebase 등등 말이다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;지원 현황 (2024.03.27 기준)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Firebase : &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;v 10.22.0&lt;/span&gt;&lt;/b&gt;에 대응&lt;/li&gt;
&lt;li&gt;Google Sign in : 이슈(&lt;a href=&quot;https://github.com/google/GoogleSignIn-iOS/issues/341&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;)만 올라오고 아직 대응 X&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1711713946245&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Support App Privacy Manifest &amp;middot; Issue #341 &amp;middot; google/GoogleSignIn-iOS&quot; data-og-description=&quot;Is your feature request related to a problem you're having? Please describe. Apple required App Privacy manifests until 2025. So GoogleSignIn-iOS SDK that contains an app privacy manifest must be s...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/google/GoogleSignIn-iOS/issues/341&quot; data-og-url=&quot;https://github.com/google/GoogleSignIn-iOS/issues/341&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d1LzUL/hyVGIoEdaN/DnqIkEvLldp6wOM4RkQV0K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/google/GoogleSignIn-iOS/issues/341&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/google/GoogleSignIn-iOS/issues/341&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d1LzUL/hyVGIoEdaN/DnqIkEvLldp6wOM4RkQV0K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;Support App Privacy Manifest &amp;middot; Issue #341 &amp;middot; google/GoogleSignIn-iOS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Is your feature request related to a problem you're having? Please describe. Apple required App Privacy manifests until 2025. So GoogleSignIn-iOS SDK that contains an app privacy manifest must be s...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;KakaoOpenSDK : &lt;s&gt;추후 업데이트 시 공지사항(&lt;a href=&quot;https://devtalk.kakao.com/t/app-store/135599&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;)을 남겨주겠다는 게시글만 존재 (제공 예정이라 함)&lt;/s&gt; -&amp;gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;v2.22.0&lt;/b&gt;&lt;/span&gt; 이상부터 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1711714005615&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;App Store 앱 제출을 위한 개인정보 보호 관련 업데이트(매니페스트) 대응 문의&quot; data-og-description=&quot;안녕하세요~ 카카오로그인API를 사용중인 개발자 입니다. 애플에서 &amp;ldquo;App Store 앱 제출을 위한 개인정보 보호 관련 업데이트(매니페스트)&amp;rdquo; 공지를 하였는데요~ https://developer.apple.com/kr/news/?id=3d8a9y&quot; data-og-host=&quot;devtalk.kakao.com&quot; data-og-source-url=&quot;https://devtalk.kakao.com/t/app-store/135599&quot; data-og-url=&quot;https://devtalk.kakao.com/t/app-store/135599&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d3E90t/hyVGIvqg5l/TdGYOgxeqHikZKbbtdI0Rk/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/fIVcw/hyVGDVaMv3/NwtQcAhb9bOsVZsMcY9Z9k/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800&quot;&gt;&lt;a href=&quot;https://devtalk.kakao.com/t/app-store/135599&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devtalk.kakao.com/t/app-store/135599&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d3E90t/hyVGIvqg5l/TdGYOgxeqHikZKbbtdI0Rk/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/fIVcw/hyVGDVaMv3/NwtQcAhb9bOsVZsMcY9Z9k/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800');&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;App Store 앱 제출을 위한 개인정보 보호 관련 업데이트(매니페스트) 대응 문의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요~ 카카오로그인API를 사용중인 개발자 입니다. 애플에서 &amp;ldquo;App Store 앱 제출을 위한 개인정보 보호 관련 업데이트(매니페스트)&amp;rdquo; 공지를 하였는데요~ https://developer.apple.com/kr/news/?id=3d8a9y&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devtalk.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kingfisher : &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;v 7.9.0&lt;/b&gt;&lt;/span&gt;에 대응&lt;/li&gt;
&lt;li&gt;Moya : Moya는 따로 대응 예정이 없다고 했고 Alamofire는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;v 5.9.0&lt;/b&gt;&lt;/span&gt;에 대응
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Alamofire를 추상화한 라이브러리인 만큼 Alamofire만 지원하면 되나 싶긴 한데 요 부분도 배포 후 피드백을 받아 추가 진행 예정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Facebook : 이슈(&lt;a href=&quot;https://github.com/facebook/facebook-ios-sdk/issues/2264&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;)만 올라오고 아직 대응 X&lt;/li&gt;
&lt;li&gt;SnapKit : &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;v 5.7.0&lt;/b&gt;&lt;/span&gt;에 대응&lt;/li&gt;
&lt;li&gt;SwiftyJSON : 3/20 대응 커밋이 올라왔으나 아직 배포 X&lt;/li&gt;
&lt;li&gt;Swinject : 3/27 대응 커밋이 올라왔으나 아직 배포 X&lt;/li&gt;
&lt;li&gt;Then : 이슈(&lt;a href=&quot;https://github.com/devxoul/Then/issues/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;)만 올라오고 답변 X&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;일단 리스트에 없는 써드파티 라이브러리들도 사용 중인 것들은 찾아봤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase, Kingfisher, Alamofire, Snapkit은 이미 대응을 해줬고 아직 대응 예정이라는 답글만 달아놓은 곳들도 꽤나 많았다.&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;&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://apnspush.com/privacy-manifest-sdks#all-86-required-sdks-and-status-in-supporting-privacy-manifest&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://apnspush.com/privacy-manifest-sdks#all-86-required-sdks-and-status-in-supporting-privacy-manifest&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713480131310&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Apple's Privacy Manifest: 86 third-party SDKs required to add Privacy Manifest and Signature | APNsPush&quot; data-og-description=&quot;Apps uploaded to App Store Connect must be built with Xcode 15 for iOS 17, iPadOS 17, tvOS 17, or watchOS 10, starting April 29, 2024. Starting in spring 2024, you must include the privacy manifest for any SDK listed below when you submit new apps in App S&quot; data-og-host=&quot;apnspush.com&quot; data-og-source-url=&quot;https://apnspush.com/privacy-manifest-sdks#all-86-required-sdks-and-status-in-supporting-privacy-manifest&quot; data-og-url=&quot;https://apnspush.com/privacy-manifest-sdks#all-86-required-sdks-and-status-in-supporting-privacy-manifest&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://apnspush.com/privacy-manifest-sdks#all-86-required-sdks-and-status-in-supporting-privacy-manifest&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://apnspush.com/privacy-manifest-sdks#all-86-required-sdks-and-status-in-supporting-privacy-manifest&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Apple's Privacy Manifest: 86 third-party SDKs required to add Privacy Manifest and Signature | APNsPush&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Apps uploaded to App Store Connect must be built with Xcode 15 for iOS 17, iPadOS 17, tvOS 17, or watchOS 10, starting April 29, 2024. Starting in spring 2024, you must include the privacy manifest for any SDK listed below when you submit new apps in App S&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;apnspush.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;h3 data-ke-size=&quot;size23&quot;&gt;앱 내 대응&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 메일을 받은 경우 해당 &lt;span style=&quot;background-color: #fcfcfc; color: #000000; text-align: left;&quot;&gt;required reason APIs를 사용한 이유와 어떤 API를 사용했는지를 App Privacy 파일에 기재해주면 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #000000; text-align: left;&quot;&gt;그리고 Network Instrument를 통해 tracking domain으로 등록할 도메인이 있는지 확인해주고 어떤 개인정보를 어떤 목적으로 사용했는지 Privacy Nutrition Label에 기재를 해주면 된다.&amp;nbsp;&lt;/span&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;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 1차 대응만 진행한 예정이고 요렇게 대응해도 부족한 부분이 있을 수 있기 때문에 추후 상황에 따라 필요한 내용을 추가로 정리해볼 예정입니다.&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 style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;적어놓은 업데이트된 라이브러리 및 앱 내 대응만 했는데도 현재 관련 메일은 오지 않고 있는 상황&lt;/b&gt;&lt;/span&gt;입니다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(라이브러리에 모두 대응할 필요는 없는건가...?)&lt;/p&gt;</description>
      <category>Software Engineering/iOS</category>
      <author>호르댕댕댕</author>
      <guid isPermaLink="true">https://ho8487.tistory.com/145</guid>
      <comments>https://ho8487.tistory.com/145#entry145comment</comments>
      <pubDate>Fri, 29 Mar 2024 21:17:59 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] 테스트 코드(유닛 테스트, Unit Test)에 대하여</title>
      <link>https://ho8487.tistory.com/144</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-29 오전 11.37.30.png&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;2036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG78sy/btsFscFuHzQ/cA576DzNvQcLt2z85keRk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG78sy/btsFscFuHzQ/cA576DzNvQcLt2z85keRk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG78sy/btsFscFuHzQ/cA576DzNvQcLt2z85keRk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG78sy%2FbtsFscFuHzQ%2FcA576DzNvQcLt2z85keRk0%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;360&quot; height=&quot;359&quot; data-filename=&quot;스크린샷 2024-02-29 오전 11.37.30.png&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;2036&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 회사에서 테스트 코드를 지속적으로 추가해주고 있고, &lt;b&gt;멋쟁이 사자처럼에서&lt;/b&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;/p&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;물론 앱 규모가 작다면 매번 직접 시뮬레이터나 실기기에서 실행을 해보면서 원하는 동작이 정상적으로 작동하는지 테스트할 수도 있을 것이다. 오히려 이렇게 하는게 더 빠를 수도 있다.&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가능하다고 해도 너무 많은 시간이 걸림&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;size16&quot;&gt;이런 상황에서 테스트가 잘 짜여져 있다면 위 한계를 해소할 수 있을 것이다.&amp;nbsp;&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;이다. 이를 통해 안정적인 확장이나 리팩토링이 가능하게 된다.&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;Testable한 코드를 짜기 위해선 의존성에 대해 고민&lt;/b&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;&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;안정적인 프로덕트의 유지 보수를 위해선 테스트 코드가 필요&lt;/b&gt;&lt;/span&gt;하다.&amp;nbsp;&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-02 오전 11.56.47.png&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KYflk/btsFn9cig4e/Mbh9ss3Vl77ty5vBGFIBe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KYflk/btsFn9cig4e/Mbh9ss3Vl77ty5vBGFIBe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KYflk/btsFn9cig4e/Mbh9ss3Vl77ty5vBGFIBe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKYflk%2FbtsFn9cig4e%2FMbh9ss3Vl77ty5vBGFIBe0%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;1890&quot; height=&quot;1022&quot; data-filename=&quot;스크린샷 2024-03-02 오전 11.56.47.png&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;1022&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;p data-ke-size=&quot;size16&quot;&gt;우리가 XCTest 프레임워크를 통해 기본적으로 만든 테스트는 대부분 유닛테스트이고 피라미드 위로 올라갈 수록 점점 사용자가 사용하는 것과 유사해지며 테스트하는 범위가 많아져서 자동적으로 테스트의 수는 줄어든다.&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;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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;프로덕트 전체 플로우&lt;/b&gt;&lt;/span&gt;를 사용자 액션과 유사하게 테스트&lt;/li&gt;
&lt;li&gt;&lt;b&gt;통합 테스트&lt;/b&gt;: &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;여러 개의 모듈, 소프트웨어를 결합&lt;/b&gt;&lt;/span&gt;하여 함께 작동하는 방식의 테스트&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유닛 테스트&lt;/b&gt;: 객체, 메서드 등 프로덕트의 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;개별 구성 요소&lt;/b&gt;&lt;/span&gt;를 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Unit Test 추가하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unit Test를 추가하는 것 자체는 아주 간단하다.&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;새롭게 프로젝트를 생성하는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 프로젝트를 생성하는 경우는 아래처럼 Include Tests를 체크한 후 생성을 해주면 Test Target이 자동으로 추가된다.&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;1352&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t9Cfi/btsFq0eC9cY/zgFSOgdljuiZO0UOQx8YzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t9Cfi/btsFq0eC9cY/zgFSOgdljuiZO0UOQx8YzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t9Cfi/btsFq0eC9cY/zgFSOgdljuiZO0UOQx8YzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft9Cfi%2FbtsFq0eC9cY%2FzgFSOgdljuiZO0UOQx8YzK%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;1352&quot; height=&quot;826&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;826&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이미 생성된 프로젝트에 테스트를 추가하는 경우&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌측 Navigator에서 Show the Test Navigator를 선택한 후 좌측 최하단의 + 버튼을 눌러 New Unit Test Target을 선택해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2552&quot; data-origin-height=&quot;2836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wjAU3/btsFlOmAlR0/XUEvpz98KkGrWLu1FZ8OVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wjAU3/btsFlOmAlR0/XUEvpz98KkGrWLu1FZ8OVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wjAU3/btsFlOmAlR0/XUEvpz98KkGrWLu1FZ8OVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwjAU3%2FbtsFlOmAlR0%2FXUEvpz98KkGrWLu1FZ8OVk%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;2552&quot; height=&quot;2836&quot; data-origin-width=&quot;2552&quot; data-origin-height=&quot;2836&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;h2 data-ke-size=&quot;size26&quot;&gt;새로 Test를 만들면 어떻게 되어 있을까?&lt;/h2&gt;
&lt;pre id=&quot;code_1709349199556&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import XCTest

final class GitPracticeTests: XCTestCase {
    override func setUpWithError() throws { }

    override func tearDownWithError() throws { }

    func testExample() throws { }

    func testPerformanceExample() throws {
        measure { }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;import XCTest&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xctest&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/xctest&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709349345273&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;XCTest | Apple Developer Documentation&quot; data-og-description=&quot;Create and run unit tests, performance tests, and UI tests for your Xcode project.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/xctest&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/xctest&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oA88T/hyVulmoPPJ/ycYhCF37ebDfmHg9N4YKTk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/eG2TY/hyVqkJu8tG/hraPXOBWhY0bn5HwxHeRVK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xctest&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/xctest&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oA88T/hyVulmoPPJ/ycYhCF37ebDfmHg9N4YKTk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/eG2TY/hyVqkJu8tG/hraPXOBWhY0bn5HwxHeRVK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;XCTest | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Create and run unit tests, performance tests, and UI tests for your Xcode project.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 자동으로 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;XCTest&lt;/b&gt;&lt;/span&gt;를 import하고 있다.&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;기록된 내용은 Show the Test Navigator / Show the Report Navigator에서 확인이 가능하다. (요 부분은 이후 내용에서도 다룰 예정)&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;XCTestCase&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xctest/xctestcase&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/xctest/xctestcase&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709349386854&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;XCTestCase | Apple Developer Documentation&quot; data-og-description=&quot;The primary class for defining test cases, test methods, and performance tests.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/xctest/xctestcase&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/xctest/xctestcase&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/O5uN9/hyVqtfjrOX/fXcGYk9l9qLSMC6AxzMZ00/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/Q5mYp/hyVujhOuGu/BRvJz0zeW1KMKfXQpMpSPK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xctest/xctestcase&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/xctest/xctestcase&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/O5uN9/hyVqtfjrOX/fXcGYk9l9qLSMC6AxzMZ00/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/Q5mYp/hyVujhOuGu/BRvJz0zeW1KMKfXQpMpSPK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;XCTestCase | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The primary class for defining test cases, test methods, and performance tests.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;h4 data-ke-size=&quot;size20&quot;&gt;setUpWithError&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xctest/xctest/3521150-setupwitherror&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/xctest/xctest/3521150-setupwitherror&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709349447683&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;setUpWithError() | Apple Developer Documentation&quot; data-og-description=&quot;Provides an opportunity to reset state and to throw errors before calling each test method in a test case.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/xctest/xctest/3521150-setupwitherror&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/xctest/xctest/3521150-setupwitherror&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cKhskU/hyVugZHnvC/kwOkrNOpdrkKTe4a9KovC1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bxyYvm/hyVqf9ea4W/zAZQDXvsP639soTbKiD0pk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xctest/xctest/3521150-setupwitherror&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/xctest/xctest/3521150-setupwitherror&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cKhskU/hyVugZHnvC/kwOkrNOpdrkKTe4a9KovC1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bxyYvm/hyVqf9ea4W/zAZQDXvsP639soTbKiD0pk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;setUpWithError() | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Provides an opportunity to reset state and to throw errors before calling each test method in a test case.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.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;테스트를 시작하기 위해 필요한 의존성 등을 준비함 (각 테스트가 시작하기 전 호출)&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;tearDownWithError&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xctest/xctest/3521151-teardownwitherror&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/xctest/xctest/3521151-teardownwitherror&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709349494740&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;tearDownWithError() | Apple Developer Documentation&quot; data-og-description=&quot;Provides an opportunity to perform cleanup and to throw errors after each test method in a test case ends.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/xctest/xctest/3521151-teardownwitherror&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/xctest/xctest/3521151-teardownwitherror&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Fmvi3/hyVqtTU6HC/jIJ8Vk6S3RHn3SvcM6OAFk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cBm4Lv/hyVqrofX1Z/J9X3Kdkk6KPF945kdSiggK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xctest/xctest/3521151-teardownwitherror&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/xctest/xctest/3521151-teardownwitherror&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Fmvi3/hyVqtTU6HC/jIJ8Vk6S3RHn3SvcM6OAFk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cBm4Lv/hyVqrofX1Z/J9X3Kdkk6KPF945kdSiggK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;tearDownWithError() | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Provides an opportunity to perform cleanup and to throw errors after each test method in a test case ends.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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-filename=&quot;스크린샷 2024-03-02 오후 12.18.35.png&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFWtLy/btsFoaCm8wW/xdinuKDZfHS6u6rqvKsgN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFWtLy/btsFoaCm8wW/xdinuKDZfHS6u6rqvKsgN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFWtLy/btsFoaCm8wW/xdinuKDZfHS6u6rqvKsgN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFWtLy%2FbtsFoaCm8wW%2FxdinuKDZfHS6u6rqvKsgN0%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;614&quot; height=&quot;406&quot; data-filename=&quot;스크린샷 2024-03-02 오후 12.18.35.png&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;740&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;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;h3 data-ke-size=&quot;size23&quot;&gt;왜 매번 테스트를 위한 준비를 새롭게 하고 다시 없애는 것일까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;테스트는 서로 영향을 주고 받지 않아야하기 때문&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;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;그러므로 테스트를 할 때 기본적으로 새롭게 인스턴스를 만들고 테스트가 끝나면 이를 초기화하는 과정으로 설계가 됐다고 생각한다.&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 XCTest 프레임워크의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;XCTAssert~&lt;/b&gt;&lt;/span&gt; 함수들을 사용해 결과를 검증하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(XCTAssertEqual, XCTAssertTrue, XCTAssertNoNil 등 다양함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709354125214&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func add(number1: Int, number2: Int) -&amp;gt; Int {
    number1 + number2
}

func test_add_two_number() {
    let number1 = 1
    let number2 = 3

    let result = add(number1: number1, number2: number2)
    let expectation = 4

    XCTAssertEqual(result, expectation)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Test는 새로운 타겟이다?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG2fNw/btsFt7qMYWy/eru2MHRcUnkXFjaFr84S0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG2fNw/btsFt7qMYWy/eru2MHRcUnkXFjaFr84S0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG2fNw/btsFt7qMYWy/eru2MHRcUnkXFjaFr84S0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG2fNw%2FbtsFt7qMYWy%2Feru2MHRcUnkXFjaFr84S0k%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;233&quot; height=&quot;132&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 생성해보면 알겠지만 Test는 기존 프로덕트와는 다른 새로운 타겟이다.&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 style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;open / public&lt;/b&gt;&lt;/span&gt;으로 선언되어있지 않다면 이에 접근 자체가 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요에 따라 접근 제어자를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;open / public&lt;/b&gt;&lt;/span&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;&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;그래서 Apple은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;@testable&lt;/b&gt;&lt;/span&gt;를 제공한다.&amp;nbsp;&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;@testable&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YTeYM/btsFm2dEEUb/WLudrMBarWddoNUw7KtwWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YTeYM/btsFm2dEEUb/WLudrMBarWddoNUw7KtwWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YTeYM/btsFm2dEEUb/WLudrMBarWddoNUw7KtwWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYTeYM%2FbtsFm2dEEUb%2FWLudrMBarWddoNUw7KtwWk%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;2136&quot; height=&quot;496&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 빌드 옵션을 보면 Enable Testability를 보면 Debug는 yes라고 되어 있다.&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;이는 컴파일을 할 때 Xcode에 자동으로 enable-testing 플래그를 두고 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;internal 보다 높은 접근 제어자의 경우 open으로 높여서 외부에서 접근이 가능&lt;/b&gt;&lt;/span&gt;하도록 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(fileprivate, private으로 선언한 경우 이는 접근제어자가 유지)&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;pre id=&quot;code_1709355247657&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@testable import {프로덕트 모듈 이름}&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;h2 data-ke-size=&quot;size26&quot;&gt;단위 테스트 구성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Given - When - Then 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AAA 패턴도 존재하지만 요게 좀 더 직관적으로 이름이 이해되기 때문에 해당 패턴을 주로 사용했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709355718976&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func add(_ number1: Int, _ number2: Int) -&amp;gt; Int {
    number1 + number2
}

func test_add_two_number() {
	// Given
    let number1 = 1
    let number2 = 3

	// When
    let result = add(number1, number2)

	// Then
    let expectation = 4

    XCTAssertEqual(result, expectation)
}&lt;/code&gt;&lt;/pre&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;Given : 해당 유닛테스트를 위해 필요한 값이나 객체 등을 준비
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setUpWithError는 공통적으로 준비해야 될 부분을 작성하고 여기선 개별적으로 필요한 것들을 준비시킨다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;When : 테스트할 메서드 실행&lt;/li&gt;
&lt;li&gt;Then : 실행한 메서드의 결과를 검증&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;이름과 설명을 보면 어느정도 직관적으로 와닿을 것이다.&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;모든 테스트에 Given / When / Then이 포함되지는 않아도 된다. (Given 같은 경우 상황에 따라 빠질 수도 있음)&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;sut&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드 예시를 보면 sut라는 단어를 본 적이 있을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 Swift에서만 사용되는 것이 아니며 소프트웨어 전반에서 공통적으로 사용되는 용어이다.&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;System Under Test&lt;/b&gt;의 줄인말로 테스트를 하게 되는 객체를 정의하게 된다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sut의 경우 해당 테스트 전반에서 필요하기 때문에 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;대부분 setUpWithError에 생성하고 tearDownWithError에서 초기화하는 식으로 구성&lt;/b&gt;&lt;/span&gt;된다.&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;테스트 코드 네이밍&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;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비 개발자들이 봤을 때에도 명확히 어떤 행동을 하는지 이해할 수 있는 네이밍
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엄격하게 네이밍 규칙을 정해두는 것보다는 최대한 이해하기 쉬운 네이밍이 더 나을 수 있음 &lt;br /&gt;(개인적으론 테스트 코드는 예외적으로 한글로 네이밍해도 괜찮은 경우가 있었음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;test는 앞에 항상 붙어야 함&lt;/li&gt;
&lt;li&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;요 정도를 염두해두고 최대한 이해하기 쉬운 네이밍을 고민해보는 것이 필요한 것 같다.&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;다양한 방면에서 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕트에 문제가 있음에도 테스트가 이를 발견하지 못한다면 이를 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;거짓 음성&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 테스트를 구성할 때 Then 절을 다양한 방면으로 검증해보는 것이 필요하다.&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;b&gt;TDD(Test Driven Development)&lt;/b&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;물론 나도 실제 개발에서 TDD를 적용해서 개발을 한 경험은 없지만 이렇게 테스트로 미리 함수나 객체가 실패하는 경우부터 성공하는 경우까지 고려를 하고 개발을 하면 앱의 안정성이 엄청 올라갈 수 있고, 설계에서도 좀 더 좋은 설계가 나올 수 있다고 생각하기 때문이다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Test Coverage&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;이를 통해 현재 테스트 코드가 프로덕트를 얼마나 검증하고 있는지 알 수 있다.&amp;nbsp;&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;스크린샷 2024-03-02 오후 2.31.02.png&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oLgzV/btsFn9i7chI/qU1pkKTW65pPdpCVIpDIk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oLgzV/btsFn9i7chI/qU1pkKTW65pPdpCVIpDIk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oLgzV/btsFn9i7chI/qU1pkKTW65pPdpCVIpDIk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoLgzV%2FbtsFn9i7chI%2FqU1pkKTW65pPdpCVIpDIk1%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;1662&quot; height=&quot;390&quot; data-filename=&quot;스크린샷 2024-03-02 오후 2.31.02.png&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;390&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;p data-ke-size=&quot;size16&quot;&gt;Test Coverage는 위 이미지의 역할 정도를 한다.&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;&gt;커버리지는 Show the Report Navigator에서 확인이 가능하다.&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;1346&quot; data-origin-height=&quot;1106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxKmZL/btsFogCw5tv/dYQfzBgyVdg37KzWTM2Zt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxKmZL/btsFogCw5tv/dYQfzBgyVdg37KzWTM2Zt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxKmZL/btsFogCw5tv/dYQfzBgyVdg37KzWTM2Zt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxKmZL%2FbtsFogCw5tv%2FdYQfzBgyVdg37KzWTM2Zt1%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;342&quot; height=&quot;281&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;1106&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;p data-ke-size=&quot;size16&quot;&gt;이는 Edit Scheme을 선택한 후&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;2674&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqON5L/btsFpQXP1ag/xr4EKysCZfaBnqlyw54SuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqON5L/btsFpQXP1ag/xr4EKysCZfaBnqlyw54SuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqON5L/btsFpQXP1ag/xr4EKysCZfaBnqlyw54SuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqON5L%2FbtsFpQXP1ag%2Fxr4EKysCZfaBnqlyw54SuK%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;2674&quot; height=&quot;722&quot; data-origin-width=&quot;2674&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Test Plans를 선택한다.&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;여기서 Configurations 탭을 선택하면 Code Coverage가 on으로 되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;1158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GKqAu/btsFoEDilbx/zwlWWUjvwChIYgfmHM45H0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GKqAu/btsFoEDilbx/zwlWWUjvwChIYgfmHM45H0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GKqAu/btsFoEDilbx/zwlWWUjvwChIYgfmHM45H0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGKqAu%2FbtsFoEDilbx%2FzwlWWUjvwChIYgfmHM45H0%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;593&quot; height=&quot;359&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;1158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인하는 방법이 Xcode가 업데이트를 하면서 이런식으로 변경이 되었다.&amp;nbsp;&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 네트워크 요청은 다른 스레드에 작업을 시키고 작업 종료를 기다리지 않고 다음 작업을 실행하는 &lt;b&gt;비동기&lt;/b&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;2904&quot; data-origin-height=&quot;1226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mzJbf/btsFlOUs2VL/gxcOJvAVvBL9Z1U0wCpljk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mzJbf/btsFlOUs2VL/gxcOJvAVvBL9Z1U0wCpljk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mzJbf/btsFlOUs2VL/gxcOJvAVvBL9Z1U0wCpljk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmzJbf%2FbtsFlOUs2VL%2FgxcOJvAVvBL9Z1U0wCpljk%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;2904&quot; height=&quot;1226&quot; data-origin-width=&quot;2904&quot; data-origin-height=&quot;1226&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;p data-ke-size=&quot;size16&quot;&gt;하지만 XCTAssert~ 쪽에 브레이크 포인트를 걸어놓고 실행을 시켜보면 브레이크 포인트가 걸리지 않는다.&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;비동기 작업이기 때문에 작업이 끝나고 실행되는 completionHandler가 호출되기 전에 test_network 함수가 끝나버리면서 검증을 하지 않고 테스트가 통과했다고 나오는 것이다.&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;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;XCTestExpectation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 사용할 수 있는 것이 XCTestExpectation이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xctest/xctestexpectation&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/xctest/xctestexpectation&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709358388711&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;XCTestExpectation | Apple Developer Documentation&quot; data-og-description=&quot;An expected outcome in an asynchronous test.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/xctest/xctestexpectation&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/xctest/xctestexpectation&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AI32K/hyVqgUBg2Y/loqBeU5y9prkSmgcpEwpX0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/A6Tm6/hyVueOmQis/UVy66TgrUZcUxeHnyZWFVK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xctest/xctestexpectation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/xctest/xctestexpectation&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AI32K/hyVqgUBg2Y/loqBeU5y9prkSmgcpEwpX0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/A6Tm6/hyVueOmQis/UVy66TgrUZcUxeHnyZWFVK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;XCTestExpectation | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An expected outcome in an asynchronous test.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3096&quot; data-origin-height=&quot;1576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X8FM1/btsFqXCevaD/QWrMJ4wrI7cpiu2NBY5dk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X8FM1/btsFqXCevaD/QWrMJ4wrI7cpiu2NBY5dk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X8FM1/btsFqXCevaD/QWrMJ4wrI7cpiu2NBY5dk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX8FM1%2FbtsFqXCevaD%2FQWrMJ4wrI7cpiu2NBY5dk1%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;3096&quot; height=&quot;1576&quot; data-origin-width=&quot;3096&quot; data-origin-height=&quot;1576&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 XCTestExpectation 인스턴스를 선언한 후 테스트 검증 부분에 조건이 모두 충족한 경우 fulfill 함수가 호출되도록 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 fulfill 함수가 호출될 때까지 wait 함수를 통해 해당 XCTestExpectation를 기다리게 하면 된다.&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;&gt;&amp;nbsp;&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;이 있다.&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;그래서 이런 방식은 가능은 하긴 하나 권장되는 방식은 아니다.&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;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사용할 수 있는 방법이 Test Double을 활용하는 것이다.&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;Test Double&lt;/h3&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;/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;위 두 조건을 충족하는 테스트를 짜기 위해 Test Double을 활용할 수 있다.&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;Moya와 Alamofire 같은 외부 라이브러리를 사용한다면 방법이 달라질 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 기본적인 개념 자체는 동일하며 이번 포스팅에선 URLSession을 기반으로 설명하고자 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-02 오후 2.58.15.png&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u5mZr/btsFqNT4l0O/bjbIYLk9ehqRDTgUv9sSPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u5mZr/btsFqNT4l0O/bjbIYLk9ehqRDTgUv9sSPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u5mZr/btsFqNT4l0O/bjbIYLk9ehqRDTgUv9sSPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu5mZr%2FbtsFqNT4l0O%2FbjbIYLk9ehqRDTgUv9sSPK%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;1814&quot; height=&quot;636&quot; data-filename=&quot;스크린샷 2024-03-02 오후 2.58.15.png&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;636&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;h4 data-ke-size=&quot;size20&quot;&gt;Mock과 Stub&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mock의 경우 행동 검증이다. ex) URLSession의 dataTask 함수 동작을 대체&lt;/li&gt;
&lt;li&gt;Stub의 경우 상태 검증이다. ex) 통신을 통해 오는 데이터 자체를 대체&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;이 2가지 개념은 혼용되어서 사용하는 경우들이 종종 있고, 나 또한 그런 경험이 있어 잘 분류하고 사용하는 것이 중요하다.&amp;nbsp;&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;1. URLSession 추상화하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 URLSession을 MockURLSession으로 대체해서 사용해야 하기 때문에 이를 한 번 추상화해놓는 것이 좋다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에선 URLSession의 dataTask 함수만 프로토콜에 정의해놓고 사용했는데 추가로 반드시 정의해야 하는 함수가 존재한다면 미리 정의를 해놓아도 무방하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709359359711&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

protocol URLSessionProtocol {
    func dataTask(
        with request: URLRequest,
        completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -&amp;gt; Void
    ) -&amp;gt; URLSessionDataTask // 기존 dataTask 함수와 동일
}

extension URLSession: URLSessionProtocol { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 이를 사용하는 객체 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상화한 URLSession을 사용할 수 있는 객체를 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 초기화할 때 Mock으로 대체해서 사용하도록 구현한다.&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;pre id=&quot;code_1709359429542&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

final class NetworkManager {
    private let urlSession: URLSessionProtocol
    
    init(urlSession: URLSessionProtocol = URLSession.shared) {
        self.urlSession = urlSession
    }

    func fetchData&amp;lt;T: Decodable&amp;gt;(
        url: URL,
        decodingType: T.Type,
        completionHandler: @escaping((Result&amp;lt;T, Error&amp;gt;) -&amp;gt; ())
    ) {
        let request = URLRequest(url: url)
        let task = urlSession.dataTask(with: request) { data, response, error in
            let successStatusCode = 200..&amp;lt;300
            guard
                let httpResponse = response as? HTTPURLResponse,
                successStatusCode.contains(httpResponse.statusCode)
            else {
                if let error {
                    completionHandler(.failure(error))
                } else {
                    let statusCodeError = NSError(
                        domain: &quot;Network&quot;,
                        code: 0,
                        userInfo: [NSLocalizedDescriptionKey: &quot;StatusCode Error&quot;]
                    )
                    completionHandler(.failure(statusCodeError))
                }
                return
            }
            
            if let error {
                completionHandler(.failure(error))
            }
            
            if let data {
                let decodedData: T
                do {
                    decodedData = try JSONDecoder().decode(T.self, from: data)
                    
                    completionHandler(.success(decodedData))
                } catch {
                    let decodingError = NSError(
                        domain: &quot;Decoding&quot;,
                        code: 0,
                        userInfo: [NSLocalizedDescriptionKey: &quot;Decoding Error&quot;]
                    )
                    completionHandler(.failure(decodingError))
                }
            }
        }
        
        task.resume()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. MockURLProtocolClass 만들기&lt;/h4&gt;
&lt;pre id=&quot;code_1709359607890&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

final class MockURLProtocol: URLProtocol {
    static var requestHandler: ((URLRequest) throws -&amp;gt; (HTTPURLResponse, Data) )?

    override class func canInit(with request: URLRequest) -&amp;gt; Bool {
        return true // request를 처리할 수 있는지 확인하는 부분, Mock이므로 항상 가능하도록 선언
    }

    override class func canonicalRequest(for request: URLRequest) -&amp;gt; URLRequest {
        return request // 동일한 Request인지 확인하고 캐시 작업을 하는 부분인데 여기도 바로 request 전달
    }

    override func stopLoading() { } // request를 호출하는 작업이 중지되며 이후 동작을 정의

    override func startLoading() { // URLProtocolClient로 어떻게 request를 처리할 지 정의
        guard let handler = MockURLProtocol.requestHandler else {
            return
        }
        do {
            let (response, data)  = try handler(request)
            client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
            client?.urlProtocol(self, didLoad: data)
            client?.urlProtocolDidFinishLoading(self)
        } catch  {
            client?.urlProtocol(self, didFailWithError: error)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 초반에 선언된 requestHandler는 나중에 Request를 받아서 어떻게 처리할 지 정의해주면 된다. (Stub 데이터를 보내도록)&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;override한 4개 함수는 공식 문서 상에서 반드시 Subclass에서 정의해야 하는 메서드로 나와 있으나, 없어도 실행이 정상적으로 되긴 했다.&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;일단 Mock 데이터를 간편하게 처리할 수 있도록 따로 캐시 작업 없이 항상 request를 처리하도록 구현해놓았다.&amp;nbsp;&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;4. 결과로 나올 Stub 파일 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 결과로 전달할 Stub 파일을 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 선언해두면 매번 PostMan 등을 통해 결과가 어떻게 전달되는지 확인할 수 있어 문서화의 역할도 할 수 있다.&amp;nbsp;&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;1436&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4SxTe/btsFpPYVh2Y/ej5IZzrcl0oyO8VakQkKiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4SxTe/btsFpPYVh2Y/ej5IZzrcl0oyO8VakQkKiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4SxTe/btsFpPYVh2Y/ej5IZzrcl0oyO8VakQkKiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4SxTe%2FbtsFpPYVh2Y%2Fej5IZzrcl0oyO8VakQkKiK%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;683&quot; height=&quot;227&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터는 JSON 형태로 오는 경우가 많기 때문에 JSON으로 설명을 하자면 이를 위해선 Strings File을 생성한다.&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;이는 원래 Localize를 위해 존재하는 파일인데 해당 파일 선택 후 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;.json&lt;/b&gt;&lt;/span&gt;으로 확장자까지 작성해주면 JSON 파일을 생성할 수 있다.&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;여기에 결과로 나올 JSON 형식의 데이터를 적어주면 된다.&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;776&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu4bb0/btsFm0UpIW4/hsznvG8ixpCLFLEYTqNP4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu4bb0/btsFm0UpIW4/hsznvG8ixpCLFLEYTqNP4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu4bb0/btsFm0UpIW4/hsznvG8ixpCLFLEYTqNP4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu4bb0%2FbtsFm0UpIW4%2FhsznvG8ixpCLFLEYTqNP4K%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;541&quot; height=&quot;233&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;334&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;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;h4 data-ke-size=&quot;size20&quot;&gt;5. Network 처리 객체 선언&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 테스트를 위해 sut로 네트워크 요청을 처리할 객체를 선언해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업은 setUpWithError에서 해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709360001213&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [MockURLProtocol.self]
let urlSession = URLSession(configuration: configuration)

sut = NetworkProvider(session: urlSession)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 URLSessionConfiguration&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ephemeral은 캐시 / 인증 / 쿠키에 따로 저장소를 사용하지 않는 URLSession 구성이다. 우리는 Mock을 사용할 것이기 때문에 이걸 사용했다.&amp;nbsp;&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;&lt;span&gt;그리고 protocolClasses로 선언한 MockURLProtocol의 타입 배열을 할당해주고 이를 통해 URLSession을 만들어주면 된다.&amp;nbsp;&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;6. 이를 활용한 테스트 코드 작성&lt;/h4&gt;
&lt;pre id=&quot;code_1709360132427&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func testExample() throws {
        // Given
        let expectation = XCTestExpectation()
        
        guard
            let path = Bundle(for: type(of: self)).path(forResource: &quot;StubRandomNumber&quot;, ofType: &quot;json&quot;),
            let jsonString = try? String(contentsOfFile: path)
        else {
            XCTFail()

            return
        }
        
        let data = jsonString.data(using: .utf8)
        let stubResponse = HTTPURLResponse(
            url: URL(string: &quot;https://csrng.net/csrng/csrng.php?min=0&amp;amp;max=50&quot;)!,
            statusCode: 200,
            httpVersion: nil,
            headerFields: nil
        )
        
        MockURLProtocol.requestHandler = { request in
            return (stubResponse!, data!)
        }
        
        // When
        sut?.fetchData(url: URL(string: &quot;https://csrng.net/csrng/csrng.php?min=0&amp;amp;max=100&quot;)!, decodingType: [RandomNumber].self, completionHandler: { result in
            switch result {
            case .success(let data):
                XCTAssertEqual(data.first?.min, 0)
                XCTAssertEqual(data.first?.max, 50)
                XCTAssertEqual(data.first?.status, &quot;success&quot;)
                XCTAssertEqual(data.first?.random, 45)
                
                expectation.fulfill()
            case .failure(let error):
                XCTFail()
            }
        })
        
        wait(for: [expectation], timeout: 0.5)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Stub 데이터를 불러와서 이를 utf8 형식의 데이터로 변환후 MockURLProtocol의 requestHandler에서 이 데이터를 항상 내보내도록 구현해주면 끝이다!&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;이를 활용해 테스트를 해보면 네트워크 요청을 직접 하지 않고 Stub으로 선언한 데이터가 항상 반환된다.&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;여기서 wait 함수에 0.5초를 기다리도록 해놨지만 실제 걸리는 시간 자체는 0.004초로 이전 1초 이상 걸리던 것에 비해 비약적으로 빨라진 것을 확인할 수 있다.&amp;nbsp;&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;style6&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;&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;&gt;&lt;a href=&quot;https://ho8487.tistory.com/75&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ho8487.tistory.com/75&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709360341997&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;[iOS] Networking Test (Stub) + RxSwift&quot; data-og-description=&quot;기존 프로젝트를 다시 MVVM + RxSwift로 리팩토링하면서 다시 Networking 작업을 하고 있다. 저번에는 네트워크와 관련된 테스트 코드를 짜지 못했었는데 이번에는 Stub을 이용해 네트워크에 연결되지 &quot; data-og-host=&quot;ho8487.tistory.com&quot; data-og-source-url=&quot;https://ho8487.tistory.com/75&quot; data-og-url=&quot;https://ho8487.tistory.com/75&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhN8hb/hyVqqpn6ax/surNdOaAWD6j5S0Y1CTEik/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/oQuo5/hyVqqiDgms/TYN9BFxxYfJ4xhrnrexJXK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/7oDzc/hyVumFC2PP/Na6GfqSTzP2d8ofw2WP2A0/img.jpg?width=1109&amp;amp;height=1037&amp;amp;face=320_393_713_821&quot;&gt;&lt;a href=&quot;https://ho8487.tistory.com/75&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ho8487.tistory.com/75&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhN8hb/hyVqqpn6ax/surNdOaAWD6j5S0Y1CTEik/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/oQuo5/hyVqqiDgms/TYN9BFxxYfJ4xhrnrexJXK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/7oDzc/hyVumFC2PP/Na6GfqSTzP2d8ofw2WP2A0/img.jpg?width=1109&amp;amp;height=1037&amp;amp;face=320_393_713_821');&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;[iOS] Networking Test (Stub) + RxSwift&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;기존 프로젝트를 다시 MVVM + RxSwift로 리팩토링하면서 다시 Networking 작업을 하고 있다. 저번에는 네트워크와 관련된 테스트 코드를 짜지 못했었는데 이번에는 Stub을 이용해 네트워크에 연결되지&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ho8487.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 작성한 방식은 예전에 사용하던 방식이라 이렇게 작성하게 되면 코드에 warning이 뜨게 되어 개선된 버전으로 Test Double에 해당하는 방식도 변경해봤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이때는 Stub과 Mock도 혼재해서 사용했군...)&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;</description>
      <category>Software Engineering/iOS</category>
      <author>호르댕댕댕</author>
      <guid isPermaLink="true">https://ho8487.tistory.com/144</guid>
      <comments>https://ho8487.tistory.com/144#entry144comment</comments>
      <pubDate>Sat, 2 Mar 2024 15:20:19 +0900</pubDate>
    </item>
    <item>
      <title>더 늦기 전에 작성하는 2023년 회고</title>
      <link>https://ho8487.tistory.com/143</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이미 회고를 적기엔 늦은 날짜이지만 더 늦어지면 아예 안 적을 것 같아서 이제와서 23년 회고를 작성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23년은 큰 변동은 없었던 한 해였고, 정말 시간이 어떻게 간 줄 모르게 빠르게 지나간 한 해였다.&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;h2 data-ke-size=&quot;size26&quot;&gt;작년 계획 평가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 작년 &lt;a href=&quot;https://ho8487.tistory.com/113&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;회고&lt;/a&gt;&amp;nbsp;에서 아래 목록을 계획으로 세웠었는데&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1707960163870&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;[회고] 2022년을 돌아보고 앞으로를 계획해보자 (비전공자의 iOS 개발자를 위한 고군분투)&quot; data-og-description=&quot;2022년은 정말 새로운 것들이 많았던 해였다. 진짜 2022년은 한마디로 모든 것이 새로운 한 해 이다. 기존 전공인 부동산학과와도 전혀 무관한 일을 하고 있고, 이전에 했던 인턴인 HR 직무와도 거&quot; data-og-host=&quot;ho8487.tistory.com&quot; data-og-source-url=&quot;https://ho8487.tistory.com/113&quot; data-og-url=&quot;https://ho8487.tistory.com/113&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lmobm/hyVjkvxcg0/XHhHySVyN7drf6Pf1FBUE0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/HxFOj/hyVjnFP8zc/FKVePUiJ3IXaBKkri8Bbuk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/e7f6M/hyVm3eBrjE/z8Dn3mqvyKqutT60Wa9N10/img.jpg?width=1109&amp;amp;height=1037&amp;amp;face=320_393_713_821&quot;&gt;&lt;a href=&quot;https://ho8487.tistory.com/113&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ho8487.tistory.com/113&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lmobm/hyVjkvxcg0/XHhHySVyN7drf6Pf1FBUE0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/HxFOj/hyVjnFP8zc/FKVePUiJ3IXaBKkri8Bbuk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/e7f6M/hyVm3eBrjE/z8Dn3mqvyKqutT60Wa9N10/img.jpg?width=1109&amp;amp;height=1037&amp;amp;face=320_393_713_821');&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;[회고] 2022년을 돌아보고 앞으로를 계획해보자 (비전공자의 iOS 개발자를 위한 고군분투)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2022년은 정말 새로운 것들이 많았던 해였다. 진짜 2022년은 한마디로 모든 것이 새로운 한 해 이다. 기존 전공인 부동산학과와도 전혀 무관한 일을 하고 있고, 이전에 했던 인턴인 HR 직무와도 거&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ho8487.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;SwiftUI 공부하기 &amp;amp; 프로젝트 시도하기 &lt;span style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: left;&quot;&gt; &lt;/span&gt; (60%)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;WWDC 일주일에 한 개 이상 보기   (30%, 특정 주제가 필요할 경우 몰아서 봄)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;꾸준히 알고리즘 문제 풀기 &lt;span style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: left;&quot;&gt;❌ (0%)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;컨퍼런스 꾸준히 참여하기 &lt;span style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: left;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: left;&quot;&gt; (80%)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;운동 꾸준히 하기   (100%)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;라이브러리 Contributor 도전하기 &lt;span style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: left;&quot;&gt;❌ (0%)&lt;/span&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;실천율이 높진 않은 것 같다. ㅎㅎ  &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;SwiftUI는 연말이지만 공부를 시작하긴 했고, 위젯을 구현하면서 회사 프로젝트에서도 간단하게나마 넣어 봤고, 컨퍼런스는 시간이 되는 대로 참석하긴 했으니 애매한 동그라미를 줬다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 다른 길로 빠져나가서 노수진 님의&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://fastcampus.co.kr/dev_red_rsj&quot;&gt;슈퍼앱 운영을 위한 확장성 높은 앱 아키텍처 구축&lt;/a&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: left;&quot;&gt;강의를 듣고 스터디 후 직접 RIBs로 앱을 구현해보기도 했고, 일주일에 1개씩 WWDC 영상을 보진 못했지만, 필요한 주제가 있을 때 몰아서 보기는 했다.&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;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: left;&quot;&gt;당시 계획을 너무 떠오르는 대로 리스트를 나열하고 마무리했다보니 실천도 제대로 되지 않은 것 같다.&lt;/span&gt;&lt;/span&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;&amp;nbsp;&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;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;23년의 학습 (70점)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;학습은 꾸준하게 진행을 했지만 조금 더 많은 것들을 학습하고 적용하지 못했다는 아쉬움에 70점 정도를 줬다.&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;Widget&lt;/b&gt;, &lt;b&gt;Deferred Deeplink&lt;/b&gt;, &lt;b&gt;GitHub Actions&lt;/b&gt; 등을 새롭게 학습하고 직접 도입해봤고, &lt;b&gt;Core Data&lt;/b&gt;도 이전보다 좀 더 심도 있게 사용해봤다. 그리고 특히 메모리 그래프를 확인해보면서 &lt;b&gt;메모리 릭&lt;/b&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;Diffable DataSource&lt;/b&gt;도 다시 학습해보고 사내 발표까지 진행했지만 아직 프로젝트에 직접 도입은 하지 못하였다. 요 부분은 올해 도입을 해서 reload 시 indexPath가 맞지 않아 버그가 나는 부분까지 해결을 해보고 싶다.&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;RIBs&lt;/b&gt;, &lt;b&gt;SwiftUI&lt;/b&gt;에 대해 학습했다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22년 말에 테스트 코드를 회사에 도입하고 올해는 이를 잘 관리하기 위해 Github Actions를 도입했었는데 현재 새로 등장한 ViewIsAppearing 메서드를 사용하면서 테스트를 위한 가상 머신 빌드가 안되는 문제가 있었다.&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;Xcode Cloud를 도입하여 이런 부분을 Xcode Cloud에서 할 수 있도록 하고, 빌드도 이를 통해 할 수 있도록 하는 부분을 현재 검토 중인데 요 부분도 잘 학습하여 팀에 기여해보고 싶다!&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;h2 data-ke-size=&quot;size26&quot;&gt;23년의 활동 (90점)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 23년은 멋쟁이 사자처럼 앱스쿨에서 멘토링 활동을 2기에 걸쳐 진행했다. (1기: 22.12 ~23.02 | 2기: 23.07 ~ 23.11)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Git 관련 특강도 직접 준비해서 진행해봤다.&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;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 부끄럽지만 &lt;a href=&quot;https://www.youtube.com/watch?v=PwRq0tmX6ok&amp;amp;t=2444s&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;현직자가 알려주는 신입 iOS 개발자 취업 전 필수 역량&lt;/a&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;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=PwRq0tmX6ok&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/pyRBi/hyVjafpzsQ/wrhmKLgcIyDAfgxe12eBW1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;현직자가 알려주는 신입 iOS 개발자 취업 전 필수 역량(테킷 앱 스쿨 취준진담)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/PwRq0tmX6ok&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 카카오 퇴근길 밋업, KWDC 23, 리멤버 / 핀다 / 이스트 소프트와 함께하는 개발 모임 등 다양한 컨퍼런스 및 활동을 진행하면서 다른 개발자 분들은 어떻게 개발을 하고 있는지 이야기를 나눠볼 수 있어 좋았다.&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;&gt;(&lt;a href=&quot;https://medium.com/@Jager-yoo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;예거&lt;/a&gt;와 &lt;a href=&quot;https://applecider2020.tistory.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;애플사이다&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;24년에도 꾸준히 개발 관련 활동을 이어 나갈 수 있도록 해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;23년의 취미생활 (100점)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 관련 회고를 하다가 취미생활이 100점이면 오히려 주객전도가 된 느낌이긴 하다...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 23년은 예~전부터 내가 해보고 싶던 것들을 시작했던 한 해라 그래도 이 부분을 빼놓기는 애매했다.&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;테니스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22년에 테니스를 시작하고 정말 꾸준하게 테니스를 치고 있다. (꾸준보다는 조금 더 과하게 치는 것 같기도 하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 동료 분이랑 아마추어 대회도 참가해봤다 (물론 예탈로 3경기만에 집에 돌아왔다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_BEC2B6EEBC12-1.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t9X7m/btsES00B3aS/0MLUUNkvVn91dvoH5XJ0e0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t9X7m/btsES00B3aS/0MLUUNkvVn91dvoH5XJ0e0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t9X7m/btsES00B3aS/0MLUUNkvVn91dvoH5XJ0e0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft9X7m%2FbtsES00B3aS%2F0MLUUNkvVn91dvoH5XJ0e0%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;279&quot; height=&quot;604&quot; data-filename=&quot;IMG_BEC2B6EEBC12-1.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&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;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;h3 data-ke-size=&quot;size23&quot;&gt;일렉기타&lt;/h3&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;IMG_6644.JPG&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boHUIk/btsEOQZlYof/EEGunKeI98OJDylsZoHAeK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boHUIk/btsEOQZlYof/EEGunKeI98OJDylsZoHAeK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boHUIk/btsEOQZlYof/EEGunKeI98OJDylsZoHAeK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboHUIk%2FbtsEOQZlYof%2FEEGunKeI98OJDylsZoHAeK%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;398&quot; height=&quot;531&quot; data-filename=&quot;IMG_6644.JPG&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&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;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;독학으로 기타를 쳐오면서 나는 악기에 재능이 없나 했었는데 막상 하니 그렇진 않은 것 같다.&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;혼자 일본 여행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼자서 여행, 더군다나 혼자서 해외여행은 난생 처음이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음이라 기간을 2박 3일로 짧게 잡았었는데 지금까지의 어떤 여행보다 만족스러웠다.&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;/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;이때부터 일본어를 알면 좀 더 재밌게 다닐 수 있을 것 같아 듀오링고 앱을 통해 일본어 공부도 하고 있다. (현재 92일째 꾸준히 공부중)&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;24년에도 혼자 여행은 다시 한 번 꼭 해보고 싶다.&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;style6&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 코드 스터디&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비즈니스 임팩트가 있는 코드 구현하기&lt;/li&gt;
&lt;li&gt;SwiftUI 스터디&lt;/li&gt;
&lt;li&gt;iOS 컨퍼런스 1회 이상 참여하기&lt;/li&gt;
&lt;li&gt;WWDC 영상 월 1회 보기&lt;/li&gt;
&lt;li&gt;블로그 2주에 1번씩 글 작성하기&lt;/li&gt;
&lt;li&gt;꾸준히 운동하기 (테니스 대회 본선 진출하기)&lt;/li&gt;
&lt;li&gt;혼자 여행가기&lt;/li&gt;
&lt;li&gt;꾸준히 일본어 공부하기&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Software Engineering/iOS</category>
      <author>호르댕댕댕</author>
      <guid isPermaLink="true">https://ho8487.tistory.com/143</guid>
      <comments>https://ho8487.tistory.com/143#entry143comment</comments>
      <pubDate>Thu, 15 Feb 2024 18:00:31 +0900</pubDate>
    </item>
    <item>
      <title>Swift를 기반으로 본 SOLID에 대하여 (객체지향 설계 원칙)</title>
      <link>https://ho8487.tistory.com/142</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요_-001 (6).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eerhUM/btsEkel8pOO/qihaTpfGfZ2l60RXKZVOW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eerhUM/btsEkel8pOO/qihaTpfGfZ2l60RXKZVOW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eerhUM/btsEkel8pOO/qihaTpfGfZ2l60RXKZVOW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeerhUM%2FbtsEkel8pOO%2FqihaTpfGfZ2l60RXKZVOW1%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;360&quot; height=&quot;360&quot; data-filename=&quot;제목을 입력해주세요_-001 (6).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&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;p data-ke-size=&quot;size16&quot;&gt;객체 지향 언어를 사용하다보면 반드시 듣게 되는 원칙 중 하나가 바로 SOLID이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Swift도 객체 지향 언어 중 하나인 만큼 SOLID는 공부하면 만나게 되는 것 중 하나이다)&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 면접 질문 리스트 중에서도 SOLID는 등장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/JeaSungLEE/iOSInterviewquestions&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/JeaSungLEE/iOSInterviewquestions&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1705481924693&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - JeaSungLEE/iOSInterviewquestions:  &amp;zwj; &amp;zwj; iOS 면접에 나올 질문들 총 정리&quot; data-og-description=&quot; &amp;zwj; &amp;zwj; iOS 면접에 나올 질문들 총 정리 . Contribute to JeaSungLEE/iOSInterviewquestions development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/JeaSungLEE/iOSInterviewquestions&quot; data-og-url=&quot;https://github.com/JeaSungLEE/iOSInterviewquestions&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qSJtj/hyU5Pibga9/ZTHbBGMzYiv9b5p6xUKv3K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/JeaSungLEE/iOSInterviewquestions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/JeaSungLEE/iOSInterviewquestions&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qSJtj/hyU5Pibga9/ZTHbBGMzYiv9b5p6xUKv3K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;GitHub - JeaSungLEE/iOSInterviewquestions:  &amp;zwj; &amp;zwj; iOS 면접에 나올 질문들 총 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; &amp;zwj; &amp;zwj; iOS 면접에 나올 질문들 총 정리 . Contribute to JeaSungLEE/iOSInterviewquestions development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-17 오후 5.57.46.png&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUsU8U/btsDDYWKzru/X9oy0Ts3oJTivbkDmmBl3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUsU8U/btsDDYWKzru/X9oy0Ts3oJTivbkDmmBl3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUsU8U/btsDDYWKzru/X9oy0Ts3oJTivbkDmmBl3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUsU8U%2FbtsDDYWKzru%2FX9oy0Ts3oJTivbkDmmBl3K%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;1738&quot; height=&quot;270&quot; data-filename=&quot;스크린샷 2024-01-17 오후 5.57.46.png&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;270&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;p data-ke-size=&quot;size16&quot;&gt;항상 공부는 했지만, 막상 말하려 하면 단일 책임의 원칙(Single Responsibility Principle)만 생각나는 SOLID...&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;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;물론 SOLID 원칙에 대한 블로그 포스팅은 이미 차고 넘치지만... 스스로도 기록하면서 좀 더 잘 알기 위해 정리를 해보기로 했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SOLID 원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 각각의 원칙으로 들어가기 전에 간단히 SOLID 원칙에 대해 살펴보자.&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;일단 SOLID는 객체 지향 프로그래밍을 하면서 어떻게 설계하면 좋은 지에 대해 5가지 원칙으로 정리한 것이다.&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;&gt;이는 다음 5가지 원칙으로 구성되어 있고 SOLID는 이 원칙들의 앞 글자만 가지고 온 것이다.&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;Single Responsibility Principle : 단일 책임의 원칙&lt;/li&gt;
&lt;li&gt;Open / Closed Principle : 개방 폐쇄 원칙&lt;/li&gt;
&lt;li&gt;Liskov Substitution Principle : 리스코프 치환 원칙&lt;/li&gt;
&lt;li&gt;Interface Segregation Principle : 인터페이스 분리의 원칙&lt;/li&gt;
&lt;li&gt;Dependency Inversion Principle : 의존성 역전의 원칙&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Single Responsibility Principle (단일 책임의 원칙)&lt;/h3&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;는 것이다.&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;&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;물론 현재 객체의 설계 원칙인 SOLID에 대해 이야기를 하고 있긴 하나 함수 / 모듈에서도 적용이 되는 개념이다.&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;pre id=&quot;code_1705821520449&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func add(num1: Int, num2: Int) -&amp;gt; Int {
    let answer = num1 + num2
    print(answer)

    return answer
}&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;&gt;예를 들어 2개의 파라미터를 받아서 이를 더해주는 간단한 함수가 있다고 가정해보자.&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;이때 해당 함수 내부에는 결과값을 print해주는 기능까지 포함되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(예제여서 간단한 print를 사용했지만 실제 프로덕트를 만들 때에는 Google Analytics에 결과값을 전달하거나 하는 기능이 있을 수 있다)&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;그렇다면 하나의 함수가 값을 더하는 기능 외에도 print를 하는 기능까지 2가지 책임이 존재하는 것이다.&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;&gt;이를 좀 더 넓은 개념으로 객체에 동일시켜도 객체를 캡슐화한 후 설계할 때 1개 이상의 책임을 가지고 있다면 단일 책임의 원칙을 위배하는 것이다.&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;&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;&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;이를 준수하기 위해선 객체 / 함수를 설계할 때 정확히 추상화 / 캡슐화를 하고 설계를 하는 것도 도움이 되겠지만 러프한 기준을 세워본다면 객체와 함수의 Line 수에 상한을 두는 것도 방법이 될 수 있다.&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;그래서 SwiftLint에서도 File / 객체 / 함수의 길이에 기본적인 제한을 둔 것이 아닌가 생각이 든다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://realm.github.io/SwiftLint/rule-directory.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://realm.github.io/SwiftLint/rule-directory.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1705822351318&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Rule Directory  Reference&quot; data-og-description=&quot;&quot; data-og-host=&quot;realm.github.io&quot; data-og-source-url=&quot;https://realm.github.io/SwiftLint/rule-directory.html&quot; data-og-url=&quot;https://realm.github.io/SwiftLint/rule-directory.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://realm.github.io/SwiftLint/rule-directory.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://realm.github.io/SwiftLint/rule-directory.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Rule Directory Reference&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;realm.github.io&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;size18&quot;&gt;- File Length&lt;/p&gt;
&lt;table style=&quot;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 style=&quot;width: 50%;&quot;&gt;warning&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;error&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1000&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;p data-ke-size=&quot;size18&quot;&gt;- Function Body Length&lt;/p&gt;
&lt;table style=&quot;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 style=&quot;width: 50%;&quot;&gt;warning&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;error&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;100&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;p data-ke-size=&quot;size18&quot;&gt;- Type Body Length&lt;/p&gt;
&lt;table style=&quot;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 style=&quot;width: 50%;&quot;&gt;warning&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;error&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;350&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Open / Closed Principle (개방 폐쇄의 원칙)&lt;/h3&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;이다.&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;&gt;개발을 하다보면 요구사항이 변경되는 경우가 종종(자주...?) 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 변경으로 인한 비용은 가능한 줄이고(Closed), 기존 구성 요소를 쉽게 확장(Open)하여 재사용이 가능하도록 해야 한다는 것이다.&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;pre id=&quot;code_1705822921500&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Guitar {
    enum Shape {
        case lespaul
        case strat
    }
    
    let shape: Shape

	init(shape: Shape) {
    	self.shape = shape
    }
    
    func pickUp() -&amp;gt; String {
    	switch shape {
        case .lespaul:
        	return &quot;humbucker&quot;
        case .strat:
        	return &quot;single&quot;
        }
    }
}

Guitar(shape: .lespaul).pickup() // &quot;humbucker&quot;
Guitar(shape: .strat).pickup() // &quot;single&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;&gt;요렇게 기타 타입에 따라 어떤 픽업이 달려있는지를 보여주는 Computed Property가 있다고 생각해보자.&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;801&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbvH0a/btsEkv8MzXe/JkIresW9SgzVeXCBR9V1jk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbvH0a/btsEkv8MzXe/JkIresW9SgzVeXCBR9V1jk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbvH0a/btsEkv8MzXe/JkIresW9SgzVeXCBR9V1jk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbvH0a%2FbtsEkv8MzXe%2FJkIresW9SgzVeXCBR9V1jk%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;182&quot; height=&quot;1200&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;1200&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zuy2p/btsEmP6qK2Z/607naiAb9JGEZ6Bcxd9erk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zuy2p/btsEmP6qK2Z/607naiAb9JGEZ6Bcxd9erk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zuy2p/btsEmP6qK2Z/607naiAb9JGEZ6Bcxd9erk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzuy2p%2FbtsEmP6qK2Z%2F607naiAb9JGEZ6Bcxd9erk%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;76&quot; height=&quot;760&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Guitar의 Shape에 종류가 추가된다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 Shape 열거형에도 값을 추가해야 하고 pickUp이라는 프로퍼티도 수정을 해줘야 한다.&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;변경 사항이 생겼을 때에는 Closed되어야 하는데 수정이 필요&lt;/b&gt;&lt;/span&gt;해진 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 위 코드는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;개방-폐쇄 원칙(OCP)을 위배한 사례&lt;/b&gt;&lt;/span&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;추상화를 해서 해결&lt;/b&gt;&lt;/span&gt; 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706874389842&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol Guitar {    
    func pickUp() -&amp;gt; String
}

class Lespaul: Guitar {    
    func pickUp() -&amp;gt; String {
		return &quot;humbucker&quot;
    }
}

class Strat: Guitar {
	func pickUp() -&amp;gt; String {
		return &quot;single&quot;
    }
}

Lespaul().pickup() // &quot;humbucker&quot;
Strat().pickup() // &quot;single&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;&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;  Liskov Substitution principle (리스코프 치환의 원칙)&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;&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;blockquote data-ke-style=&quot;style2&quot;&gt;컴퓨터 프로그램에서 자료형 S가 자료형 T의 서브타입라면 필요한 프로그램의 속성의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 교체할 수 있어야 한다는 원칙&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;이는 위에서 들었던 기타 예시로 좀 더 풀어서 보자면&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;기타에는 Lespaul과 Strat 등 하위 타입들이 존재하고 기존 기타를 Strat으로 바꾸더라도 잘 동작해야 한다는 것이다.&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;pre id=&quot;code_1706875026530&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Guitar {
    func play() {
        print(&quot;딩가딩가&quot;)
    }
}

class Strat: Guitar {
    override func play() {
        print(&quot;징징징&quot;)
    }
}

var guitar = Guitar()
guitar.play() // &quot;딩가딩가&quot;

guitar = Strat()
guitar.play() // &quot;징징징&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;&gt;부모 클래스 타입인 guitar 프로퍼티가 자식 클래스로 대체되더라도 기본적으로 play는 동작을 잘 하는 것을 볼 수 있다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 위에서 설명한 개방 폐쇄의 원칙(OCP)와도 관련이 있으며 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;리스코프 치환 원칙을 위반할 경우 OCP 또한 위반할 가능성이 높아진다&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;size16&quot;&gt;새로운 하위 타입이 상위 클래스를 대체할 수 없도록 구현이 된다면 계속해서 상위 클래스에 대한 수정이 필요할 것이고 그렇다면 이는 OCP를 위반한 것이기 때문이다.&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;  Interface Segregation (인터페이스 분리 원칙)&lt;/h3&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;는 원칙이다. 즉 상속으로 인해 불필요한 구현을 최대한 방지하도록 하는 것이다.&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;pre id=&quot;code_1706921494165&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol Guitar {
    func stroke()
    func arpegio()
}

class Lespaul: Guitar {
    func stroke() { }
    func arpegio() { }
}

class Strat: Guitar {
    func stroke() { }
    func arpegio() { }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에선 Interface가 따로 없지만 프로토콜과 유사한 개념으로 볼 수 있다.&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;이를 프로토콜로 구현하면 다음과 같을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706921850394&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol DrumGuitar {
    func stroke()
    func arpegio()
    func beat()
}

class Lespaul: DrumGuitar {
    func stroke() { }
    func arpegio() { }
    func beat() { } // 사용하지 않는 함수도 구현을 해줘야 함
}

class Strat: DrumGuitar {
    func stroke() { }
    func arpegio() { }
    func beat() { } // 사용하지 않는 함수도 구현을 해줘야 함
}

class NewTypeGuitar: DrumGuitar {
    func stroke() { }
    func arpegio() { }
    func beat() { }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 인터페이스를 너무 크게 구현을 해놓는다면 Lespaul과 Strat처럼 사용하지 않는 함수를 저렇게 구현해놔야 한다.&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;인터페이스 분리의 원칙을 위반&lt;/b&gt;&lt;/span&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;pre id=&quot;code_1706922124019&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol Guitar {
    func stroke()
    func arpegio()
}

protocol Drum {
    func beat()
}

class Lespaul: Guitar {
    func stroke() { }
    func arpegio() { }
}

class Strat: Guitar {
    func stroke() { }
    func arpegio() { }
}

class NewTypeGuitar: Drum, Guitar {
    func stroke() { }
    func arpegio() { }
    func beat() { }
}&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;&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;  Dependency Inversion (의존성 역전)&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;&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;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;pre id=&quot;code_1706922992581&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class GuitarShop {
    let electric: Electric
    let acoustic: Acoustic
    let classic: Classic
}

class Electric {

}

class Acoustic {

}

class Classic {

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 GuitarShop은 High level 객체이고 하위에 있는 Electric / Acoustic / Classic 들은 Low level 객체인 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 GuitarShop은 Electric, Acoustic, Classic에 의존성을 가지게 된다.&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;의존성이 점점 많아지고, 그럼 코드의 변경과 확장에 불리&lt;/b&gt;&lt;/span&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;이를 해결하기 위해 Dependency Inversion을 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706923635587&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class GuitarShop {
    let guitars: [Guitar] = [] // Guitar에만 Dependency를 가지고 있음
    func add(guitar: Guitar) {
    	guitars.append(guitar)
    }
}

protocol Guitar {
    var guitarString: String { get set }
    func play()
}

class Electric: Guitar {
    var guitarString: String = &quot;iron&quot;
    func play() {
    	print(&quot;좡좡좡&quot;)
    }
}

class Acoustic: Guitar {
    var guitarString: String = &quot;thick iron&quot;
    func play() {
    	print(&quot;띵가띵가&quot;)
    }
}

class Classic: Guitar {
    var guitarString: String = &quot;nylon&quot;
    func play() {
    	print(&quot;띵띵&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;&gt;이런 식으로 되면 High level 객체인 GuitarShop은 프로토콜인 Guitar에만 의존성을 가지고 있고 각각 Electric / Acoustic / Classic에는 의존성을 가지고 있지 않게 된다.&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;따라서 이후 Guitar 종류가 추가되더라도, GuitarShop은 수정될 필요가 없게 되면서 확장에는 유리하고 코드 변경은 최소화할 수 있게 되는 것이다.&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;이는 RIBs를 살펴보면 요런 구조로 잘 구현이 되어 있다.&amp;nbsp;&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;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 SOLID 원칙을 전부 철저하게 준수하면서 회사에서 개발을 해나가는 것은 정말 쉽지 않다. 이미 의존성이 엄청 엮여있을 수도 있고 빠르게 피쳐를 진행해야 해서 미쳐 적용을 못할 수도 있다.&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;하지만 막상 정리하고 보니 좋은 코드라고 생각했던 기준들이 대부분 SOLID에 적용이 됐다.&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <author>호르댕댕댕</author>
      <guid isPermaLink="true">https://ho8487.tistory.com/142</guid>
      <comments>https://ho8487.tistory.com/142#entry142comment</comments>
      <pubDate>Sat, 3 Feb 2024 10:42:37 +0900</pubDate>
    </item>
  </channel>
</rss>