<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-hamin-kim.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sat, 12 Jul 2025 04:49:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-hamin-kim.log</title>
            <url>https://velog.velcdn.com/images/dev-hamin-kim/profile/5d9bb2fb-5bfb-474d-8aa6-901e3347dcba/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-hamin-kim.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-hamin-kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[???: 버그가 아니고...]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EB%B2%84%EA%B7%B8%EA%B0%80-%EC%95%84%EB%8B%88%EA%B3%A0</link>
            <guid>https://velog.io/@dev-hamin-kim/%EB%B2%84%EA%B7%B8%EA%B0%80-%EC%95%84%EB%8B%88%EA%B3%A0</guid>
            <pubDate>Sat, 12 Jul 2025 04:49:47 GMT</pubDate>
            <description><![CDATA[<h1 id="이-이야기의-시작은">이 이야기의 시작은...</h1>
<blockquote>
<p>바야흐로 <a href="https://github.com/dev-hamin-kim/HangulKit">HangulKit</a>의 Swift DocC <a href="https://swiftpackageindex.com/dev-hamin-kim/hangulkit/v0.1.12/tutorials/hangulkit/hangulkit101">튜토리얼 문서</a>를 작성하고 있을 때로 거슬러 올라갑니다... 그리고 현재진행형...</p>
</blockquote>
<p><a href="https://swiftpackageindex.com/dev-hamin-kim/hangulkit/v0.1.12/tutorials/hangulkit/hangulkit101">튜토리얼 문서</a>를 들어가보시면 아시겠지만, DocC로는 반응형 튜토리얼을 만들 수 있는데요. SwiftUI와 유사?동일?한 문법으로 작성해주면 알아서 <a href="https://developer.apple.com/tutorials/swiftui">이런</a> 아주 기깔나는 튜토리얼을 뽑아줍니다.</p>
<p>다만 사소한 문제점이 있습니다.</p>
<p>그것은 어떤 소프트웨어라도 피할 수 없는 숙명...</p>
<h2 id="바로-버그가-있다는-것이죠">바로 버그가 있다는 것이죠.</h2>
<p>DocC는 Tutorial을 작성할 때, <code>code1.swift</code>와 <code>code2.swift</code>라는 파일을 생성해서 각각 코드를 작성하고, 그 코드를 튜토리얼 내부의 단계, <code>@Steps</code>안의 <code>@Step</code>에서 불러와서 비교를 할 수 있습니다.</p>
<p>그러니까, <code>code2.swift</code>를 바로 직전 코드인 <code>code1.swift</code>와 비교를 해서 다른 부분을 강조해서 표시해주는 기능이 있답니다. 옵션에 따라 바로 직전 코드가 아니라 다른 코드를 비교하게 할 수도 있구요!</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/ae75cad0-fede-427d-9b11-3bec9f963c14/image.png" alt=""></p>
<p>뭐 대충 위의 스크린샷과 같은 느낌입니다.</p>
<p>근데 스크린샷을 보고 &quot;잘 되는 거 아니냐? 무슨 버그가 있다는 거냐&quot;고 하시면... </p>
<p>코드 변경점 강조가 간헐적으로 안 되더라고요. 왜인지 <em>(당시에는)</em> 몰랐구요.</p>
<h2 id="나만-겪는-문제가-아니었다">나만 겪는 문제가 아니었다</h2>
<p>그래서 대충 짧_(지않았고찾는데꽤시간이걸렸지만그냥설명하기귀찮아서생략)_은 구글링을 마치니, Swift DocC GitHub Repo에 <a href="https://github.com/swiftlang/swift-docc/issues/1007">이슈</a>가 이미 등록되어 있더라고요!</p>
<h2 id="그래서-일단-버그를-재현해보았습니다">그래서 일단 버그를 재현해보았습니다</h2>
<p>부분적으로 코드를 지워보기도 하고,
같은 문제를 겪던 다른 2명의 개발자 분들의 코드도 뜯어보고 하며,
<em>(여기도 자세한 설명은 생략)</em>
결국은 재현해내는데 성공을 했답니다!</p>
<p><a href="https://github.com/swiftlang/swift-docc/issues/1007">이슈</a>에 기록이 남아있으니 직접 보시는 것도 좋지만, 또 글을 읽으러 온 분들에게 각박하게 페이지에서 쫓아내는 것도 도리가 아니기에 친절하지만 간략하게 설명을 드리자면:</p>
<pre><code>@Steps {
    @Step {
        첫번째 스텝입니다.
        @Code(file: code1.swift, name: somefile.swift)
    }
    @Step {
        두번째 스텝입니다.
        @Code(file: code2.swift, name: somefile.swift)
    }
    @Step {
        세번째 스텝입니다.
        @Code(file: code2.swift, name: somefile.swift)
    }
}</code></pre><p>요로코롬 두번째와 세번째 <code>@Step</code>에서 똑같은 코드 파일을 사용할 경우 뭔가 내부적으로 꼬이면서 첫번째 <code>@Step</code>에서 두번째 <code>@Step</code>으로 넘어갈 때 <code>code1.swift</code>와 <code>code2.swift</code>의 차이점이 강조가 되지 않는 것이었습니다.</p>
<p>근데 말이죠...</p>
<p>관련해서 정리하여 이슈에 코멘트를 남기고 난 얼마 뒤...</p>
<h2 id="무려-애플-개발자피셜">무려 애플 개발자피셜</h2>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/12849173-d160-4d97-a726-6919d9ddaa67/image.png" alt=""></p>
<p>버그 아니고 의도된 동작이라는 답변을 주셨는데요...!</p>
<p>답변을 요약하자면:</p>
<blockquote>
<p>두 개 이상의 <code>@Steps</code>가 하나의 코드 파일을 지칭하고 있을 경우, DocC는 현재 <code>@Step</code>의 코드를 직전의 <code>@Step</code>과 비교하고, 현재 <code>@Step</code>과 직전 <code>@Step</code>의 (코드가) 같으니, 강조가 되지 않을 것이다. </p>
</blockquote>
<p>인데요...</p>
<h2 id="뭐지-그럼-내가-틀린건가">뭐지 그럼 내가 틀린건가?</h2>
<p>근데 아무리 생각해봐도 이상한 겁니다.</p>
<blockquote>
</blockquote>
<ol>
<li>답변이 맞다고 해도, 첫번째와 두번째 단계에선 여전히 (다른 코드이니) 변경점이 표시되어야 하는 것이 아닌가?</li>
<li>그럼 왜 세번째 단계를 빼면 버그가 사라지는가?</li>
</ol>
<p>그래서...</p>
<h3 id="아뇨-잘못된-건-잘못된-거죠">아뇨 잘못된 건 잘못된 거죠!</h3>
<p>위에서 설명한 것처럼 그게 그렇게 되면 안 되는 거 아니냐고요<del>~</del>라는 내용의 코멘트를 이슈에다가 또 달았는데요, <span style="color: lightgray">궁금하면 <a href="https://github.com/swiftlang/swift-docc/issues/1007">이슈</a>에 있으니 참고바라요</span></p>
<p>이 글을 쓰고 있는 현 시간 기준으로 아직 별 다른 코멘트가 없으시네요... 많이 바쁘신가봅니다...</p>
<p><span style="color: lightgray"><del><em>(나도바쁜데나도9to6풀타임근무뛰고남는시간쪼개서하는건데)</em></del></span></p>
<p>그래서... 일단 제 나름대로의 해결책을 생각해보자면...</p>
<h3 id="해결책">해결책</h3>
<h4 id="1-공식-문서에-codefilename를-사용할-땐-중복-파일을-사용하지-말라고-명시">1. 공식 문서에 @Code(file:name:)를 사용할 땐 중복 파일을 사용하지 말라고 명시</h4>
<p>가장 간단하죠? 암것도 안 고쳐도 되고 그냥 버그나니까 하지마!라고 해버리면 되는 <span style="color: lightgray"><del>군대식</del></span> 해결책!</p>
<p>하지만 뭐랄까... 그렇게 한다면 파일을 하나 더 생성해야하는 수고가 있잖아요...</p>
<p>하나의 파일을 단 한 번만 불러서 쓸 수 있다면 튜토리얼 때 같은 파일의 내용을 여러 번 사용하고 싶을 때 계속 새로 파일을 만들어야 하는데.. 파일도 많아지고 복잡하잖아요...</p>
<p>그래서 생각해본 다음 해결책은?</p>
<h4 id="2-같은-코드파일을-지목하고-있어도-알아서-잘-표시해준다">2. 같은 코드파일을 지목하고 있어도 알아서 잘 표시해준다.</h4>
<p>근데 또 여기엔 내부 로직에서 두 가지 갈래가 있거든요?</p>
<p>내부적으로 같은 코드 파일 이름을 지칭하고 있으면:</p>
<blockquote>
</blockquote>
<ol>
<li>동일한 파일을 이름만 다르게 두 개 생성해서 이름만 다르게,
그러니까 code2a, code2b 이런 식으로 해서
알아서 두 개의 파일을 만들어주는 것. (이러면 기존 로직에 덧붙이게 되겠죠)</li>
<li>알아서 잘 표시가 되게 기존 로직을 수정한다.</li>
</ol>
<p>글로만 봐도 좀 복잡해보입니다.</p>
<p>그래서 DocC를 포크해서 직접 이것저것 만져보고, 괜찮은 해결책이 나오면 PR까지도 날려볼 생각으로,</p>
<h3 id="docc를-직접-건드려-보려고-했는데요">DocC를 직접 건드려 보려고 했는데요...</h3>
<p>여기서부터 또 난장판이었거든요...
그건 이제 다음 시간에... 찾아옵니다...</p>
<p>그럼</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/7e64a868-d8b0-4659-bfd3-256f5061a565/image.webp" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[아니 Swift엔 이게 없다고?]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EC%95%84%EB%8B%88-%EC%9D%B4%EA%B2%8C-%EC%97%86%EB%8B%A4%EA%B3%A0</link>
            <guid>https://velog.io/@dev-hamin-kim/%EC%95%84%EB%8B%88-%EC%9D%B4%EA%B2%8C-%EC%97%86%EB%8B%A4%EA%B3%A0</guid>
            <pubDate>Thu, 22 May 2025 11:38:18 GMT</pubDate>
            <description><![CDATA[<p>은/는, 이/가와 같은 조사 선택.
수사, 서수사, 순 우리말 날짜. 
표준 발음법, 로마자 표기 등…</p>
<p>한글을 다루는 건 쉽지 않은 일입니다.</p>
<p>TypeScript는 토스의 <a href="https://es-hangul.slash.page/">es-hangul</a>이라는 훌륭한 라이브러리가 있지만, Swift는 아직 없더라고요. (최소한 제가 찾아본 바로는…)</p>
<p>그래서 Swift로 같은 걸 사용해보고 싶어서 만들어봤습니다.</p>
<p><a href="https://github.com/dev-hamin-kim/HangulKit">HangulKit</a>입니다.</p>
<p>TypeScript로 작성된 es-hangul을 Swift로 포팅해놓은 녀석이라고 보시면 됩니다. 근데 이제 Swifty함을 곁들인…</p>
<p>만들면서 생각한 것들을 적어놓을 수도, 아닐 수도 있습니다... 모든 것은 제 가용한 시간에 달렸습니다. HangulKit 작업하는 데에 쓸 시간도 모자른지라…</p>
<p>여튼 서론은 여기까지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DelegateProxy를 사용해서 델리게이트 패턴을 사용하는 라이브러리를 Rx로 둔갑시켜보자]]></title>
            <link>https://velog.io/@dev-hamin-kim/DelegateProxy%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%8D%B8%EB%A6%AC%EA%B2%8C%EC%9D%B4%ED%8A%B8-%ED%8C%A8%ED%84%B4%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A5%BC-Rx%EB%A1%9C-%EB%91%94%EA%B0%91%EC%8B%9C%EC%BC%9C%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev-hamin-kim/DelegateProxy%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%8D%B8%EB%A6%AC%EA%B2%8C%EC%9D%B4%ED%8A%B8-%ED%8C%A8%ED%84%B4%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A5%BC-Rx%EB%A1%9C-%EB%91%94%EA%B0%91%EC%8B%9C%EC%BC%9C%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 13 Feb 2025 04:38:03 GMT</pubDate>
            <description><![CDATA[<p>네 안녕하십니까.</p>
<p>참으로 굉장히 오랜만이네요.</p>
<p>제가 뭘 하고 있었냐면, </p>
<h1 id="틴더식-카드-스와이프">틴더식 카드 스와이프</h1>
<p>를 구현한 오픈소스 라이브러리를 갖다가 프로젝트에 적용시키고 있었단 말이죠.</p>
<p>뭐 대충 이런 겁니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/313a1efc-ece2-4611-8d8e-e0446e7ff950/image.gif" alt=""></p>
<p><a href="https://github.com/mac-gallagher/Shuffle">Shuffle</a>이라는 오픈소스 라이브러리인데요, 델리게이트 패턴을 사용한 녀석이라, 처음에 적용하는 데에는 큰 무리가 없었습니다.</p>
<p>UICollectionView와 상당히 흡사한 Delegate와 Datasource 패턴을 채용했기 때문이죠.</p>
<p>근데... RxSwift를 사용해서 리팩터링을 진행하던 과정 중에 이 친구를 어떻게 Rx로 사용할 것인가?</p>
<p>라는 의문이 들었고, 잘 찾아보니 답이 나왔습니다. </p>
<h2 id="답은-delegateproxy다">답은 DelegateProxy다.</h2>
<p>그럼 작성해줍시다.</p>
<pre><code class="language-swift">extension SwipeCardStack: HasDelegate {
    public typealias Delegate = SwipeCardStackDelegate
}
</code></pre>
<p>일단 위의 코드로 Shuffle의 SwipeCardStack의 스와이프 액션 등을 처리해주는 Delegate가 있다고 명시해주고,</p>
<pre><code class="language-swift">
class RxSwipeCardStackDelegateProxy
: DelegateProxy&lt;SwipeCardStack, SwipeCardStackDelegate&gt;
, DelegateProxyType
, SwipeCardStackDelegate {

    weak private(set) var cardStack: SwipeCardStack?

    init(cardStack: SwipeCardStack) {
        self.cardStack = cardStack
        super.init(parentObject: cardStack, delegateProxy: RxSwipeCardStackDelegateProxy.self)
    }

    static func registerKnownImplementations() {
        self.register { RxSwipeCardStackDelegateProxy(cardStack: $0) }
    }
}</code></pre>
<p>위 코드처럼 DelegateProxy를 작성해줍니다.</p>
<p>클래스명은 RxCocoa 내부에서 Rx+(원래 클래스 이름)+DelegateProxy로 다 지어놨길래 마음의 평안을 위해 통일해줍니다.</p>
<pre><code class="language-swift">extension Reactive where Base: SwipeCardStack {
    var delegate: DelegateProxy&lt;SwipeCardStack, SwipeCardStackDelegate&gt; {
        return RxSwipeCardStackDelegateProxy.proxy(for: base)
    }

    // (기존 delegate의 메서드들을 넣을 곳)

}
</code></pre>
<p>그 담엔 이렇게 Reactive의 extension으로 작성을 해주고요,
아래에 기존 delegate의 메서드들을 넣어줍시다.</p>
<pre><code class="language-swift">// 기존 delegate의 메서드들
@objc
optional func cardStack(_ cardStack: SwipeCardStack, didSelectCardAt index: Int)

@objc
optional func cardStack(_ cardStack: SwipeCardStack, didSwipeCardAt index: Int, with direction: SwipeDirection)

@objc
optional func cardStack(_ cardStack: SwipeCardStack, didUndoCardAt index: Int, from direction: SwipeDirection)</code></pre>
<p>그러니까 요놈들을</p>
<pre><code class="language-swift">// 넣어줌
extension Reactive where Base: SwipeCardStack {
    var delegate: DelegateProxy&lt;SwipeCardStack, SwipeCardStackDelegate&gt; {
        return RxSwipeCardStackDelegateProxy.proxy(for: base)
    }

    var didSelectCardAt: Observable&lt;Int&gt; {
        return delegate
            .methodInvoked(#selector(SwipeCardStackDelegate.cardStack(_:didSelectCardAt:)))
            .map { num in
                return num[1] as? Int ?? 0
            }
    }

    var didSwipeAllCards: Observable&lt;Void&gt; {
        return delegate
            .methodInvoked(#selector(SwipeCardStackDelegate.didSwipeAllCards(_:)))
            .map { _ in () }
    }

    var didSwipeCardAt: Observable&lt;(Int, SwipeDirection)&gt; {
        return delegate
            .methodInvoked(#selector(SwipeCardStackDelegate.cardStack(_:didSwipeCardAt:with:)))
            .map { args in
                return (
                    index: args[1] as? Int ?? 0,
                    direction: args[2] as! SwipeDirection
                )
            }
    }
}</code></pre>
<p>이렇게 넣으면 된다...!</p>
<p>그럼 기존의 델리게이트에서 메서드가 호출되는 타이밍에 .methodInvoked가 호출되어 Observable을 반환하게 됩니다.</p>
<p>그럼 View와 ViewModel에 적용해볼까요?</p>
<h2 id="아니-왜-또-안되는거야">아니 왜 또 안되는거야</h2>
<p>근데 안되네요. 기존의 Delegate 패턴을 사용했을때 잘 뜨던 카드 스택들이 래핑하고 나니 안됩니다...</p>
<p>왜 안되는지... 뭐가 잘못되었는지 한참을 생각하고 코드를 읽어보다가...</p>
<p>설마... 하는 생각이 들어 다른 뷰의 프로퍼티로 분리해뒀던 것 때문에↓</p>
<pre><code>프로젝트/
├─ class ItemSearchView/
│  ├─ 이것저것 뷰 컴포넌트들
├─ class ItemCardsView/
│  ├─ cardStack = SwipeCardStack()
</code></pre><p>그런 건가 싶어 합쳐주었고↓</p>
<pre><code>
프로젝트/
├─ class ItemSearchView/
│  ├─ 이것저것 뷰 컴포넌트들
│  ├─ cardStack = SwipeCardStack()
├─ class ItemCardsView -&gt; delete/</code></pre><p>그러니까 됩니다 또...?</p>
<h3 id="사소한-소란이-있긴-했지만">사소한 소란이 있긴 했지만</h3>
<p>여튼 그렇게 해결이 되었습니다.</p>
<p>예에에에에소리질러<del>~</del></p>
<p><em>(실제로 몇시간동안 끙끙댄 후에 해결하고 소리지름 리얼참트루스토리)</em></p>
<h2 id="어-근데-그럼-datasource는요">어 근데 그럼 Datasource는요?</h2>
<p>그게 말입니다....</p>
<p>Shuffle이 가지고있는 Delegate와 Datasource가 UICollectionView와 유사하게 되어 있어,</p>
<p>RxCocoa의 UICollectionView+Rx와 동일한 구조로 작업하면 되지 않을까... 하고 코드를 읽고 적용해보려 했거든요?</p>
<p>근데 안돼요.</p>
<p>계속 에러가 뜨더라고요...</p>
<p>정확히는:</p>
<blockquote>
<p>&#39;subscribeProxyDataSource(ofObject:dataSource:retainDataSource:binding:)&#39; requires that &#39;any RxSwipeCardStackDataSourceProxy.Delegate&#39; (aka &#39;any SwipeCardStackDataSource&#39;) be a class type</p>
</blockquote>
<p>이렇게 뜨는데,
대충 해석하자면:</p>
<blockquote>
<p>응 니가 준거 클래스 타입 아니야 돌아가~</p>
</blockquote>
<p>라네요? 그래서 RxSwipeCardStackDataSourceProxy와 관련된 클래스들을 찾아봤는데 죄다 클래스였고,</p>
<p>SwipeCardStackDataSource도 확인해봤는데 AnyObject였단 말입니다..</p>
<pre><code class="language-swift">public protocol SwipeCardStackDataSource: AnyObject {
  func cardStack(_ cardStack: SwipeCardStack, cardForIndexAt index: Int) -&gt; SwipeCard
  func numberOfCards(in cardStack: SwipeCardStack) -&gt; Int
}</code></pre>
<p>그래서 일단은 데이터소스는 기존 형식을 사용하는 중인데...
이거 왜 이러는지 아시는 분은 댓글로 알려주시면 감사하겠습니다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[포켓몬] 한국어네? 한국어야?]]></title>
            <link>https://velog.io/@dev-hamin-kim/%ED%8F%AC%EC%BC%93%EB%AA%AC-%ED%95%9C%EA%B5%AD%EC%96%B4%EB%84%A4-%ED%95%9C%EA%B5%AD%EC%96%B4%EC%95%BC</link>
            <guid>https://velog.io/@dev-hamin-kim/%ED%8F%AC%EC%BC%93%EB%AA%AC-%ED%95%9C%EA%B5%AD%EC%96%B4%EB%84%A4-%ED%95%9C%EA%B5%AD%EC%96%B4%EC%95%BC</guid>
            <pubDate>Mon, 06 Jan 2025 01:47:26 GMT</pubDate>
            <description><![CDATA[<p>예, 간만입니다.</p>
<p>일단 해야 하는 게 뭐였냐면...</p>
<p>PokéAPI를 사용해서 MVVM 구조로 도감을 만드는 것입니다.</p>
<p>RxSwift와 MVVM 구조는 처음이라 좀 익숙치 않았는데,</p>
<p>처음이니까~ 하면서 우당탕탕 진행하면서</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/5030ab30-adad-492f-8d9f-435faa8c1201/image.png" alt=""></p>
<p>아무 일 없던 척 다음 단계로 넘어가던 와중에...</p>
<h1 id="현지화를-해야된다">현지화를 해야된다</h1>
<p>영어로 된 포켓몬의 이름과 타입 등을 한국어로 현지화해서 나타내야 하는 과정에 도달했습니다.</p>
<p>근데 과제에선 말이죠,</p>
<pre><code class="language-swift">import Foundation

enum PokemonTranslator {
    private static let koreanNames: [String: String] = [
        &quot;bulbasaur&quot;: &quot;이상해씨&quot;,
        &quot;ivysaur&quot;: &quot;이상해풀&quot;,

        // (중략)

        &quot;mewtwo&quot;: &quot;뮤츠&quot;,
        &quot;mew&quot;: &quot;뮤&quot;
    ]

    static func getKoreanName(for englishName: String) -&gt; String {
        return koreanNames[englishName.lowercased()] ?? englishName
    }
}

/* 사용 예시
 let pokemonName = &quot;Pikachu&quot;
 let koreanName = PokemonTranslator.getKoreanName(for: pokemonName)
 print(&quot;\(pokemonName)의 한국어 이름: \(koreanName)&quot;)
 */</code></pre>
<p>요런 식으로 현지화를 하라고 코드를 제공해주셨더라고요?</p>
<h3 id="물론-나쁘지-않은-해결책이지만">물론... 나쁘지 않은 해결책이지만...</h3>
<blockquote>
</blockquote>
<ol>
<li>저렇게 코드 고봉밥이 있는 것</li>
<li>추후 범위를 넓히면 또 수동으로 추가해줘야 한다는 점</li>
<li>사용하지 않을 수도 있는 정보들이 static으로 선언되어 메모리에서 자리를 차지하고 있는 것</li>
</ol>
<p>↑와 같은 사유 때문에 맘이 불편해서, 분명히 방법이 있을 것이야...를 되뇌며 pokeAPI를 쥐 잡듯 뒤졌습니다.</p>
<p>그러다가...?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/181d50eb-0c12-4d91-9af4-3cade5b0f06b/image.png" alt=""></p>
<p>음?
너 일로와봐</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/5e58cc16-e92d-4024-8641-7a38cc2994f3/image.png" alt=""></p>
<p>어어??
다국어 지원이 안 되는 데 이게 있을 리가 없죠?</p>
<h3 id="더-찾아봅시다">더 찾아봅시다.</h3>
<p>그랬더니 PokéAPI 깃헙에서 <a href="https://github.com/PokeAPI/pokeapi/issues/235">이것</a>을 발견했습니다.</p>
<p>링크 들어가기 귀찮은 분들을 위한 스샷↓
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/1b0bded8-ea31-4b36-8829-554e22020964/image.png" alt=""></p>
<p>오호... pokemon species로 찾으면 뭔가 나올 것 같네요.</p>
<p>PokéAPI 문서로 돌아가봅시다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/b5b391f5-590f-4619-8f63-260ee060329e/image.png" alt=""></p>
<p>음... pokemon species니까... Pokémon에서 찾아볼까요...?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/73f60a2d-0c02-4d83-b27f-9ccffb887d71/image.png" alt=""></p>
<p>오호
어디 그럼 <a href="https://pokeapi.co/api/v2/pokemon-species/1">https://pokeapi.co/api/v2/pokemon-species/1</a> 로 들어가서 최종 확인해봅시다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/15bcdb1c-3f12-4926-ba9d-98157a48290d/image.png" alt=""></p>
<p> 딴~ 라라란~ 따라란~ 따라란~ 따~ 쿵짝짝~ 쿵짝짝~ 따라리라라리
<em>(BGM - 요한 슈트라우스 2세: 봄의 소리 왈츠)</em></p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/a92d5da6-9712-452a-b779-14761c30ef18/image.jpeg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/291021a2-5807-48c7-9ece-076e57c9b91f/image.png" alt=""></p>
<h2 id="한국어네">한국어네?</h2>
<p>한국어야?</p>
<p>갓챠</p>
<p>있었습니다. 한국어가!</p>
<p>그럼 새롭게 발견한 이 녀석을 사용해주기 위해...</p>
<pre><code class="language-swift">struct PokemonSpecies: Codable {
    var names: [Name] = []
}

struct Name: Codable {
    let name: String
    let language: Language
}

struct Language: Codable {
    let name: String
    let url: String
}</code></pre>
<p>JSON 디코딩을 위한 구조체를 선언해주고...</p>
<pre><code class="language-swift">private func fetchLocalizedNames() {
    guard let url = URL(string: &quot;https://pokeapi.co/api/v2/pokemon-species/\(pokemonID.description)&quot;) else {
        namesSubject.onError(NetworkError.invalidURL)
        return
    }

    NetworkManager.shared.fetch(url: url)
        .subscribe(onSuccess: { [weak self] (names: PokemonSpecies) in
            self?.namesSubject.onNext(names)
        }, onFailure: { [weak self] error in
            self?.namesSubject.onError(error)
        }).disposed(by: disposeBag)
}</code></pre>
<p>ViewModel에서 정보를 받아오는 부분을 추가해주고...</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/aedc61ef-1fe4-4f6c-bf86-d40cf37c4bb2/image.png" alt=""></p>
<p>View에서 적용해주면...?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/e22cbd33-d7fa-4dae-89c0-58ab32b6fdae/image.png" alt=""></p>
<p>한국어로 잘 나오는군요</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/8c405a0a-265f-42aa-a5c3-7c18f18df27f/image.jpg" alt=""></p>
<h3 id="추가-사항">추가 사항</h3>
<p>다른 분들이 겪으신 29. 니드런♀이나 122. 마임맨 등의 api에서 영어이름이 수정되어 현지화된 이름이 나오지 않는 문제를 소 뒷걸음질치다 쥐 잡은 격으로 겪지 않게 되었네요...? 완전 럭키비키잖아💛✨</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/cbb7d20c-ddea-4a9a-97b2-6fb29595aca1/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[영화관] 사실은 시력이슈였구요]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EC%98%81%ED%99%94%EA%B4%80-%EC%82%AC%EC%8B%A4%EC%9D%80-%EC%8B%9C%EB%A0%A5%EC%9D%B4%EC%8A%88%EC%98%80%EA%B5%AC%EC%9A%94</link>
            <guid>https://velog.io/@dev-hamin-kim/%EC%98%81%ED%99%94%EA%B4%80-%EC%82%AC%EC%8B%A4%EC%9D%80-%EC%8B%9C%EB%A0%A5%EC%9D%B4%EC%8A%88%EC%98%80%EA%B5%AC%EC%9A%94</guid>
            <pubDate>Thu, 26 Dec 2024 01:28:30 GMT</pubDate>
            <description><![CDATA[<p>저번 이야기로부터 이어지니까 읽고 와주세요 <a href="https://velog.io/@dev-hamin-kim/%EC%98%81%ED%99%94%EA%B4%80-%EC%95%84%EB%8B%88-forEach%EB%82%98-for-in%EC%9D%B4%EB%82%98-%ED%95%98%EB%93%9C%EC%BD%94%EB%94%A9%EC%9D%B4%EB%82%98-%EB%98%91%EA%B0%99%EC%95%84%EC%95%BC-%ED%95%98%EB%8A%94-%EA%B1%B0-%EC%95%84%EB%8B%98">링크까지 친절히 걸어드릴게요.</a></p>
<p>읽고 오시기 귀찮은 분들을 위해 제가 대신 요약하자면,</p>
<blockquote>
<ol>
<li>forEach 혹은 for-in 반복문으로 UIView의 Subview로 UIView와 UILabel을 넣어주고</li>
<li>SnapKit을 활용해 제약조건을 설정해주면?</li>
<li>축☆런타임 에러★축</li>
<li>유는 모든 구성요소가 넣어지기 전에 SnapKit 코드가 실행되어서인것으로 추정.</li>
<li>forEach나 for-in 반복문 말고 addSubview를 직접 반복해서 적어주면 버그 X</li>
</ol>
</blockquote>
<p>와 참 친절한 요약!</p>
<p>여튼, 수많은 뻘짓 끝에 버그의 실로 놀라운 해결책을 찾아냈습니다.</p>
<p>그러나 여백이 부족하여 이를 적지 않겠다...는 농담이구요</p>
<p>그저 시력이 딸려서 발생한 휴먼에러입니다.</p>
<p>저번 글에 넣어놓은 짤방에 답이 있었더라고요...</p>
<p>다시 갖고 왔습니다:</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/610601f5-5605-496f-9f26-492c4a88635e/image.png" alt=""></p>
<p>보이시나요?</p>
<p>빨간색으로 표시해보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/313eebaa-ec8f-4415-b978-e3ddd7c0e068/image.png" alt=""></p>
<p>ㅎㅎ
ㅎ</p>
<p>ㅋㅋㅋㅋㅋㅋㅋ</p>
<p>selectedBox만 두개...</p>
<p>즉 selectedLabel이 subView에 들어가지 않은 상태에서 SnapKit으로 SuperView로 constraint를 잡으려니까 에러가 뜬 것이었네요...</p>
<p>네... 저걸 코드를 짤 때도, 주석처리를 할 때도, 스크린샷을 찍을 때도, 짤방까지 만들때까지도 발견을 못 한게 신기하네요...</p>
<p>진즉에 GPT한테 물어봤으면 짚어줬을 것 같은데 그놈의 쓸데없는 자존심때문에 안 물어보다가 여기까지 끌렸습니다.</p>
<p>여튼, SnapKit의 &quot;<code>Fatal error: Expected superview but found nil when attempting make constraint equalToSuperview.</code>&quot;라는 에러는, SuperView를 찾을 수 없을 때 나오는 에러이다...!</p>
<p>ㅎㅋㅋㅎ...ㅎ....ㅋㅋㅋ....ㅎㅎㅎ...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[비교도 해보자]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EB%B9%84%EA%B5%90%EB%8F%84-%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev-hamin-kim/%EB%B9%84%EA%B5%90%EB%8F%84-%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 23 Dec 2024 09:31:14 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/10f85da2-f1c8-40d2-b709-bd5c344f0d51/image.webp" alt=""></p>
<h1 id="두-날짜를-비교하는-중위-연산자들------">두 날짜를 비교하는 중위 연산자들 (==, !=, &lt;, &gt;, &lt;=, &gt;=)</h1>
<p>딱히 설명이 필요할까 싶습니다.</p>
<p>스크린샷으로 갈음합니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/dd836e5e-e395-440d-93e0-64fe1d1a0c57/image.png" alt=""></p>
<h1 id="comparedate---comparisonresult">compare(Date) -&gt; ComparisonResult</h1>
<p>이건 위에서 본 중위 연산자들이랑 비슷한데요,</p>
<p>리턴값이 Bool이 아니라, ComparisonResult라는 Enum 값입니다.</p>
<p>Enum 값이기에, Switch-case 문에서 사용하기 유용하겠죠?</p>
<p>.rawValue를 붙여서 1, 0, -1 정수값으로 나타낼 수도 있긴 한데....
<em>사실 그럴 바에 중위 연산자들을 쓰는 게 낫지 않나...?</em></p>
<p>여튼 코드 예시를 보여드리겠습니다:</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/a3611934-b39c-4938-9f14-d8f396d3283b/image.png" alt=""></p>
<p>이해가 잘 되셨길 바라며...</p>
<p>다음 항목으로 넘어가도록 하겠습니다.</p>
<h1 id="distanceto-date---timeinterval">distance(to: Date) -&gt; TimeInterval</h1>
<p>이 친구는 간단합니다.</p>
<p>두 Date 간 TimeInterval을 알려줍니다.</p>
<p>보면 바로 이해 되실겁니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/d15816ba-9344-4bfa-80fe-bc1e0552269b/image.png" alt=""></p>
<p>참 쉽죠? (2)</p>
<h1 id="timeintervalsincedate---timeinterval">timeIntervalSince(Date) -&gt; TimeInterval</h1>
<p>바로 위의 distance(to:)와 똑같은 친구 아니냐고요?</p>
<p>비슷합니다.</p>
<p>아빠의 이름이 <strong>진화</strong>고 <strong>승우</strong>라는 이름의 아들이 있다고 하면,</p>
<p>승우아빠가 진화냐 진화아들이 승우냐 그 차이입니다.</p>
<p>서순이죠.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/db58f468-b78c-4ac0-aee9-8cbb0654fe1a/image.png" alt=""></p>
<p>조금 헷갈리셨다면 다시 찬찬히 읽어보시면 이해가 되실 겁니다.</p>
<h1 id="timeintervalsincenowreferencedate1970">timeIntervalSinceNow/ReferenceDate/1970</h1>
<p>네... 기준점 차이입니다. 이것도 별거 없으니 스샷으로 갈음합니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/0040b1db-e3e2-4444-b4ad-27eaaec766a9/image.png" alt=""></p>
<h1 id="addtimeintervaltimeinterval--addingtimeintervaltimeinterval">addTimeInterval(TimeInterval) / addingTimeInterval(TimeInterval)</h1>
<p>둘 다 이름 그대로 Date에 TimeInterval을 더합니다.</p>
<p>앞에 붙는 단어가 add와 adding라는 차이가 있는데요.</p>
<p>둘의 차이는 Date를 추가로 반환하냐 아니냐의 차이입니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/639d306c-e6b6-4a9f-94f1-ca8d87448697/image.png" alt=""></p>
<p>그래서 adding은 let 상수에도 쓰일 수 있지만,</p>
<p>add는 그렇게 못 씁니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/b0a6ce92-fd2f-4dff-b534-c676e6c0b1a5/image.png" alt=""></p>
<p>변수로 바꿔주니 잘 되네요.</p>
<p>그럼 다음에 계속...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[영화관] 아니 forEach나 for-in이나 하드코딩이나 똑같아야 하는 거 아님?]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EC%98%81%ED%99%94%EA%B4%80-%EC%95%84%EB%8B%88-forEach%EB%82%98-for-in%EC%9D%B4%EB%82%98-%ED%95%98%EB%93%9C%EC%BD%94%EB%94%A9%EC%9D%B4%EB%82%98-%EB%98%91%EA%B0%99%EC%95%84%EC%95%BC-%ED%95%98%EB%8A%94-%EA%B1%B0-%EC%95%84%EB%8B%98</link>
            <guid>https://velog.io/@dev-hamin-kim/%EC%98%81%ED%99%94%EA%B4%80-%EC%95%84%EB%8B%88-forEach%EB%82%98-for-in%EC%9D%B4%EB%82%98-%ED%95%98%EB%93%9C%EC%BD%94%EB%94%A9%EC%9D%B4%EB%82%98-%EB%98%91%EA%B0%99%EC%95%84%EC%95%BC-%ED%95%98%EB%8A%94-%EA%B1%B0-%EC%95%84%EB%8B%98</guid>
            <pubDate>Tue, 17 Dec 2024 13:53:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/d8828837-ec3a-47da-a837-b20fa922e7f4/image.png" alt=""></p>
<p>오늘도 알쏭달쏭 트러블슈팅에 오신 여러분을 환영합니다.</p>
<p>하...</p>
<h2 id="배경-설명을-조금-하자면요">배경 설명을 조금 하자면요,</h2>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/28d6ac03-9072-4d8d-aa9e-80e5cb938908/image.png" alt=""></p>
<p>요런 녀석을 만들어야 해서,</p>
<p>대충 요런 코드를 짜는 중이었습니다.</p>
<pre><code class="language-swift">import UIKit

import SnapKit
import Then

final class ColorGuide: UIView {

    private let availableBox = UIView().then {
        $0.backgroundColor = .available
        $0.layer.cornerRadius = 4
    }

// 중략...

    private let availableLabel = UILabel().then {
        $0.text = &quot;선택 가능&quot;
        $0.textColor = ._100
    }
// 중략...
</code></pre>
<p>이렇게 네모 상자가 될 UIView와 그 옆에 붙을 UILabel을 만들어주고,</p>
<pre><code class="language-swift">override init(frame: CGRect) {
    super.init(frame: frame)
    backgroundColor = .systemBackground
    setViews()
    setConstraints()
}

// 중략...
private func setViews() {
    [
        availableBox, availableLabel,
        unavailableBox, unavailableLabel,
        selectedBox, selectedBox
    ].forEach { addSubview($0) }
}

private func setConstraints() {
    let boxSize: CGFloat = 12
    let boxLabelDistance: CGFloat = 7
    let leadingTrailingDistance: CGFloat = 16

        // 선택 가능
    availableBox.snp.makeConstraints { make in
        make.leading.equalToSuperview()
        make.size.equalTo(boxSize)
        make.centerY.equalToSuperview()
    }
    availableLabel.snp.makeConstraints { make in
        make.leading.equalTo(availableBox.snp.trailing).offset(boxLabelDistance)
        make.centerY.equalToSuperview()
    }
    // 후략...
}</code></pre>
<p>대충 이런 코드로 뷰를 넣어주고 constraints를 적용해주는 코드를 작성했습니다.</p>
<p>별 문제 없을 것 같아 보이지 않나요?</p>
<p>저도 그렇게 생각했습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/fa3e1681-47f7-4733-89d6-2d479bc623c0/image.png" alt=""></p>
<p>빨간색으로 왼뺨을 맞기 전까진요.</p>
<p>여차저차 이리저리 생각해서 가설을 내려봤습니다.</p>
<blockquote>
<ol>
<li>고차함수인 forEach를 쓰면 (.map이나 .filter처럼) 컴파일러의 최적화가 관여할 여지가 늘어나니,</li>
<li>어떻게 해서 뒤에 오는 setConstraints가 먼저 실행되어서,</li>
<li>각 요소들 중 일부가 subview로 들어가지 못했고,</li>
<li>결과적으로 들어가지 못 한 요소의 superview를 잡으려고 해서 그런 게 아닐까?</li>
</ol>
</blockquote>
<p><em>미리 스포하자면, 1번은 거짓이었습니다...
사유: <a href="https://stackoverflow.com/questions/45333177/when-to-use-foreach-instead-of-for-in">언제 forEach를 또는 for-in 반복문을 쓸 것이냐</a> &amp; <a href="https://github.com/swiftlang/swift/blob/1bae49950e9b4934b6fd4abecf68a10181e60736/stdlib/public/core/Sequence.swift#L1058">Swift source code</a>
정리하면서 보니 소 뒷걸음질치다 쥐를 잡은 격이네요. <del>소띠라서그런가</del></em></p>
<h2 id="그래서-하드코딩으로-해봤습니다">그래서 하드코딩으로 해봤습니다.</h2>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/68de981d-3e47-4a2b-a0ba-134e03dc1365/image.png" alt=""></p>
<p>어... 잘... 되네요...?</p>
<p>그럼 이론 상 같은 코드를 반복하여 실행해주는
for-in 반복문도 되어야 하는 것이겠죠?</p>
<h3 id="근데-말이죠">근데 말이죠...</h3>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/f9179479-3102-451e-984c-74bb0d1d4dcb/image.png" alt=""></p>
<p>아니 믿었던 반복문 ㄴㅓ ㅁㅏ ㅈㅓ...
왼뺨을 맞고 눈물흘리며 찾아간 친구가 오른뺨 마저도 때려주는 기분이네요.</p>
<p>원인의 실마리가 미궁 속으로 빠져버렸습니다...
근데 오늘은 너무나도 졸린 나머지 다음에 이어서 알아보도록 하겠습니다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Date()나 Date.now나 똑같은 거 아님?]]></title>
            <link>https://velog.io/@dev-hamin-kim/Date%EB%82%98-Date.now%EB%82%98-%EB%98%91%EA%B0%99%EC%9D%80-%EA%B1%B0-%EC%95%84%EB%8B%98</link>
            <guid>https://velog.io/@dev-hamin-kim/Date%EB%82%98-Date.now%EB%82%98-%EB%98%91%EA%B0%99%EC%9D%80-%EA%B1%B0-%EC%95%84%EB%8B%98</guid>
            <pubDate>Thu, 12 Dec 2024 13:24:24 GMT</pubDate>
            <description><![CDATA[<h1 id="date의-타입-프로퍼티-now">Date()의 타입 프로퍼티 now</h1>
<blockquote>
</blockquote>
<p>Q. 어? 근데 Date()를 그대로 이니셜라이징해도 현재 시간이 나오는 건 똑같지 않나요?</p>
<blockquote>
</blockquote>
<p>now는 왜 따로 있어요?</p>
<p>이거에 대해서는 배경 지식에 대한 이해가 필요한데요...</p>
<p>길게 설명하면 읽는 사람도 쓰는 사람도 귀찮으니,</p>
<p>세 줄 요약으로 갈음하겠습니다:</p>
<ol>
<li><p>원래는 Date.now가 없었음.</p>
</li>
<li><p>사람들이 다들 Date의 Extension으로 직접 Date.now를 만들어서 사용함. (Date() 보다 Date.now가 현재시간이라는 것이 명확하니까!)</p>
</li>
<li><p>Swift 5.5에서 정식으로 추가됨. (iOS 15 / macOS 12 이상에서 사용 가능.)</p>
</li>
</ol>
<blockquote>
<p>자세한 내용은:
<a href="https://stackoverflow.com/questions/78750336/swift-date-vs-date-now-when-to-use-which">https://stackoverflow.com/questions/78750336/swift-date-vs-date-now-when-to-use-which</a>
<a href="https://forums.swift.org/t/date-now-and-other-calendar-thoughts/12853">https://forums.swift.org/t/date-now-and-other-calendar-thoughts/12853</a></p>
</blockquote>
<p>코드 예시 하나 던져놓고 넘어가겠습니다:</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/cf6ba137-5be2-46c2-ab1d-ee0c94798a72/image.png" alt=""></p>
<h1 id="타입-프로퍼티-distantfuturedistantpast">타입 프로퍼티 distantFuture/distantPast</h1>
<p>직역하면 먼 미래와 먼 과거입니다.</p>
<p>그리고 말 그대로 먼 미래의 시간과 먼 과거의 시간을 나타냅니다.</p>
<p>얼마나 머냐고요?</p>
<p>2000...년정도?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/60a5f849-12d5-4d7f-9185-dbba6cfe148d/image.png" alt=""></p>
<p>아니 이런 거 뒀다 어디 써요? 라고 하실 수도 있지만,</p>
<p><a href="https://stackoverflow.com/questions/44250193/what-is-a-practical-example-using-distantfuture">캘린더 앱 등에서 지금으로부터 모든 과거/미래의 이벤트를 갖고 오고 싶다 할 때 유용하겠습니다.</a></p>
<p>다음에 계속...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[연락처] 그건 제 잔상입니다만?]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EC%97%B0%EB%9D%BD%EC%B2%98-%EA%B7%B8%EA%B1%B4-%EC%A0%9C-%EC%9E%94%EC%83%81%EC%9E%85%EB%8B%88%EB%8B%A4%EB%A7%8C</link>
            <guid>https://velog.io/@dev-hamin-kim/%EC%97%B0%EB%9D%BD%EC%B2%98-%EA%B7%B8%EA%B1%B4-%EC%A0%9C-%EC%9E%94%EC%83%81%EC%9E%85%EB%8B%88%EB%8B%A4%EB%A7%8C</guid>
            <pubDate>Tue, 10 Dec 2024 05:43:14 GMT</pubDate>
            <description><![CDATA[<p>아...</p>
<p>또 그 시간이 왔군요</p>
<p>트러블슈팅의 시간 말입니다.</p>
<p>제목에서 유추하신 분들도 있겠지만, 바로 짤로 보는 것이 이해가 빠르니 보여드리겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/09624cb9-2506-4486-add7-335002c1de9c/image.gif" alt=""></p>
<p>거슬리군요 참으로...</p>
<p>하지만 이제 짬바가 조금 생겨서일까요?</p>
<p>바로 해.결.해버렸읍니다...</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/4dcb88d4-9c34-48b2-82aa-9e1e8ede48bb/image.gif" alt=""></p>
<p>어떻게 해결했냐하면...</p>
<pre><code class="language-swift">self.backgroundColor = .systemBackground</code></pre>
<p>위에 덮여지는 뷰에 이 코드 한 줄 넣으니 해결이더군요.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/1887ac1f-54f8-44b1-9956-494e97c20c64/image.gif" alt=""></p>
<p>아이 신나</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dates and Times에 대해서 알아보자. (feat. Typealais)]]></title>
            <link>https://velog.io/@dev-hamin-kim/Dates-and-Times%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90.-feat.-Typealais</link>
            <guid>https://velog.io/@dev-hamin-kim/Dates-and-Times%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90.-feat.-Typealais</guid>
            <pubDate>Mon, 09 Dec 2024 12:45:51 GMT</pubDate>
            <description><![CDATA[<p>코딩을 하다 보면 시간을 다룰 일이 종종 있습니다.
 
Swift에서 날짜와 시간을 담당하고 있는 각종 구조체들과 메소드, 프로퍼티에 대해서 알아보겠습니다.</p>
<h1 id="typealias-timeinterval">typealias TimeInterval</h1>
<p>*typealias란, 기존의 자료형을 별명으로 부르는 것이라고 생각하면 편합니다.</p>
<p><em>마치 울트라리스크를 울트라, 울라리, 울리크 등으로 바꿔 부르지만 여러분들은 다 알아들으시는 것 처럼 말이죠.</em>
 
TimeInterval은 Double입니다.
근데 시간을 초 단위로 나타내는... 친구죠.
 
뭔 소린가 싶으시면 아래를 보시면 됩니다↓</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/5dd9d6b8-2f1f-4b5b-ac26-18f313ac6291/image.png" alt=""></p>
<p>어렵잖게 이해하셨을것이라 생각하고 넘어가겠습니다.</p>
<p>뒤에 나올 Date에서 시간 간격을 지정할 때 유용하게 쓰이니 기억해두면 좋습니다.</p>
<h1 id="date">Date()</h1>
<p>위에서 보셨듯, 아무런 파라미터를 넣지 않고 Date를 이니셜라이징하면,</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/ab7ea53d-056f-40e6-a038-6ea1f47a0c94/image.png" alt=""></p>
<p>현재의 날짜와 시간으로 이니셜라이징이 됩니다.</p>
<p>이어서 다른 이니셜라이징 방법을 살펴봅시다.</p>
<h1 id="datetimeintervalsincenow">Date(timeIntervalSinceNow:)</h1>
<p>저기 저 위에서 첫번째 스샷 내부 코드에서 보신 친구죠?
뭔지 모르겠으면 위에서 다시 보고 오시면 됩니다.
저는 불친절한 사람이라 다시 적어주지 않습니다.</p>
<h1 id="datetimeintervalsince">Date(timeInterval:since:)</h1>
<p>위의 Date(timeIntervalSinceNow:)와 비슷하게 생겼죠? 실제로 비슷합니다.
since: 뒤에 지금의 시간 대신 다른 Date를 넣어주면 그 시간을 기준으로 시간을 계산해줍니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/3599a1ee-bc84-418a-915e-a7aedac3c54a/image.png" alt=""></p>
<p>요렇게 말이죠.</p>
<h1 id="datetimeintervalsincereferencedate">Date(timeIntervalSinceReferenceDate:)</h1>
<p>얘도 바로 위에 있는 친구랑 똑같습니다. 다만 ReferenceDate가 기준인데요.
2001.01.01 00:00:00 UTC가 기준입니다.
그 기준으로 초 단위로 시간을 입력해주시면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/092fa0b6-5bd4-4cbe-b15f-f0006f5ed5f8/image.png" alt=""></p>
<h1 id="datetimeintervalsince1970">Date(timeIntervalSince1970:)</h1>
<p>얘도 바로 위에 있는 친구랑 똑같습니다.
다만 유닉스 시간, 즉, 1970.01.01 00:00:00 UTC가 기준입니다. (후략)</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/288bb876-51a3-4f2d-b171-2f1212f5908a/image.png" alt=""></p>
<p>졸리니 이만 하고 나중에 이어서 적어보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SwiftUI에서 @State와 @Binding의 차이가 뭘까?]]></title>
            <link>https://velog.io/@dev-hamin-kim/SwiftUI%EC%97%90%EC%84%9C-State%EC%99%80-Binding%EC%9D%98-%EC%B0%A8%EC%9D%B4%EA%B0%80-%EB%AD%98%EA%B9%8C</link>
            <guid>https://velog.io/@dev-hamin-kim/SwiftUI%EC%97%90%EC%84%9C-State%EC%99%80-Binding%EC%9D%98-%EC%B0%A8%EC%9D%B4%EA%B0%80-%EB%AD%98%EA%B9%8C</guid>
            <pubDate>Thu, 05 Dec 2024 11:34:40 GMT</pubDate>
            <description><![CDATA[<p>SwiftUI로 앱을 만들다 보면, 토글, 버튼 등의 변수 값 저장을 위해 @State를 사용합니다.</p>
<p>근데 또 보면 어쩔때는 @Binding을 사용하죠?
차이가 뭘까요?</p>
<p>코딩에 영어가 필수란 말을 들어보셨을 건데, 여기서 체감 가능합니다.
Binding은 묶음이라는 뜻입니다.</p>
<p>뭐에 묶였길래 묶음이라고 했을까요?</p>
<p>다른 State에 묶어서 동작하기 때문입니다. </p>
<p>코드로 예시를 한 번 볼까요?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/a60fb86f-02f0-49ef-89ef-c60e6df8628a/image.png" alt=""></p>
<p>위 코드는 showChildView라는 상태 속성(@State)을 이용해서 true일 경우 ChildView가 나오고,
false일 경우 나타나지 않습니다.</p>
<p>프리뷰를 보면 아래처럼 나옵니다:</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/da35bd43-25d8-402d-aaf6-a86b41dbcd27/image.png" alt=""></p>
<p>클릭하면?
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/79fce388-44f1-4c24-84e5-f42cbeefe194/image.png" alt=""></p>
<p>이렇게 됩니다.</p>
<p>어 근데 이러면 전 화면으로 어떻게 돌아가나요?
showChildView를 false로 바꿔주면 되겠죠?</p>
<p>어디 한번 해봅시다.</p>
<p>ChildView가 나타났을 때니, ChildView에서 showChildView를 false로 바꿔주는 버튼을 만들면 되겠죠?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/d53bc3d8-5497-440b-bcc6-66de878e599f/image.png" alt=""></p>
<p>네. 안됩니다. 같은 scope에 있지 않아서죠.</p>
<p>아니 그럼 영원히 닫지 못 하는 창을 계속 띄우고 있어야 하냐고요?</p>
<p>다행스럽게도 방법이 있습니다.</p>
<p>@Binding을 사용해서 ParentView에 있는 showChildView 상태 속성과 연결해 주면 됩니다.</p>
<p>@Binding을 추가해 주면 아래와 같이 뜨는데요,
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/c073ce81-a717-4a9a-9619-438f9db2be84/image.png" alt=""></p>
<p>ChildView()를 호출할 때 파라미터로 Binding을 요구하는 것을 볼 수 있습니다.</p>
<p>그럼 다시 ParentView()로 돌아가서, ChildView의 파라미터를 넣어주면?
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/eb947b1f-2fa8-414c-9685-7be37d6e6ec6/image.webp" alt=""></p>
<p>와! 해결!</p>
<p>한줄 요약: @State에 묶어서 사용하는 것이 @Binding이다.</p>
<blockquote>
</blockquote>
<p>배움에 도움이 된 영상
<a href="https://www.youtube.com/watch?v=uiptHyyxF3Y">https://www.youtube.com/watch?v=uiptHyyxF3Y</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[고차함수의 클로저가 헷갈려요...? (feat. reduce)]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98%EC%9D%98-%ED%81%B4%EB%A1%9C%EC%A0%80%EA%B0%80-%ED%97%B7%EA%B0%88%EB%A0%A4%EC%9A%94...-feat.-reduce-hnr38k29</link>
            <guid>https://velog.io/@dev-hamin-kim/%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98%EC%9D%98-%ED%81%B4%EB%A1%9C%EC%A0%80%EA%B0%80-%ED%97%B7%EA%B0%88%EB%A0%A4%EC%9A%94...-feat.-reduce-hnr38k29</guid>
            <pubDate>Wed, 04 Dec 2024 14:41:48 GMT</pubDate>
            <description><![CDATA[<h1 id="아-맞다">아 맞다.</h1>
<p>생각해보니, 저번 포스팅에서 제목에 reduce를 넣어놓고는 내용에선 까먹고 적지 않은 것이 있더라고요?</p>
<p>그래서 이어서 적어보려 합니다.</p>
<p>후행 클로저와도 어느정도 연관이 있는 이야기다 보니, 어찌보면 잘 된 일이겠죠.</p>
<p><a href="https://velog.io/@dev-hamin-kim/%ED%81%B4%EB%A1%9C%EC%A0%80%EA%B0%80-%ED%97%B7%EA%B0%88%EB%A0%A4...%EC%9A%94-feat.-filter">저번 포스팅에서</a> 썼던 reduce 예제를 다시 가져와보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/dfc536b4-aa96-45e8-acfa-4134c1227163/image.png" alt=""></p>
<p>reduce를 쓰다보면, 트레일링 클로저를 사용해 어떻게 reduce를 할지 전달해 주곤 합니다.</p>
<blockquote>
</blockquote>
<p>트레일링 클로저가 뭔지 모른다고요? <a href="https://velog.io/@dev-hamin-kim/%ED%81%B4%EB%A1%9C%EC%A0%80%EA%B0%80-%ED%97%B7%EA%B0%88%EB%A0%A4...%EC%9A%94-feat.-filter">클릭하십시오 휴먼.</a>
또는 클로저가 뭔지, 또는 클로저 축약문법이 익숙하지 않다고요? <a href="https://velog.io/@dev-hamin-kim/%ED%81%B4%EB%A1%9C%EC%A0%80%EB%8A%94-%ED%95%A8%EC%88%98%EB%8B%A4">클릭하십시오 휴먼.</a></p>
<h2 id="클로저-또-너냐">클로저 또 너냐</h2>
<p>위의 글을 읽었거나, 클로저에 대한 어느정도의 이해가 있다면 말이죠,</p>
<pre><code class="language-swift">let 머리부터발끝까지: [String] = [&quot;머리&quot;, &quot;부터&quot;, &quot;발끝&quot;, &quot;까지&quot;]

let 오로나민씨 = 머리부터발끝까지.reduce(&quot;&quot;) { $0 + $1 }

print(오로나민씨 + &quot;오로나민씨&quot;) // 머리부터발끝까지오로나민씨</code></pre>
<p>여기서 $0, $1이 왜 쓰이는지 아실 겁니다.</p>
<p>근데 이건 어떨까요?</p>
<pre><code class="language-swift">let 머리부터발끝까지: [String] = [&quot;머리&quot;, &quot;부터&quot;, &quot;발끝&quot;, &quot;까지&quot;]

let 사랑스러워 = 머리부터발끝까지.reduce(&quot;&quot;, +)

print(사랑스러워 + &quot;다사랑스러워&quot;) // 머리부터발끝까지다사랑스러워</code></pre>
<p>이게 왜 되지? 싶지 않으신가요?</p>
<pre><code class="language-swift">let 오로나민씨 = 머리부터발끝까지.reduce(&quot;&quot;) { $0 + $1 }
let 사랑스러워 = 머리부터발끝까지.reduce(&quot;&quot;, +)</code></pre>
<p>아니, 트레일링 클로저까지만 알면 끝난 거 아니었어? 하실 수 있습니다.</p>
<p>하지만 이번엔 비교적 짧으니 좀만 견뎌보시죠.</p>
<h2 id="q-저게-왜-되냐">Q. 저게 왜 되냐?</h2>
<h3 id="a-고차함수는-클로저를-받으니까요">A: 고차함수는 클로저를 받으니까요!</h3>
<p>예제로 쓰인 고차함수 reduce의 정의를 볼까요?
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/855fdb4f-e139-4f52-9662-8f314aca8b8f/image.png" alt=""></p>
<p>위의 정의에서 클로저를 받는 아래 코드를 봅시다.</p>
<pre><code class="language-swift">nextPartialResult: (Result, Self.Element) throws -&gt; Result</code></pre>
<p>이해를 돕기 위해 헷갈릴 수 있는 제네릭과 에러 처리 구문 throws를 없애보겠습니다.</p>
<pre><code class="language-swift">nextPartialResult: (String, String) -&gt; String</code></pre>
<p>즉, nextPartialResult는 String 2개를 받아 String을 반환하는 클로저(익명함수)를 받는 함수의 전달인자인 것입니다.</p>
<p>근데 <a href="https://velog.io/@dev-hamin-kim/%ED%81%B4%EB%A1%9C%EC%A0%80%EB%8A%94-%ED%95%A8%EC%88%98%EB%8B%A4">클로저 첫 번째 시리즈</a>를 보셨으면 아시겠지만, 보통 우리가 클로저라고 부르는 것은 익명 함수입니다.</p>
<p>네. 다시 말하지만 클로저는 함수입니다.</p>
<p>근데 우리가 자각하지 못 하는 다른 함수 친구가 또 있다면 믿으시겠습니까?</p>
<p>바로 연산자들입니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/3b82e2a0-3894-4280-bb5d-b9687f7d0153/image.png" alt=""></p>
<blockquote>
<p>일반 함수들과는 다르게 <code>prefix/postfix/infix operator</code>라고 선언해줘야 연산자 앞/뒤의 값을 가져올 수 있어서 쵸큼 다르긴 한데...</p>
</blockquote>
<p>함수라는건 똑같으니 일단 계속 진행하겠습니다.</p>
<p><em><strong>연산자가 함수이니,
함수를 전달인자로 받는 고차함수에서 사용이 가능한 것입니다.</strong></em></p>
<pre><code class="language-swift">let 오로나민씨 = 머리부터발끝까지.reduce(&quot;&quot;) { $0 + $1 }
let 사랑스러워 = 머리부터발끝까지.reduce(&quot;&quot;, +)</code></pre>
<h3 id="그래서-이런-짓도-가능합니다">그래서 이런 짓도 가능합니다</h3>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/c28ad2fb-b3c7-4df1-a945-956aa15ee5c7/image.png" alt=""></p>
<pre><code class="language-swift">import Foundation

let 머리부터발끝까지: [String] = [&quot;머리&quot;, &quot;부터&quot;, &quot;발끝&quot;, &quot;까지&quot;]

// 클로저로 작성하여 전달.
let 힘차게 = { $0 + $1 + &quot;!&quot; }

// 중위 연산자 ~ 선언 후,
infix operator ~

// 함수로 작성하여 전달.
func ~ (lhs: String, rhs: String) -&gt; String {
    return lhs + rhs + &quot;~&quot;
}

let 사랑스러워 = 머리부터발끝까지.reduce(&quot;&quot;, +)
let 힘차게머리부터발끝까지 = 머리부터발끝까지.reduce(&quot;&quot;, 힘차게)
let 늘어지게머리부터발끝까지 = 머리부터발끝까지.reduce(&quot;&quot;, ~)

print(사랑스러워 + &quot;다사랑스러워&quot;) // 머리부터발끝까지다사랑스러워
print(힘차게머리부터발끝까지 + &quot;다입수&quot;) // 머리!부터!발끝!까지!다입수
print(늘어지게머리부터발끝까지 + &quot;오로나민씨&quot;) // 머리~부터~발끝~까지~오로나민씨</code></pre>
<p>참 신기하지 않나요?</p>
<p>오늘은 여기까지입니다.</p>
<p>나가세요 이제.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/44e09ad3-33aa-4923-97c3-e77b0522c6ba/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/4203946d-e164-4ad5-918f-765f522fb98f/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/2e987e39-e057-4e35-9975-27647ac79714/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/3e9612ff-9f4f-4731-b139-926585c70cb5/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/734cadaf-2945-4cf9-a4a3-18a020f26e90/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/3305d15e-d8b4-4755-9145-50be266f4a29/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/b15e6831-a105-4d12-8c17-01cbe4a4d724/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Instance Property count를 파헤쳐보자.]]></title>
            <link>https://velog.io/@dev-hamin-kim/Instance-Property-count%EB%A5%BC-%ED%8C%8C%ED%97%A4%EC%B3%90%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev-hamin-kim/Instance-Property-count%EB%A5%BC-%ED%8C%8C%ED%97%A4%EC%B3%90%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 02 Dec 2024 14:22:48 GMT</pubDate>
            <description><![CDATA[<p>코딩테스트 문제를 풀다가 문득 .count의 시간복잡도에 대한 의문이 생겨서 파헤쳐봤습니다.</p>
<blockquote>
<p>입력값을 거르는 과정에서 .count를 이용해 걸러줄 것인가?
거르지 않아도 함수 내부 로직에서 O(n)의 시간복잡도로 걸러지는 구조이다.
즉, 다른 말로 하자면?</p>
<blockquote>
</blockquote>
<p>.count가 O(n)의 시간복잡도를 가진다면 손해, O(1)라면 이득인 구조.</p>
</blockquote>
<p>그래서 찾아보니, collection이 RandomAccessCollection 프로토콜에 부합하면,
instance property인 count가 O(1)의 시간복잡도를 가진다고 하네요?</p>
<p>부합 안 하면 O(n) 이래요.</p>
<p>제가 뭐 어떻게 지어낸 말이 아니라 애플이 그렇대요.</p>
<blockquote>
<p><strong>Complexity</strong>
O(1) if the collection conforms to RandomAccessCollection; otherwise, O(n), where n is the length of the collection.
<em>Source:</em> <a href="https://developer.apple.com/documentation/swift/collection/count-4l4qk"><em>Apple Developer Documentation</em></a></p>
</blockquote>
<p>그래서...</p>
<h3 id="randomaccesscolletion">RandomAccessColletion</h3>
<p>은 또 뭔지 찾아봤습니다.</p>
<p>오호... 흥미롭네요...</p>
<p>이 프로토콜에 대해선 나중에 따로 글을 써 보도록 하고,</p>
<p>일단은 요점만 쓰자면, Array의 경우 Array의 Element가 Copyable과 Escapable 프로토콜을 따르는 경우, RandomAccessColletion 프로토콜에 부합한다고 하네요.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/a0428ac4-a9c4-4b12-8e66-2c6a493bcc09/image.png" alt=""></p>
<p>일단 <a href="https://developer.apple.com/documentation/Swift/Copyable">Character는 Copyable 프로토콜을 따릅니다.</a>
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/a5fd2fc5-9fc7-4cc9-897d-27e5e31aee77/image.png" alt=""></p>
<p>그럼 Escapable은?</p>
<p><a href="https://developer.apple.com/documentation/foundation">공식문서</a>에서 찾아봐도 안 나오네요?</p>
<p>Swift 6에서 새롭게 도입된 프로토콜인 것 같은데... <em>일해라 애플</em></p>
<p>일단은 <a href="https://swiftinit.org/ptcl/swift/swift/escapable">여기</a>선 String과 Character가 Escapable 프로토콜을 준수한다고 하니,
참이라고 간주하고  이어가자면,</p>
<p>지금처럼 [Character]를 쓰는 케이스에선 RandomAccessCollection 프로토콜에 부합하는 Collection이니, O(1)의 시간복잡도로 count를 활용할 수 있을 것으로 추론했습니만...!</p>
<h3 id="뻘짓이었습니다">뻘짓이었습니다</h3>
<p>그 문제가요...</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/4753dffb-911f-44b1-9531-9a05b32ca37d/image.png" alt=""></p>
<p>사실 입력으로 String을 받는 경우였고...</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/33f5583d-0ef8-4d12-af2e-6da31a0475cc/image.png" alt=""></p>
<p>String의 인스턴스 프로퍼티 count는 O(n)의 시간복잡도를 가진다고 공식문서에 명시되어 있네요.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/985a3e84-caa2-474d-9ed3-2a85e3419ec9/image.jpg" alt=""></p>
<p>그래도 지식이 늘었잖습니까?</p>
<p>한 잔 합시다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/478f129e-8078-4753-8ac1-991b93bac885/image.gif" alt=""></p>
<p>그래도 오늘까지 배운 지식으로 추론해보면, 
<del>나중에 공식문서에서 String의 .count가 O(1)의 시간복잡도로 변경될 수도 있지 않을까? 하는 추측을 남겨놓습니다.</del>
<del>하지만 추론 과정 중, Array == Collection이지만 Collection != Array 이기에, 왠지 틀린 추측일 것 같아</del></p>
<p>수정합니다, 대신 위에 추측했던 것에 대한 논리 흐름을 남겨놓습니다:</p>
<blockquote>
</blockquote>
<ol>
<li>RandomAccessCollection 프로토콜에 부합하는 Array의 count는 O(1)의 시간복잡도를 가진다.</li>
<li>Copyalbe과 Escapable 프로토콜에 부합하는 element로 이루어진 Array는 RandomAccessCollection이다.</li>
<li>String은 Collection 프로토콜을 따르며, String.element는 Character이다.</li>
<li>Character는 Copyable과 Escapable 프로토콜을 준수한다.<blockquote>
</blockquote>
따라서, [Character]의 .count는 O(1)의 시간복잡도를 가질 것이다.</li>
</ol>
<p>졸려서 내일 확인해봐야겠네요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[올바른 괄호]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B4%84%ED%98%B8</link>
            <guid>https://velog.io/@dev-hamin-kim/%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B4%84%ED%98%B8</guid>
            <pubDate>Mon, 02 Dec 2024 12:08:39 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<blockquote>
</blockquote>
<p>괄호가 바르게 짝지어졌다는 것은 &#39;(&#39; 문자로 열렸으면 반드시 짝지어서 &#39;)&#39; 문자로 닫혀야 한다는 뜻입니다. 예를 들어:</p>
<blockquote>
<blockquote>
</blockquote>
<p>&quot;()()&quot; 또는 &quot;(())()&quot; 는 올바른 괄호입니다.
&quot;)()(&quot; 또는 &quot;(()(&quot; 는 올바르지 않은 괄호입니다.</p>
</blockquote>
<p>&#39;(&#39; 또는 &#39;)&#39; 로만 이루어진 문자열 s가 주어졌을 때, 문자열 s가 올바른 괄호이면 true를 return 하고, 올바르지 않은 괄호이면 false를 return 하는 solution 함수를 완성해 주세요.</p>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12909#">링크</a></p>
<h2 id="1차-코드-작성">1차 코드 작성</h2>
<pre><code class="language-swift">import Foundation
func solution(_ s:String) -&gt; Bool {

    // 첫 문자가 &quot;)&quot; 경우 false를 리턴
    guard s.first == &quot;(&quot; else { return false }

    var answer = false
    var stack: [Character] = []
    for char in s {

        switch char {
        case &quot;(&quot;: stack.append(char)
        case &quot;)&quot;: stack.popLast()
        default:
            answer = false
            break
        }
    }
    if stack.isEmpty {
        answer = true
    }
    return answer
}</code></pre>
<p>설명이 필요하지는 않을 것 같아 생략합니다.</p>
<h3 id="1트-성공">1트 성공</h3>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/eae4f478-78f8-4def-b9d3-fef84c07187b/image.png" alt=""></p>
<p>아싸</p>
<h2 id="헛점발견">헛점발견</h2>
<p>근데 소소한 헛점을 발견해서 고쳤습니다.</p>
<pre><code class="language-swift">switch char {
case &quot;(&quot;: stack.append(char)
case &quot;)&quot;: stack.popLast()
default:
    answer = false
    break
}</code></pre>
<p>저기서 answer를 false로 설정해줘도 아래의 if문에서 stack이 비었으면 answer = true가 실행되기에 고쳐줬습니다.</p>
<pre><code class="language-swift">switch char {
case &quot;(&quot;: stack.append(char)
case &quot;)&quot;: stack.popLast()
default:
    return false
}</code></pre>
<p>물론, 로직 상 거의 발생하지 않을 문제이지만, 소 잃고 외양간 고치는 것 보다는 미리미리 방지하는 게 낫지 않습니까?</p>
<h2 id="또-다른-헛점-발견">또 다른 헛점 발견</h2>
<p>앞에 guard문 제대로 썼나 이것저것 집어넣어보다가 이걸 발견했습니다.</p>
<pre><code class="language-swift">&quot;())()&quot;</code></pre>
<p>이걸 집어넣으면 false를 리턴해야 하는데 true를 리턴하네요...?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/18d25295-228b-4005-a923-ba25bcae30aa/image.png" alt=""></p>
<p>않이?</p>
<p>근데 왜 통과시켜줘요 프로그래머스님아</p>
<p>여튼 고치긴 했습니다.</p>
<h3 id="고친-코드">고친 코드</h3>
<pre><code class="language-swift">import Foundation

func solution(_ s:String) -&gt; Bool {

    // 첫 문자가 &quot;)&quot;일 경우, 또는 마지막 문자가 &quot;(&quot; 일 경우,
    // 무조건 닫히지 않은 괄호이니, false를 리턴
    guard s.first == &quot;(&quot; || s.last == &quot;)&quot; else { return false }

    var answer = false
    var stack: [Character] = []

    for char in s {

        switch char {
        case &quot;(&quot;: stack.append(char)
        case &quot;)&quot;:
            // &quot;())()&quot;와 같은 케이스 걸러주기 위해,
            // popLast가 nil일 경우 false를 리턴하도록 작성.
            if stack.popLast() == nil { return false }
        default: // 문자열 중 괄호 외의 문자를 발견 시 false를 리턴하도록 하는
            return false
        }
    }

    if stack.isEmpty {
        answer = true
    }

    return answer
}</code></pre>
<h4 id="결과는">결과는?</h4>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/da483ce3-9a42-407c-a9e0-940ae5fd4ed7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/52f80d21-1184-46ec-914a-18ba9630c160/image.png" alt=""></p>
<p>통과입니다.</p>
<h2 id="아니-그래서-글자수는-왜-안-셌음">아니 그래서 글자수는 왜 안 셌음?</h2>
<p>세볼까 생각을 했는데요, 그게 생각보다 간단한 문제가 아니더라고요.
count의 시간복잡도가 O(n)이냐 O(1)이냐에 따라 다른데...</p>
<p>그게 자료형이 어떤 프로토콜을 따르는지에 따라 다르답니다.
이에 대해 찾아보다가 따로 <a href="https://velog.io/@dev-hamin-kim/Instance-Property-count%EB%A5%BC-%ED%8C%8C%ED%97%A4%EC%B3%90%EB%B3%B4%EC%9E%90">글을</a> 쓰게 되었습니다.</p>
<p>요약하자면, [Character]에서 .count를 쓰면, O(1)의 시간복잡도를,
String에서 .count를 쓰면 O(n)의 시간복잡도를 가진다는 내용입니다.</p>
<p>그니까, 어차피 for-in 반복문 쓰면 O(n)의 시간복잡도가 비용으로 지불되는데,
그 전에 굳이 또 한 번의 O(n)을 들여서 글자수를 셀 필요가 없다는 것이죠.</p>
<p>그래서 안 셌습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/949d44c0-2262-4283-8c37-ade89211c656/image.jpeg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[키오스크] 있었는데요 없었습니다]]></title>
            <link>https://velog.io/@dev-hamin-kim/%ED%82%A4%EC%98%A4%EC%8A%A4%ED%81%AC-%EC%9E%88%EC%97%88%EB%8A%94%EB%8D%B0%EC%9A%94-%EC%97%86%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@dev-hamin-kim/%ED%82%A4%EC%98%A4%EC%8A%A4%ED%81%AC-%EC%9E%88%EC%97%88%EB%8A%94%EB%8D%B0%EC%9A%94-%EC%97%86%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Thu, 28 Nov 2024 12:18:04 GMT</pubDate>
            <description><![CDATA[<p>제목을 본 사람들의 반응은 크게 세 가지로 나뉠 것으로 예상하는데요,</p>
<blockquote>
</blockquote>
<ol>
<li>&quot;이게 무슨 말이냐...&quot;라고 의문이 드셨을 분,</li>
<li>&quot;아 이 밈 중독자 <span style="color: rgba(300,179,180,1)"><em>[검열됨]</em></span> 또 이러네&quot;라고 생각하셨을 분,</li>
<li>&quot;뭔가 사라지는 버그를 발견했나 보구만&quot;이라고 눈치채신 눈치 빠른 분.</li>
</ol>
<p>네.</p>
<p>이 포스트는 무언가 사라지는 버그를 잡는 과정을 기록한 포스트입니다.</p>
<p>이 글을 눈치 빠른 3번 유형의 사람들에게 바칩니다.</p>
<h1 id="그래서-무슨-버그냐">그래서 무슨 버그냐?</h1>
<p>백문이 불여일견이라고, 일단 보여드리겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/7448526a-4b57-4f76-9e8d-6a66b187228b/image.gif" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/2aac1155-4081-48b6-9f47-98551a8506a4/image.gif" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/4569deeb-d9c9-4697-9bc1-1acb9b66317b/image.jpg" alt=""></p>
<p>네... 말이 필요없죠...</p>
<p>굳이 말로 풀어서 쓰자면,
음료 갯수를 더하거나 빼면 갯수를 표시해주는 라벨이 뒤틀리거나 사라지는 버그입니다.</p>
<p>아니 왜 이러는 걸까요?</p>
<p>생각을 해봅시다.</p>
<h2 id="처음에는-말이죠">처음에는 말이죠...</h2>
<p>연산 프로퍼티 didSet을 사용해서 라벨의 텍스트가 변경되는 방식이라, 그 녀석을 의심했습니다.</p>
<p>근데 암만 봐도 그건 아닌 것 같더라고요. 그랬다면 애초에 초기 설정부터 이상하게 되었을 테니까요.</p>
<p>라벨 레이아웃이 뒤틀리는 것으로 보아, 
라벨을 설정해주는 함수를 라벨이 바뀔 때 마다 다시 호출해야 하나?라는 생각도 했습니다.</p>
<p>그리고 이런저런 생각을 머릿속으로 했지만 결과적으론 틀린 추론이었습니다.</p>
<p>그래서 답답한 마음에 계속해서 버그를 뚫어져라 보다보니 미묘한 점을 발견했습니다...</p>
<h2 id="미묘한-움직임">미묘한 움직임</h2>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/e77af943-2f3f-45e1-a0ba-75ccfd162f10/image.gif" alt=""></p>
<p>버튼을 클릭할 때 가격 표시와 +/- 버튼이 움찔거리는 것이 보이시나요?</p>
<p>저기가 뭔가 의심스러웠습니다.</p>
<p>그리고 그 의심은 아래에서 점점 확신이 되어갔습니다.</p>
<h2 id="계속-일부러-고장내보자">계속 일부러 고장내보자</h2>
<p>그래서 일부러 고장내고 View Hierarchy를 보고 단서를 찾으려고 하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/002419d7-c955-421a-ae92-b754fddc31c1/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/d90dcc01-cf3b-4ce7-8821-ce0ffebae3c3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/da966ee2-f6c3-4bda-b9e4-07d4c6ca5090/image.png" alt=""></p>
<p>위의 View Hierarchy를 토대로, 아래와 같이 추측했습니다.</p>
<blockquote>
</blockquote>
<ol>
<li>음료의 갯수를 표현하는 숫자 라벨의 너비가 변경됨 (1 &gt;&gt; 2나 9 &gt;&gt; 10 처럼 너비가 크게 변하면)</li>
<li>그 숫자 라벨을 둘러싸고 있는 -/+ 버튼의 위치가 오락가락함.</li>
<li>숫자가 바뀌면서 오락가락하다가 뷰가 무너짐. (이에 대한 정확한 명칭은 잘 모르겠습니다...)</li>
</ol>
<h3 id="그래서">그래서...?</h3>
<blockquote>
</blockquote>
<p>설마 숫자 라벨에 constraint가 적용이 안 되어서 와장창 무너진 것은 아닌가?</p>
<p>라는 가설을 세우고, 해결하기 위해 아래 코드로 숫자 라벨의 widthAnchor와 heightAnchor의 constraint를 지정해줬습니다.</p>
<pre><code class="language-swift">label.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
    label.widthAnchor.constraint(equalToConstant: 10)
    label.heightAnchor.constraint(equalToConstant: 10)
])
</code></pre>
<p>문제가 확인되었을까요?</p>
<p>빌드를 눌러서 확인해봅시다.</p>
<p>빌드가 안되네?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/23f1b6f5-2be7-4e60-a853-7a00128d1b25/image.png" alt=""></p>
<p>아</p>
<p>급하게 하느라 쉼표를 까먹었읍니다</p>
<p>ㅎㅎㅎ...</p>
<p>넣어주고 다시 해봅시다.</p>
<h1 id="잡았다-요놈">잡았다 요놈</h1>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/0cdee381-3fa9-4a6f-aa8f-99454dd0577e/image.gif" alt=""></p>
<p>오...!</p>
<p>좀 찌부되긴 했는데 여튼 일단 해결했잖습니까?</p>
<p>해결 방법을 발견하고 신나서 버그를 발견한 팀원에게 자랑을 하고 밥을 먹으러 갔습니다.</p>
<p>근데 밥을 먹고 온 사이 팀원이 코드를 수정하였다네요...?</p>
<p>어... 음... 떠넘기려는 건 아니었는데 그게... 그렇게 보이긴 하네요;</p>
<p>열심히 일한 우리 팀원에게 큰 박수 부탁드립니다.</p>
<p>여튼 팀원이 수정해준 최종 결과물은 이렇습니다:</p>
<h3 id="최종-결과물">최종 결과물</h3>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/a8d3968f-05ed-4be6-9b83-ffbdd2a1445c/image.gif" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/336ddd1e-8f38-475c-94d2-cc80aa9afca5/image.jpeg" alt=""></p>
<p>깔끔하네요.</p>
<h1 id="it-aint-over-til-its-over">It ain&#39;t over, &#39;til it&#39;s over</h1>
<p>이렇게 해피해피엔딩이었다면 좋았겠지만...</p>
<p>저희 팀은 또 다른 문제를 맞닥뜨리게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/010fa59c-3153-4aa8-a1b4-b74e18a2a356/image.webp" alt=""></p>
<p>(대충 다음에 계속된다는 소리)<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/6f5d0931-66f3-44f2-9d35-f7e30e6f0d3f/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[베스트앨범]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EB%B2%A0%EC%8A%A4%ED%8A%B8%EC%95%A8%EB%B2%94</link>
            <guid>https://velog.io/@dev-hamin-kim/%EB%B2%A0%EC%8A%A4%ED%8A%B8%EC%95%A8%EB%B2%94</guid>
            <pubDate>Fri, 22 Nov 2024 11:01:03 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<blockquote>
<p>스트리밍 사이트에서 장르 별로 가장 많이 재생된 노래를 두 개씩 모아 베스트 앨범을 출시하려 합니다. 노래는 고유 번호로 구분하며, 노래를 수록하는 기준은 다음과 같습니다.</p>
</blockquote>
<ol>
<li>속한 노래가 많이 재생된 장르를 먼저 수록합니다.</li>
<li>장르 내에서 많이 재생된 노래를 먼저 수록합니다.</li>
<li>장르 내에서 재생 횟수가 같은 노래 중에서는 고유 번호가 낮은 노래를 먼저 수록합니다.<blockquote>
</blockquote>
노래의 장르를 나타내는 문자열 배열 genres와 노래별 재생 횟수를 나타내는 정수 배열 plays가 주어질 때, 베스트 앨범에 들어갈 노래의 고유 번호를 순서대로 return 하도록 solution 함수를 완성하세요.</li>
</ol>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42579">링크</a></p>
<h2 id="처음-든-생각">처음 든 생각</h2>
<p>해쉬 문제니까 Dictionary 갖고 풀면 되겠다!라고 생각이 들어서,
hashMap이라는 이름의 딕셔너리 변수를 선언해주었습니다. 이렇게요:</p>
<pre><code class="language-swift">var hashMap: [String: [Int]] = [:]</code></pre>
<p>Key가 될 String에는 장르 이름이 들어가고, Value가 될 [Int]에는 노래의 인덱스, 재생 횟수 등이 들어가게 말이죠.</p>
<p>근데 생각해보니까 장르의 총 재생 횟수, 곡 별로 재생 횟수도 비교해야 되더라고요.</p>
<p>그래서 그냥 구조체를 만들었습니다 ㅎ</p>
<pre><code class="language-swift">struct GenreDetails {
    private(set) var songs: [Song] = []
    private(set) var totalPlays: Int = 0

    mutating func addSong(_ song: Song) {
        songs.append(song)
        totalPlays += song.played
    }
}

struct Song {
    let played: Int
    let index: Int
}</code></pre>
<p>그래서 요 구조체들이...</p>
<pre><code class="language-swift">var hashMap: [String: GenreDetails] = [:]</code></pre>
<p>요렇게 들어갈 수 있게요.</p>
<p>테스트나 함 돌려봅시다.</p>
<p>근데 [String: GenreDetails]라고 되어있으니 통일성이 좀 모자라서 마음이 불편해지네요.</p>
<p>Typealias를 사용해서 Genre로 String을 둔갑시켜줍니다.</p>
<pre><code class="language-swift">typealias Genre = String</code></pre>
<p>이러면 이제 String의 별칭으로 Genre를 쓸 수 있게 되구요,</p>
<pre><code class="language-swift">var hashMap: [Genre: GenreDetails] = [:]</code></pre>
<p>요런 편안하고 깔끔한 코드가 되게 됩니다.</p>
<p>그 뒤로는 그냥 문제 읽으면서... 적절히 코드를 작성해줬구요.</p>
<p>아래의 코드가 나왔습니다.</p>
<h3 id="코드">코드</h3>
<pre><code class="language-swift">import Foundation

typealias Genre = String

struct GenreDetails {
    private(set) var songs: [Song] = []
    private(set) var totalPlays: Int = 0

    mutating func addSong(_ song: Song) {
        songs.append(song)
        totalPlays += song.played
    }
}

struct Song {
    let played: Int
    let index: Int
}

func solution(_ genres:[String], _ plays:[Int]) -&gt; [Int] {
    var hashMap: [Genre: GenreDetails] = [:]
    var result: [Int] = []

    for index in 0..&lt;genres.count {
        let song = Song(played: plays[index], index: index)
        hashMap[genres[index], default: GenreDetails()] .addSong(song)
    }

    let sortedByTotalPlays = hashMap.sorted { (first, second) in
        return first.value.totalPlays &gt; second.value.totalPlays
    }

    for genre in sortedByTotalPlays {

        switch genre.value.songs.count {
        case 1: result.append(genre.value.songs[0].index)
        case 2:
            let first = genre.value.songs[0]
            let second = genre.value.songs[1]

            if first.played &gt; second.played {
                result.append(first.index)
                result.append(second.index)
            } else if first.played &lt; second.played {
                result.append(second.index)
                result.append(first.index)
            }
        case 3...:
            var inOrder = genre.value.songs.sorted { $0.played &lt; $1.played }

            let last = inOrder.popLast()
            let secondLast = inOrder.popLast()

            result.append((last?.index)!)
            result.append((secondLast?.index)!)


        default: print(&quot;error&quot;)
        }
    }

    return result
}</code></pre>
<h3 id="테스트도">테스트도?</h3>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/5f0447a0-905c-48ee-9b20-d852b8729bc8/image.png" alt=""></p>
<p>통과하는군요.</p>
<p>그럼 이대로 채점까지 ㄱㄱ합시다.</p>
<h2 id="그런데">그런데...</h2>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/a0adf28d-fad9-4418-8ff2-51121a298959/image.png" alt=""></p>
<p>넌 못지나간다를 시전하는 프로그래머스씨...
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/ff20bdee-b6b5-458b-bf66-f27bc419e458/image.gif" alt=""></p>
<p>않이외않돼는거야</p>
<p>빡침을 잠시 가라앉히고 문제를 다시 읽어보고 옵시다...</p>
<h3 id="간과한-것">간과한 것</h3>
<blockquote>
<ol start="3">
<li>장르 내에서 재생 횟수가 같은 노래 중에서는 고유 번호가 낮은 노래를 먼저 수록합니다.</li>
</ol>
</blockquote>
<p>신나게 문제 푸느라 마지막 기준을 그냥 무시해버려서... 안되었던 것입니다...</p>
<p>그래도 조건문 몇 개만 추가해주면 되니 얼른 가서 고치고 옵시다.</p>
<h4 id="고친-코드">고친 코드</h4>
<pre><code class="language-swift">import Foundation

struct GenreDetails {
    private(set) var songs: [Song] = []
    private(set) var totalPlays: Int = 0

    mutating func addSong(_ song: Song) {
        songs.append(song)
        totalPlays += song.played
    }
}

struct Song {
    let played: Int
    let index: Int
}

func solution(_ genres:[String], _ plays:[Int]) -&gt; [Int] {
    var hashMap: [String: GenreDetails] = [:]
    var result: [Int] = []

    for index in 0..&lt;genres.count {
        let song = Song(played: plays[index], index: index)
        hashMap[genres[index], default: GenreDetails()] .addSong(song)
    }

    let sortedByTotalPlays = hashMap.sorted { (first, second) in
        return first.value.totalPlays &gt; second.value.totalPlays
    }

    for genre in sortedByTotalPlays {

        switch genre.value.songs.count {
        case 1: result.append(genre.value.songs[0].index)
        case 2:
            let first = genre.value.songs[0]
            let second = genre.value.songs[1]

            if first.played &gt; second.played {
                result.append(first.index)
                result.append(second.index)
            } else if first.played &lt; second.played {
                result.append(second.index)
                result.append(first.index)
            } else if first.index &lt; second.index {
                result.append(first.index)
                result.append(second.index)
            } else if first.index &gt; second.index {
                result.append(second.index)
                result.append(first.index)
            }
        case 3...:
            var inOrder = genre.value.songs.sorted { $0.played &lt; $1.played }

            let last = inOrder.popLast()
            let secondLast = inOrder.popLast()

            if last?.played == secondLast?.played &amp;&amp; last!.index &gt; secondLast!.index {
                result.append((secondLast?.index)!)
                result.append((last?.index)!)
            } else {
                result.append((last?.index)!)
                result.append((secondLast?.index)!)
            }

        default: print(&quot;error&quot;)
        }
    }

    return result
}</code></pre>
<h3 id="그래서">그래서?</h3>
<p>잘 됐을까요?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/6f2bb68b-1ee8-48eb-acaf-ff7b8f971982/image.png" alt=""></p>
<p>예압</p>
<h2 id="근데-말이죠">근데 말이죠</h2>
<p>코드가 좀 지저분하기도 하고, 가장 큰 두 녀석을 뽑을 때 .sorted을 쓰기는 시간복잡도가 O(n log n)이라 아무래도 좀 마음이 불편하단 말이죠.</p>
<h3 id="그러니-두-가지를-목표로-코드를-수정해봅시다">그러니 두 가지를 목표로 코드를 수정해봅시다.</h3>
<ol>
<li>지저분한 코드 깔끔하게 변경하기</li>
<li>.sorted 대신 다른 방법으로 가장 재생횟수가 높은 두 곡 뽑기</li>
</ol>
<h4 id="1-지저분한-코드-깔끔하게-변경하기">1. 지저분한 코드 깔끔하게 변경하기</h4>
<p>지저분한 코드에 가장 큰 지분을 차지하는 반복문을 가지고 왔습니다.</p>
<pre><code class="language-swift">for genre in sortedByTotalPlays {

    switch genre.value.songs.count {
    case 1: result.append(genre.value.songs[0].index)
    case 2:
        let first = genre.value.songs[0]
        let second = genre.value.songs[1]

        if first.played &gt; second.played {
            result.append(first.index)
            result.append(second.index)
        } else if first.played &lt; second.played {
            result.append(second.index)
            result.append(first.index)
        } else if first.index &lt; second.index {
            result.append(first.index)
            result.append(second.index)
        } else if first.index &gt; second.index {
            result.append(second.index)
            result.append(first.index)
        }
    case 3...:
        var inOrder = genre.value.songs.sorted { $0.played &lt; $1.played }

        let last = inOrder.popLast()
        let secondLast = inOrder.popLast()

        if last?.played == secondLast?.played &amp;&amp; last!.index &gt; secondLast!.index {
            result.append((secondLast?.index)!)
            result.append((last?.index)!)
        } else {
            result.append((last?.index)!)
            result.append((secondLast?.index)!)
        }

    default: print(&quot;error&quot;)
    }</code></pre>
<p>으...
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/ec8e2d5e-3a32-4b12-b5fb-e4df16c61bfe/image.jpeg" alt=""></p>
<p>눈쌀이 찌푸려지는군요</p>
<p>밑에 2번에서 만들 getTwoMaxValues 메서드로 대체해줍니다.</p>
<pre><code class="language-swift">for genre in sortedByTotalPlays {

    if genre.value.songs.count == 1 {
        result.append(genre.value.songs[0].index!)
    } else if genre.value.songs.count &gt;= 2 {
        result.append(contentsOf: genre.value.getTwoMaxValues())
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/439a2354-d791-4c36-a3dc-4866c78036d7/image.jpg" alt=""></p>
<p>한결 낫네요.</p>
<h4 id="2-sorted-대신-다른-방법으로-가장-재생횟수가-높은-두-곡-뽑기">2. .sorted 대신 다른 방법으로 가장 재생횟수가 높은 두 곡 뽑기</h4>
<p>GenreDetail 구조체 내부에 새로운 메서드를 선언해줍니다.</p>
<p>가장 많은 플레이 수를 기록한 2곡을, 그리고 플레이수가 가장 많은 곡이 동일하면 인덱스가 적은 친구를 반환하도록 해줍니다.</p>
<pre><code class="language-swift">func getTwoMaxValues() -&gt; [Int] {
    var first = Song()
    var second = Song()

   // 반복문을 한번만 거치기에 시간복잡도가 O(n)이 됩니다.
    for song in songs {
        if song.played &gt; first.played {
            second = first
            first = song
        } else if song.played &gt; second.played {
            second = song
        }
    }

    if first.played == second.played &amp;&amp; first.index! &gt; second.index! {
        return [second.index!, first.index!]
    } else {
        return [first.index!, second.index!]
    }
}</code></pre>
<p>옵셔널 강제 추출을 좋아하진 않지만, 추후 호출이 될 때, Song이 두 개 이상 들어있는 Array에서만 호출을 하기 때문에 로직 상 안전할 것 같아서 강제 추출을 때려버렸습니다.</p>
<p>그래서... </p>
<h3 id="최종-코드">최종 코드</h3>
<pre><code class="language-swift">import Foundation

typealias Genre = String

struct GenreDetails {
    private(set) var songs: [Song] = []
    private(set) var totalPlays: Int = 0

    mutating func addSong(_ song: Song) {
        songs.append(song)
        totalPlays += song.played
    }

    func getTwoMaxValues() -&gt; [Int] {
        var first = Song()
        var second = Song()

        for song in songs {
            if song.played &gt; first.played {
                second = first
                first = song
            } else if song.played &gt; second.played {
                second = song
            }
        }

        if first.played == second.played &amp;&amp; first.index! &gt; second.index! {
            return [second.index!, first.index!]
        } else {
            return [first.index!, second.index!]
        }
    }
}

struct Song {
    let played: Int
    let index: Int?

    init() {
        self.played = 0
        self.index = nil
    }

    init(played: Int, index: Int?) {
        self.played = played
        self.index = index
    }
}

func solution(_ genres:[String], _ plays:[Int]) -&gt; [Int] {
    var hashMap: [Genre: GenreDetails] = [:]
    var result: [Int] = []

    for index in 0..&lt;genres.count {
        let song = Song(played: plays[index], index: index)
        hashMap[genres[index], default: GenreDetails()] .addSong(song)
    }

    let sortedByTotalPlays = hashMap.sorted { (first, second) in
        return first.value.totalPlays &gt; second.value.totalPlays
    }

    for genre in sortedByTotalPlays {

        if genre.value.songs.count == 1 {
            result.append(genre.value.songs[0].index!)
        } else if genre.value.songs.count &gt;= 2 {
            result.append(contentsOf: genre.value.getTwoMaxValues())
        }
    }

    return result
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/f3717ef4-8461-4c6b-ade5-5135e80807d5/image.png" alt=""></p>
<p>잘 되는군요.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/45e7e212-78ff-405c-acb8-f3d4bc5f94f1/image.jpeg" alt=""></p>
<h4 id="근데-사실">근데 사실...</h4>
<p>1.의 반복문에서</p>
<pre><code class="language-swift">else if genre.value.songs.count &gt;= 2 {
    result.append(contentsOf: genre.value.getTwoMaxValues())
}</code></pre>
<p>요 부분은 count가 2일때 약간의 성능 상 손해보는 부분이 있긴 합니다.</p>
<p>getTwoMaxValues()메서드를 거칠 필요 없이 두 노래의 재생횟수를 비교하면 되는데, 메서드를 거침으로써 약간의 손해를 봐서 그렇습니다.</p>
<p>극한의 성능을 쥐어짜야한다면 저것까지도 고려하는 것이 좋겠습니다.</p>
<p>여튼 여기까지만 해야겠네요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[계산기] 계속해서 UI를 만들어보자]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EA%B3%84%EC%82%B0%EA%B8%B0-%EA%B3%84%EC%86%8D%ED%95%B4%EC%84%9C-UI%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev-hamin-kim/%EA%B3%84%EC%82%B0%EA%B8%B0-%EA%B3%84%EC%86%8D%ED%95%B4%EC%84%9C-UI%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 21 Nov 2024 08:50:24 GMT</pubDate>
            <description><![CDATA[<p>전편에 버튼 뼈대를 만들었으니 이제 구성해봅시다.</p>
<p>일단 가로로 뻗은 버튼스택을 만들어줄 메서드 makeHorizontalButtonStack()을 작성해봅시다.</p>
<pre><code class="language-swift">class ViewController: UIViewController {

    // 이전 코드 생략
    let buttonLayout = [
        &quot;7&quot;, &quot;8&quot;, &quot;9&quot;, &quot;+&quot;
//        &quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;-&quot;,  일단
//        &quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;*&quot;,  한줄만 
//        &quot;AC&quot;, &quot;0&quot;, &quot;=&quot;, &quot;/&quot;, 생성하기.
    ]

    // 중략

    private func makeHorizontalButtonStack(with buttonLayout: [String]) -&gt; UIStackView {
        let buttonStack = UIStackView()

        buttonLayout.forEach {
            let button = Button(title: $0)

            button.snp.makeConstraints {
                $0.width.height.equalTo(80)
            }

            buttonStack.addArrangedSubview(button)
        }

        buttonStack.axis = .horizontal
        buttonStack.backgroundColor = .black
        buttonStack.spacing = 10
        buttonStack.distribution = .fillEqually

        return buttonStack
    }

}</code></pre>
<p>이렇게 해서 만들어진 UIStackView를 configureUI에 넣고 실행해주면?</p>
<pre><code class="language-swift">class ViewController: UIViewController {

    let label = UILabel()
    let buttonLayout = [
        &quot;7&quot;, &quot;8&quot;, &quot;9&quot;, &quot;+&quot;
//        &quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;-&quot;,
//        &quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;*&quot;,
//        &quot;AC&quot;, &quot;0&quot;, &quot;=&quot;, &quot;/&quot;,
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        let buttonStacks = makeHorizontalButtonStack(with: buttonLayout)

        numberLabel()
        view.addSubview(buttonStacks)

        buttonStacks.snp.makeConstraints {
            $0.top.equalTo(label.snp.bottom).offset(50)
            $0.centerX.equalToSuperview()
        }

    }
    // 후략
}</code></pre>
<p>이런 게 나온다!</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/de9d981e-11fb-435f-815b-223bc880de7d/image.png" alt=""></p>
<p>와!</p>
<p>의도했던 대로 기호를 넣으니까 저절로 색상이 설정되는 것이 아주 마음이 편안하군요.</p>
<p>한번 다른 녀석들로도 시험해봅시다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/fc537126-baa9-4f72-8a53-549b68c071cc/image.png" alt=""></p>
<p>바람직하군요.</p>
<p>근데 생각을 해보니까 buttonLayout을 2차원 배열([[String]])으로 만들면 더 깔끔하게 가능하지 않을까라는 생각이 듭니다.</p>
<p>바로 실행해줍니다.</p>
<pre><code class="language-swift">class ViewController: UIViewController {

    let buttonLayout = [
        [&quot;7&quot;, &quot;8&quot;, &quot;9&quot;, &quot;+&quot;],
        [&quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;-&quot;],
        [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;*&quot;],
        [&quot;AC&quot;, &quot;0&quot;, &quot;=&quot;, &quot;/&quot;]
    ]

    // 후략
}</code></pre>
<p>이렇게 buttonLayout을 2차원 배열로 변경해주고,</p>
<p>아까 만들었던 메서드 makeHorizontalButtonStack을 활용한 새로운 메서드 makeButtonStack을 만들어서,</p>
<pre><code class="language-swift">class ViewController: UIViewController {

// 생략

    private func makeButtonStack(with buttonLayout: [[String]]) -&gt; UIStackView {
        let verticalStack = UIStackView()

        buttonLayout.forEach {
            let horizontalStack = makeHorizontalButtonStack(with: $0)

            verticalStack.addArrangedSubview(horizontalStack)
        }

        verticalStack.axis = .vertical
        verticalStack.backgroundColor = .black
        verticalStack.spacing = 10
        verticalStack.distribution = .fillEqually

        return verticalStack
    }

}</code></pre>
<p>이렇게 해주면?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/c56e2097-551a-45e9-af5b-2bc960185a7c/image.png" alt=""></p>
<p>짜쟌.</p>
<p>아주 오이시하고 우마이합니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/11a78b92-6f2a-47b6-9700-a24e60af65d0/image.jpeg" alt=""></p>
<p>덤으로 버튼 연결도 확인해 봤는데,</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/036afb1e-26b9-48e4-ad2b-8369fccfaad2/image.png" alt=""></p>
<p>잘 되었군요.</p>
<p>이히히히히히히히힣</p>
<p>혹시나 하는 마음에 (저번 숫자야구처럼) 어디선가 루프를 만들었는데 모르고 있는건 아닌지 확인하려고 스레드도 살펴봤습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/568aa327-cc22-4094-9282-9a00e97b2f7e/image.png" alt=""></p>
<p>별일... 없어보이죠?</p>
<p>넘어갑시다...</p>
<p>근데 UI는 이제 다 짠 거 아닌가...?</p>
<p>제목을 &quot;로직을 만들어보자&quot;로 해서 새로운 글을 만든 뒤에 계속 진행해봅시다.</p>
<p>다음 글에서 뵙겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/890a461e-46b4-4af5-9864-ee2ed0afd689/image.webp" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[계산기] 이번엔 UI를 만들어보자]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EA%B3%84%EC%82%B0%EA%B8%B0-%EC%9D%B4%EB%B2%88%EC%97%94-UI%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev-hamin-kim/%EA%B3%84%EC%82%B0%EA%B8%B0-%EC%9D%B4%EB%B2%88%EC%97%94-UI%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Wed, 20 Nov 2024 00:56:43 GMT</pubDate>
            <description><![CDATA[<h1 id="lv-1">Lv. 1</h1>
<p>Lv. 1에선 계산기의 메인 숫자 라벨을 만들어 줄 겁니다.</p>
<p>음... 뭔가 글을 쓸 만한 껀덕지가 없네요.</p>
<p>일단 사용한 코드 먼저 봅시다.</p>
<h2 id="코드">코드</h2>
<pre><code class="language-swift">import UIKit
import SnapKit

class ViewController: UIViewController {

    let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {

        view.backgroundColor = .black

        label.text = &quot;12345&quot;
        label.font = .boldSystemFont(ofSize: 60)
        label.textColor = .white
        label.textAlignment = .right

        view.addSubview(label)

        label.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview().inset(30)
            make.top.equalToSuperview().inset(200)
            make.height.equalTo(100)
        }

    }

}</code></pre>
<p>이렇게 썼고</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/387e3049-1c3a-4189-ae82-5dd101f3773e/image.png" alt=""></p>
<p>이렇게 나왔습니다.</p>
<p>나중에 엄청 수정될 예정이니 다음으로 넘어가겠습니다.</p>
<h1 id="lv-2--5">Lv. 2 ~ 5</h1>
<h2 id="를-진행하기-전에">를 진행하기 전에...</h2>
<p>다음 단계들을 쓱 훑어보니, 여러 생각이 들었습니다.</p>
<p>나열하자면:</p>
<blockquote>
</blockquote>
<ol>
<li>버튼 위치를 차후에 변경하려면 번잡할 것 같다.</li>
<li>각 버튼 별 역할(숫자, 연산자, 변형자)에 따라 색상이 달라져야 하는데, 일일히 지정해 주기 싫다.</li>
<li>나중에 로직을 구현하면서 위에 말한 각 버튼 별 역할에 따라 동작을 달리 해줘야 할 것 같은데, 코드가 지저분해질 것 같다.</li>
</ol>
<h3 id="그래서-다음과-같은-조치를-취하였습니다">그래서 다음과 같은 조치를 취하였습니다:</h3>
<h4 id="1-버튼-위치를-차후에-변경하기-쉽게-하자">1. 버튼 위치를 차후에 변경하기 쉽게 하자</h4>
<p>buttons라는 [String]을 생성해 원하는 버튼을 배열 안에 제가 지정해주면 추후 버튼을 생성할 때 forEach를 사용해서 할 생각으로 만들었습니다.</p>
<p>뭐 이런↓ 느낌이죠?</p>
<pre><code class="language-swift">// 이전 코드는 적절히 생략되었습니다.

class ViewController: UIViewController {

    let buttons: [String] = [
        &quot;7&quot;, &quot;8&quot;, &quot;9&quot;, &quot;+&quot;,
        &quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;-&quot;,
        &quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;*&quot;,
        &quot;AC&quot;, &quot;0&quot;, &quot;=&quot;, &quot;/&quot;,
    ]

// 이후 코드 또한 적절히 생략되었습니다.</code></pre>
<p>나중에 할 때 잘... 되겠죠...?
<em>(뉴비라해보기전까지될지안될지진짜모름)</em></p>
<h4 id="2-각-버튼-별-역할에-따라-색상이-자동으로-달라지게-하자">2. 각 버튼 별 역할에 따라 색상이 자동으로 달라지게 하자</h4>
<p>그러기 위해서 버튼의 유형을 먼저 enum으로 나누어주었습니다.</p>
<p>이렇게↓요.</p>
<pre><code class="language-swift">enum ButtonRole {
    case number // 숫자 (0...9)
    case operation // 연산자 (operator를 쓸랬는데 예약어 크리...)
    case modifier // 변형자 (AC, 하나 지우기 등)
    case undefined // 미지의 존재 (몰라 이거 뭐야 무서워)
}</code></pre>
<p>그 다음에 Button 클래스에서 입력받을 title에 따라 자동으로 지정되게 해주었습니다.</p>
<p>연산 프로퍼티(computed property)를 사용해서 말이죠.</p>
<pre><code class="language-swift">class Button: UIButton {
    var title: String

    var buttonRole: ButtonRole {
        if self.title.isNumber {
            return .number
        }

        switch self.title {
        case &quot;+&quot;, &quot;-&quot;, &quot;*&quot;, &quot;/&quot;: return .operation
        case &quot;AC&quot;, &quot;+/-&quot;, &quot;%&quot;: return .modifier
        default: return .undefined
        }
    }

 // 적절한 중략

 extension String {
    var isNumber: Bool {
        CharacterSet(charactersIn: self).isSubset(of: .decimalDigits)
    }    
}</code></pre>
<p>여담이지만 변수명을 buttonRole 대신 role, type을 쓰려고 했는데 이미 UIButton에 있어서... 어쩔 수 없이 buttonRole으로 지었습니다.</p>
<p>이렇게 입력받은 title의 값을 이용해서 buttonRole을 지정해줬으니, 버튼 색 또한 자동으로 지정되게 해 볼까요?</p>
<pre><code class="language-swift">class Button: UIButton {
    var title: String
    var buttonRole: ButtonRole { ... }

    var color: UIColor {
        switch self.buttonRole {
        // 일단은 임의의 색을 지정해줍니다.
        case .number: UIColor(red: 58/255,
                           green: 58/255,
                           blue: 58/255,
                           alpha: 1.0)
        case .operation: UIColor.systemOrange
        case .modifier: UIColor.systemGray
        case .undefined: UIColor.systemRed
        }

    }    
    // 후략
}</code></pre>
<p>이렇게 또 한번 연산 프로퍼티를 이용하여 색이 적절히 지정되게 해 줍니다.</p>
<p>그럼 이걸 써먹어야 하겠죠?</p>
<p>이니셜라이징 후 버튼에 대한 세부사항을 설정해주는 메서드를 만듭시다.</p>
<pre><code class="language-swift">// 이전 코드 생략

    init(title: String) {
        self.title = title
        super.init(frame: .zero)

        setButton()
    }

    func setButton() {
        setTitle(self.title, for: .normal)
        titleLabel?.font = .boldSystemFont(ofSize: 30)
        backgroundColor = color
        frame.size = CGSize(width: 80, height: 80)

        let action = #selector(onTap)
        addTarget(self, action: action, for: .touchDown)
    }

    @objc func onTap(_ sender: UIButton) {
        let title = sender.titleLabel?.text

        print(&quot;\(title ?? &quot;error&quot;) tapped&quot;)
    }</code></pre>
<p>요렇게요.</p>
<h4 id="3-로직을-구현하면서-위에-말한-각-버튼-별-역할에-따라-동작을-달리-해주자">3. 로직을 구현하면서 위에 말한 각 버튼 별 역할에 따라 동작을 달리 해주자</h4>
<p>는 위에서 설명이 되었네요.</p>
<p>넘어가겠습니다.</p>
<p>뭔가 성의 없다고 느껴지신다면 그게 정상입니다.</p>
<p>잠을 제대로 못 자서 제 정신이 아니거든요.</p>
<p>진짜 쓰러질 것 같으니
이만 자러가겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/d48767d8-5110-462c-aea4-69a713810028/image.webp" alt=""></p>
<p>(다음 시간에 계속...)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[고차함수 vs. 반복문]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98-vs.-%EB%B0%98%EB%B3%B5%EB%AC%B8</link>
            <guid>https://velog.io/@dev-hamin-kim/%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98-vs.-%EB%B0%98%EB%B3%B5%EB%AC%B8</guid>
            <pubDate>Sun, 17 Nov 2024 11:38:27 GMT</pubDate>
            <description><![CDATA[<h1 id="인생에는-수-많은-선택이-존재합니다">인생에는 수 많은 선택이 존재합니다.</h1>
<p>짜장이냐 짬뽕이냐,
김치찌개냐 된장찌개냐,
물복이냐 딱복이냐.</p>
<p>이런 논쟁들이 때론 과격해지기도 합니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/b718f0c0-1007-4df7-b5fe-489b699b8095/image.png" alt=""></p>
<p>이렇게 말이죠.</p>
<p>오죽하면, 인생은 B와 D사이의 C라는 말이 존재할 정도입니다.
많이들 들어보셨겠지만, Birth와 Death 사이의 Choice라는 말이죠.</p>
<p>아니라고요?</p>
<p>치킨이라고요?</p>
<p>어...</p>
<p>알파벳 기준으로 Birth와 Death 사이의 Chicken이고,
한글 기준으로 탄생(ㅌ)과 죽음(ㅈ) 사이의 치킨(ㅊ, ㅋ)이니 틀린 말이 아니긴 합니다.</p>
<p>치킨먹고싶네요.</p>
<p>뭐 여튼간에, Swift에서도 이러한 선택들이 존재합니다.</p>
<p>구조체를 쓸 것이냐 클래스를 쓸 것이냐,
UIKit을 쓸 것인가 SwiftUI를 쓸 것이냐,
고차함수를 쓸 것인가 반복문을 쓸 것이냐.</p>
<p>오늘은 고차함수와 반복문의 차이점에 대해 알아보겠습니다.</p>
<h1 id="고차함수-그게-뭔데">고차함수? 그게 뭔데?</h1>
<p>map, filter, reduce처럼 클로저(익명함수)를 전달받는 함수를 뜻합니다.</p>
<pre><code class="language-swift">let 김치찌개: [String] = [&quot;김&quot;, &quot;치&quot;, &quot;찌&quot;, &quot;개&quot;]

let 변형된김치찌개 = 김치찌개.map { $0 + $0 }
let 졸여진김치찌개 = 김치찌개.reduce(&quot;&quot;, +)
let 걸러진김치찌개 = 김치찌개.filter { &quot;찌개&quot;.contains($0) }

print(변형된김치찌개) // [&quot;김김&quot;, &quot;치치&quot;, &quot;찌찌&quot;, &quot;개개&quot;]
print(졸여진김치찌개) // 김치찌개
print(걸러진김치찌개) // [&quot;찌&quot;, &quot;개&quot;]</code></pre>
<p>대표적인 map, filter, reduce 말고도 더 있긴 한데 일단 3개만 예를 들었습니다.</p>
<p>같은 코드를 반복문으로 볼까요?</p>
<pre><code class="language-swift">let 김치찌개: [String] = [&quot;김&quot;, &quot;치&quot;, &quot;찌&quot;, &quot;개&quot;]

var 변형된김치찌개: [String] = []
for item in 김치찌개 {
    변형된김치찌개.append(item + item)
}

var 졸여진김치찌개: String = &quot;&quot;
for item in 김치찌개 {
    졸여진김치찌개 += item
}

var 걸러진김치찌개: [String] = []
for item in 김치찌개 {
    if &quot;찌개&quot;.contains(item) {
        걸러진김치찌개.append(item)
    }
}

print(변형된김치찌개) // [&quot;김김&quot;, &quot;치치&quot;, &quot;찌찌&quot;, &quot;개개&quot;]
print(졸여진김치찌개) // 김치찌개
print(걸러진김치찌개) // [&quot;찌&quot;, &quot;개&quot;]</code></pre>
<p>뭐 대략 이런 느낌입니다.</p>
<p>그래서 둘 중 뭐가 더 빠르냐? 뭘 어떨 때 써야 하느냐?
라는 질문에는,</p>
<h1 id="일단-결론부터-요약하자면">일단 결론부터 요약하자면:</h1>
<ol>
<li>고차함수<strong>만</strong> 사용할 경우는 대개 고차함수가 빠르다.</li>
<li>그러나 메서드 체이닝을 하게 된다면, 반복문이 빠르다.</li>
</ol>
<p>글로만 보니 조금 모호하죠?</p>
<p>차례대로 예를 들어가며 설명하겠습니다.</p>
<blockquote>
<p>아래의 내용은 <a href="https://www.skoumal.com/en/performance-of-built-in-higher-order-functions-map-filter-reduce-and-flatmap-vs-for-in-loop-in-swift/">이 문서의</a> 요약본이며, 원문을 읽어보시는 것을 추천드립니다.</p>
</blockquote>
<h2 id="1-고차함수만-사용할-경우에는-대개-고차함수가-빠르다">1. 고차함수만 사용할 경우에는 대개 고차함수가 빠르다.</h2>
<p><a href="https://www.skoumal.com/en/performance-of-built-in-higher-order-functions-map-filter-reduce-and-flatmap-vs-for-in-loop-in-swift/">이 문서에</a> 따르면, 실험을 진행했을 때:</p>
<ol>
<li><p>map 함수가 반복문보다 1.63배,</p>
</li>
<li><p>filter 함수가 반복문보다 1.11배 빠르지만,</p>
</li>
<li><p>reduce와 flatMap의 경우, 반복문이 각각 1.05배, 1.06배 빠른 것을 볼 수 있습니다.</p>
</li>
</ol>
<p>다만...</p>
<h2 id="2-메서드-체이닝을-하게-된다면-반복문이-빠르다">2. 메서드 체이닝을 하게 된다면, 반복문이 빠르다.</h2>
<h3 id="메서드-체이닝이-뭐죠">메서드 체이닝이 뭐죠?</h3>
<pre><code class="language-swift">let 김치찌개: [String] = [&quot;김&quot;, &quot;치&quot;, &quot;찌&quot;, &quot;개&quot;]

// 일반적인 사용
let 변형된김치찌개 = 김치찌개.map { $0 + $0 }
let 졸여진김치찌개 = 김치찌개.reduce(&quot;&quot;, +)
let 걸러진김치찌개 = 김치찌개.filter { &quot;찌개&quot;.contains($0) }

// 메서드 체이닝
let 변형되고졸여지고걸러진김치찌개 = 김치찌개.map { $0 + $0 }.reduce(&quot;&quot;, +).filter { &quot;찌개&quot;.contains($0) }

print(변형된김치찌개) // [&quot;김김&quot;, &quot;치치&quot;, &quot;찌찌&quot;, &quot;개개&quot;]
print(졸여진김치찌개) // 김치찌개
print(걸러진김치찌개) // [&quot;찌&quot;, &quot;개&quot;]
print(변형되고졸여지고걸러진김치찌개) // 찌찌개개</code></pre>
<p>요래... 스까서 쓰는 걸 메서드 체이닝이라고 합니다.</p>
<h3 id="map--filter--reduce">map + filter + reduce</h3>
<p>이렇게 위 3개의 고차함수를 섞어서 쓸 경우, 각자 O(n)의 시간복잡도를 가지고 있는 관계로 <a href="https://stackoverflow.com/questions/49887447/swifts-map-and-filter-functions-time-complexity">(출처)</a>, for-in을 사용한 반복문에 비해 2.37배 느린 것을 확인할 수 있습니다.</p>
<p>긍까 방대한 데이터를 다룰 땐 지양하는 것이 좋겠다...가 결론이 되겠군요.</p>
<h1 id="파생되는-의문점">파생되는 의문점</h1>
<ol>
<li>메서드 체이닝을 하는 대신 Collection의 extension으로 커스텀 함수를 만들어서 쓴다면 똑같은 반복문보다 성능이 좋게 나올까?</li>
<li>스택 오버플로우 댓글 중에 Playground는 최적화된 컴파일로 실행되지 않는다는 내용을 봤는데 사실인지?</li>
<li>고차함수는 lazy evaluation을 사용할 수 있는데, 사용 시 반복문 대비 얻는 이점이 체이닝을 해도 될 정도일까?</li>
</ol>
<p>이건 다음 시간에 알아보도록 하겠습니다...</p>
<blockquote>
<p>참고한 페이지들:
<a href="https://medium.com/@everton.iosdev/are-higher-order-functions-more-efficient-than-a-for-in-loop-in-swift-a-performance-experiment-dcad336ba715">Are higher-order functions more efficient than a for-in loop in Swift?</a>
<a href="https://stackoverflow.com/questions/33750636/swift-performance-map-and-reduce-vs-for-loops">Swift performance: map() and reduce() vs for loops</a>
<a href="https://www.skoumal.com/en/performance-of-built-in-higher-order-functions-map-filter-reduce-and-flatmap-vs-for-in-loop-in-swift/">Performance of built-in higher-order functions Map, Filter, Reduce, and flatMap vs. for-in loop in Swift</a>
<a href="https://www.linkedin.com/pulse/loops-maps-who-faster-time-space-complexity-we-coming-george-michelon">Loops and Maps : Who is faster? Time and Space complexity , we are coming!!!</a>
<a href="https://sujinnaljin.medium.com/swift-map-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0-4b057834d310">[Swift] map 파헤치기
</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[숫자야구] 이것저것 더 추가해보자]]></title>
            <link>https://velog.io/@dev-hamin-kim/%EC%88%AB%EC%9E%90%EC%95%BC%EA%B5%AC-%EC%9D%B4%EA%B2%83%EC%A0%80%EA%B2%83-%EB%8D%94-%EC%B6%94%EA%B0%80%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev-hamin-kim/%EC%88%AB%EC%9E%90%EC%95%BC%EA%B5%AC-%EC%9D%B4%EA%B2%83%EC%A0%80%EA%B2%83-%EB%8D%94-%EC%B6%94%EA%B0%80%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 07 Nov 2024 08:40:32 GMT</pubDate>
            <description><![CDATA[<p>저번 포스트에 이어 계속해서 만들어봅시다.</p>
<h1 id="lv-3">Lv. 3</h1>
<blockquote>
<p>정답이 되는 숫자를 0에서 9까지의 서로 다른 3자리의 숫자로 바꿔주세요</p>
</blockquote>
<p>라는데... 뭐지 나 이거 Lv. 1에서 해버렸는디...</p>
<p>대충 럭키비키자나를 외치며 다음 단계로 넘어갑시다.</p>
<h1 id="lv-4">Lv. 4</h1>
<p>눈치 챘을 수도 있지만, 저번 포스팅에 임시로 만들어 놓은 안내문구는,
말투를 살짝 문명 5 킹갓세종대왕님의 그것을 따라했으니,
지금 만드는 안내 문구도 비슷하게 만들어 보았다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/7cfefa95-49f8-4be6-ace6-0bbd0a29af9b/image.jpg" alt=""></p>
<blockquote>
<p>뭐 대충 이런 느낌으로다가.
<a href="https://www.youtube.com/watch?v=EWWigRGNdrU">사진 출처 - 유튜브 궁능TV</a></p>
</blockquote>
<p>쉬워보이니까 후딱 만들고 다음 단계로 넘어갑시다.</p>
<pre><code class="language-swift">import Foundation

print(&quot;숫자야구↗ 게임에↘ 당도한것을→ 환영하오↘ 낯↘선↗이여↘&quot;)

mainPage()

func mainPage() {
    print(&quot;원하는 번호를 입력하게나.&quot;)
    print(&quot;1. 놀이 시작하기 2. 놀이 기록 보기. 3. 종료하기&quot;)

    let input = readLine()

    switch input {
    case &quot;1&quot;: playGame()
    default: print(&quot;유효하지 않은 입력이네.&quot;)
    }
}

func playGame() {
    var game = NumberBaseball()
    // (중략)
    print(&quot;숫자를 입력하게나.&quot;)

    while !hadRightGuess {
        let input = Array(readLine()!)

        if input == game.answer {
            hadRightGuess = true
            print(&quot;축하하네, 정답이군.&quot;)
            mainPage()
        }

        if input.first == &quot;0&quot; {
            print(&quot;첫 번째 자리는 0이 들어가지 않는다네.&quot;)
        } else if input.count == 3
            &amp;&amp; input[0] != input[1]
            &amp;&amp; input[0] != input[2]
            &amp;&amp; input[1] != input[2] {
            print(&quot;\(game.umpireCall(for: input).strike) 스트라이크, \(game.umpireCall(for: input).ball) 볼.&quot;)
        } else {
            print(&quot;3자리의 서로 중복되지 않는 숫자만 입력이 가능하네.&quot;)
        }
    }
}
</code></pre>
<p>...</p>
<p>라고 생각하고 만들다가 버그를 만났습니다.</p>
<h2 id="버그버그">버그버그</h2>
<p>무슨 버그냐?</p>
<p>게임을 몇 번 진행하고 나서 메인페이지에서 게임을 종료하면 이전에 진행했던 게임 중 맞췄던 정보가 남아있는 버그입니다.</p>
<p>스샷을 보면 이해가 되실 겁니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/1994380b-0a30-4199-a9b6-da16a8a12720/image.png" alt=""></p>
<p>아니 저게 저기서 왜 나오죠?</p>
<p>왤까요?</p>
<p>컴퓨터는 굉장히 정직하고 착하고 진실된 종입니다.</p>
<p>제가 시킨 대로 충실히 임무를 수행했죠.</p>
<p>playGame() 안에 있는 while 문을 다시 한번 찬찬히 뜯어볼까요?</p>
<pre><code class="language-swift">while !hadRightGuess {
    let input = Array(readLine()!)

    if input == game.answer {
        hadRightGuess = true
        print(&quot;축하하네, 정답이군.&quot;)
        mainPage()
    }

    if input.first == &quot;0&quot; {
        print(&quot;첫 번째 자리는 0이 들어가지 않는다네.&quot;)
    } else if input.count == 3
                &amp;&amp; input[0] != input[1]
                &amp;&amp; input[0] != input[2]
                &amp;&amp; input[1] != input[2] {
        print(&quot;\(game.umpireCall(for: input).strike) 스트라이크, \(game.umpireCall(for: input).ball) 볼.&quot;)
    } else {
        print(&quot;3자리의 서로 중복되지 않는 숫자만 입력이 가능하네.&quot;)
    }
}</code></pre>
<p>왜 버그가 났는지 아시겠나요?</p>
<p>코드 일부만 떼어서 보겠습니다.</p>
<pre><code class="language-swift">if input == game.answer {
    hadRightGuess = true
    print(&quot;축하하네, 정답이군.&quot;)
    mainPage()
}</code></pre>
<p>답을 맞추면 실행되는 코드인데요,</p>
<p>맞추고 나면 mainPage() 함수를 호출해 다시 메인페이지가 불러지는 구조입니다.</p>
<p>문제는 여기서 발생합니다.</p>
<p>저 코드는 while문 안에 있는 코드죠?</p>
<p>즉:</p>
<blockquote>
</blockquote>
<ol>
<li>playGame()의 안에서 mainPage()를 호출하는 바람에 기존의 while문은 종료되지 않고 계속 남아있는 와중에,</li>
<li>mainPage()에서는 다시 새로운 playGame()을 호출하여 또 다시 게임을 진행합니다.
그리고 또 맞추면 1.로 돌아가 반복되다가,</li>
<li>mainPage()에서 새 게임을 시작하지 않고 종료하면</li>
<li>1.에서 멈춰있던 while문들이 한번에 풀려나게 되어, 그 아래의 if문으로 들어가서 프린트가 되어버린 것입니다.</li>
</ol>
<p>아니 그럼 저기 mainPage() 뒤에다가 break 문 넣어주면 장땡 아닌가요?</p>
<pre><code class="language-swift">while !hadRightGuess {
    let input = Array(readLine()!)

    if input == game.answer {
        hadRightGuess = true
        print(&quot;축하하네, 정답이군.&quot;)
        mainPage()
        break
    }
    // 후략...</code></pre>
<p>이렇게 말이죠?</p>
<p>물론 이렇게 하면 겉보기엔 괜찮아 보입니다.</p>
<p>근데 보이지 않는 문제가 있습니다...</p>
<p>이해를 돕기 위해 print문 하나를 추가해보겠습니다.</p>
<pre><code class="language-swift">while !hadRightGuess {
    let input = Array(readLine()!)

    if input == game.answer {
        hadRightGuess = true
        print(&quot;축하하네, 정답이군.&quot;)
        mainPage()
        print(game)
        break
    }
    // 후략...</code></pre>
<p>실행 결과입니다.
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/ba0d7b59-98b0-4e2f-bc7e-bb9d7fa642cb/image.png" alt=""></p>
<p>눈치... 채셨나요?</p>
<p>mainPage() 함수 뒤에 모든 것들이, mainPage() 함수가 종료될 때까지 실행되지 않고 있다는 것입니다...</p>
<p>심각성을 알아보기 위해 몇 번 더 돌려볼까요?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/3b328d91-39c8-4529-8034-bb92e7680724/image.png" alt=""></p>
<p>(등골이 오싹)</p>
<p>아주 끔찍한 일이 일어날 수 있는 경우입니다.</p>
<p>물론 지금은 텍스트 기반의 아주 간단한 게임이라,
어지간히 쌓여도 요즘같이 컴퓨팅 파워가 발달한 경우에는 티도 안나겠죠.</p>
<p>근데 만약 이게 복잡한 앱이었고, 필요없는 데이터가 저렇게 계속 쌓인다면?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/e4e05402-1c7e-4460-a9df-8bfc431e1a84/image.webp" alt=""></p>
<p>좋지 않은 일이 일어날 것입니다.</p>
<h3 id="그래서-어떻게-해결했냐고요">그래서 어떻게 해결했냐고요?</h3>
<p>의외로... 간단했습니다.
아까 break문을 넣었을 때의 코드를 갖고 올까요?</p>
<pre><code class="language-swift">while !hadRightGuess {
    let input = Array(readLine()!)

    if input == game.answer {
        hadRightGuess = true
        print(&quot;축하하네, 정답이군.&quot;)
        mainPage()
        break
    }
    // 후략...
}</code></pre>
<p>mainPage를... while 문 바깥으로 옮겨주면 해결되는 문제였습니다...</p>
<pre><code class="language-swift">while !hadRightGuess {
    let input = Array(readLine()!)

    if input == game.answer {
        hadRightGuess = true
        print(&quot;축하하네, 정답이군.&quot;)
        break
    }
    // 후략...
}
mainPage()</code></pre>
<p>이런 식으로 말이죠...?</p>
<p>이러면 while문이 mainPage()가 호출되기 전에 종료되니 해피엔딩 아닐까요?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/c77f23fc-5b09-46d9-88e9-728ce9f8819b/image.jpeg" alt=""></p>
<p>그런데 말입니다...</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/59c2dda7-a101-4e95-900e-a21bbe950423/image.png" alt=""></p>
<p>겉 보기엔 문제가 없어 보이죠...?</p>
<h3 id="응-아니야-해결-안-됐어-돌아가">응 아니야 해결 안 됐어 돌아가</h3>
<p>하지만 뭔가 찝찝한 마음에 스레드를 관찰해보기로 했습니다...</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/0e5f765f-835e-4118-b735-5c40abdbaacd/image.png" alt=""></p>
<p>사용이 끝난 NumberBaseball의 인스턴스가 종료되지 않고 남아있던 것이었습니다....!</p>
<p>꺄아아아아아아아아ㅏㅏㅏㅏㅏㅏㅏㅏㅏ악</p>
<h3 id="그래서-어떻게-해결했냐고요-2트">그래서 어떻게 해결했냐고요? (2트)</h3>
<p>playGame()에서 mainPage()를 찾아볼 수 없게 피의 숙청을 감행했습니다.</p>
<p>그리고 keepPlaying이라는 boolean 변수를 생성해 playGame()의 밖에서,
mainPage()가 계속 호출될 수 있게 해주었습니다.</p>
<p>그랬더니?</p>
<p><img src="https://velog.velcdn.com/images/dev-hamin-kim/post/3cbfda8a-921f-4c22-b1a6-1d34dcafe516/image.png" alt=""></p>
<p>깔-꼼해진 스레드를 볼 수 있습니다.
아이신나</p>
<h1 id="lv-5와-lv6">Lv. 5와 Lv.6...</h1>
<h2 id="나는-위-레벨에-대한-실로-놀랍게도-간단한-코딩법을-발견했다">나는 위 레벨에 대한 실로 놀랍게도 간단한 코딩법을 발견했다.</h2>
<p>하지만 여백이 부족하여 이를 적지 않겠다... 는 아니구요...</p>
<p>다 써놓고 수정하는 와중에 글이 한 번 날아가서 현타와서 안 쓸겁니다...</p>
<p>임시저장 눌렀다고........................
왜 다 날라갔냐고......................</p>
<p>그냥 제 깃허브에서 코드를 보시길 바랍니다.</p>
<blockquote>
<p><a href="https://github.com/dev-hamin-kim/NumberBaseball">https://github.com/dev-hamin-kim/NumberBaseball</a></p>
</blockquote>
<p>그럼 
<img src="https://velog.velcdn.com/images/dev-hamin-kim/post/83fe7f81-3126-4c20-806f-d0b3e5e6b789/image.webp" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>