<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-yong.log</title>
        <link>https://velog.io/</link>
        <description>🧑🏻‍💻 iOS Developer @TOSS</description>
        <lastBuildDate>Sun, 09 Jan 2022 06:09:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-yong.log</title>
            <url>https://images.velog.io/images/dev-yong/profile/67d05967-87db-47d5-a07b-4e0fed890878/25545112.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-yong.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-yong" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[RxSwift] 8.`MainScheduler.instance` vs `MainScheduler.asyncInstance`]]></title>
            <link>https://velog.io/@dev-yong/RxSwift-8.MainScheduler.instance-vs-MainScheduler.asyncInstance</link>
            <guid>https://velog.io/@dev-yong/RxSwift-8.MainScheduler.instance-vs-MainScheduler.asyncInstance</guid>
            <pubDate>Sun, 09 Jan 2022 06:09:16 GMT</pubDate>
            <description><![CDATA[<h1 id="mainscheudler">MainScheudler</h1>
<ul>
<li><code>DispatchQueue.main</code> 에서 수행되어야 하는 작업의 추상화이다.</li>
<li><code>schedule</code> methods는 <code>DispatchQueue.main</code> 에서 호출되어지며, 스케쥴링 없이 즉각적으로 수행한다.</li>
<li>Main Sehcudler는 <code>SerialDispatchQueueScheduler</code>이다.</li>
<li>UI 작업의 수행이 일반적으로 사용된다.</li>
<li><code>observeOn</code> 연산자에 최적화되어 있다. <ul>
<li><code>subscribeOn</code> 을 사용하여 메인 스레드에서 observable을 구독하도록 하려면 <code>ConcurrentMainScheduler</code>가 해당 목적에 더욱 최적화되어 있으므로 <code>ConcurrentMainScheduler</code>를 사용하도록 한다.</li>
</ul>
</li>
</ul>
<h2 id="asyncinstance"><code>asyncInstance</code></h2>
<ul>
<li>항상 비동기식으로 작업을 예약하고 기본 대기열에서 예약된 호출에 대해 최적화를 수행하지 않는다.</li>
<li><code>asyncInstance</code>는 Event의 async 전달을 보장하지만, <code>instance</code>는 이미 MainThread에 있을 경우 event를 sync하게 전달한다.</li>
<li>이미 Main thread에 있을 때 async 전달을 강제해야 하는 이유는 아래와 같다.<ul>
<li>동일한 파이프라인에서 하나의 이벤트가 새로운 이벤트의 전달을 트리거하는 재귀 반응 파이프라인이 존재한다.</li>
<li>만일 이러한 이벤트가 sync하게 발생하면 Rx contract가 깨지고 RxSwift는 첫 번째 이벤트가 완료되기 이전에 두번째 이벤트를 전달하려고 시도햇다는 경고를 내보내게 된다.</li>
<li>이 경우, <code>MainScheduler.asyncInstance</code>에서 주기를 깨는 것을 관찰할 수 있다.</li>
</ul>
</li>
</ul>
<p><a href="https://stackoverflow.com/questions/58332584/rxswift-mainscheduler-instance-vs-mainscheduler-asyncinstance">https://stackoverflow.com/questions/58332584/rxswift-mainscheduler-instance-vs-mainscheduler-asyncinstance</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxSwift] (7) `subscribeOn` vs `observeOn`]]></title>
            <link>https://velog.io/@dev-yong/RxSwift-observeOn-vs-subscribeOn</link>
            <guid>https://velog.io/@dev-yong/RxSwift-observeOn-vs-subscribeOn</guid>
            <pubDate>Sun, 09 Jan 2022 04:10:30 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-swift">Observable.create { observer in
    // &quot;Subscription Code&quot;
    // - `subscrbie()`의 호출에 의하여 호출된다.
    // - subscription을 생성하고 element를 생산한다.
}
.map { ... }
.subscribe(onNext: {
    // &quot;Observation Code&quot;
    // - 방출되어진 element들을 observe한다.
})</code></pre>
<h2 id="subscribe-on">Subscribe On</h2>
<ul>
<li>Subscription Code 가 수행되는 scheduler를 변경할 수 있도록 한다. (<code>subscribeOn()</code>)</li>
<li>Subscription Code는 <code>subscribe()</code> 가 실행되는 thread와 동일한 thread에서 수행된다.</li>
</ul>
<pre><code class="language-swift">Observable.create { observer in
        // Subscription Code
}
.map { ... }
.subscribe(onNext: {
        ...
})</code></pre>
<h2 id="observe-on">Observe On</h2>
<ul>
<li>Opservation code가 수행되는 scheduler를 변경할 수 있도록 한다. (<code>observeOn()</code>)</li>
</ul>
<pre><code class="language-swift">Observable.create { observer in
    ...
}
.map { ... }
.subscribe(onNext: {
        // Observation Code
})</code></pre>
<p><a href="http://rx-marin.com/post/observeon-vs-subscribeon/">http://rx-marin.com/post/observeon-vs-subscribeon/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxSwift] 6.Operator-Combining]]></title>
            <link>https://velog.io/@dev-yong/RxSwift-6.Operator-Combining</link>
            <guid>https://velog.io/@dev-yong/RxSwift-6.Operator-Combining</guid>
            <pubDate>Sun, 09 Jan 2022 04:05:50 GMT</pubDate>
            <description><![CDATA[<h2 id="startwith"><code>startWith</code></h2>
<ul>
<li>초기 값을 sequence의 앞에 붙인다.</li>
</ul>
<pre><code class="language-swift">Observable.from(1...5)
.startWith(0)
.debug()
.subscribe()</code></pre>
<h2 id="concat"><code>concat</code></h2>
<ul>
<li>두 개 이상의 sequence를 직렬화 한다.<ul>
<li>하나의 <code>Observable</code>이 완료될 때까지 event를 전달하고 완료 되면 그 다음 <code>Observable</code>의 이벤트를 연이어 전달한다.</li>
<li>다른 <code>Observable</code>이 완료되면 직렬화된 <code>Observable</code>은 <code>.completed</code>된다.</li>
</ul>
</li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()
let subject1 = PublishSubject&lt;Int&gt;()
let subject2 = ReplaySubject&lt;Int&gt;.create(bufferSize: 1)

_ = Observable.concat(subject1, subject2)
    .debug()
    .subscribe()
    .disposed(by: disposeBag)

subject1.on(.next(1))
subject1.on(.next(2))
subject1.on(.next(3))
subject2.on(.next(10))
subject1.on(.next(4))
subject2.on(.next(20))
subject1.on(.completed)</code></pre>
<h2 id="concatmap"><code>concatMap</code></h2>
<ul>
<li><code>Observable</code>에서 방출한 element를 새로운 <code>Observable</code>를 만들며, 만들어진 <code>Observable</code>의 element를 방출한다.</li>
<li><code>concat</code> +  <code>flatMap</code></li>
</ul>
<pre><code class="language-swift">let sequences = [
    &quot;key🟢&quot;: Observable.of(1, 2, 3).debug(&quot;🟢&quot;),
    &quot;key🔴&quot;: Observable.of(10, 20, 30).debug(&quot;🔴&quot;)
]

_ = Observable.of(&quot;key🟢&quot;, &quot;key🔴&quot;)
    .concatMap { sequences[$0] ?? .empty() }
    .debug(&quot;concatMap&quot;)
    .subscribe()</code></pre>
<h1 id="merge">Merge</h1>
<h2 id="merge-1"><code>merge</code></h2>
<ul>
<li>같은 타입의 Event를 방출하는 <code>Observable</code> 을 합성한다.</li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()
let subject1 = PublishSubject&lt;String&gt;()
let subject2 = PublishSubject&lt;String&gt;()

Observable.merge(
    subject1.debug(&quot;🟢&quot;),
    subject2.debug(&quot;🔴&quot;)
)
    .debug(&quot;merge&quot;)
    .subscribe()
    .disposed(by: disposeBag)

subject1.on(.next(&quot;1&quot;))
subject2.on(.next(&quot;A&quot;))
subject1.on(.next(&quot;2&quot;))
subject1.on(.next(&quot;3&quot;))
subject2.on(.next(&quot;B&quot;))
subject1.on(.completed)
subject2.on(.next(&quot;After completed&quot;))</code></pre>
<h1 id="combine">Combine</h1>
<h2 id="combinelatest"><code>combineLatest</code></h2>
<ul>
<li>Inner sequence의 최종값을 받아 방출하는 sequence를 만든다.</li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()
let subject1 = PublishSubject&lt;String&gt;()
let subject2 = PublishSubject&lt;String&gt;()

Observable.combineLatest(
    subject1.debug(&quot;🟢&quot;),
    subject2.debug(&quot;🔴&quot;)
) { (element1, element2) -&gt; String in
    &quot;🟢\(element1)  🔴\(element2)&quot;
}
.debug(&quot;combineLatest&quot;)
.subscribe()
.disposed(by: disposeBag)

subject1.on(.next(&quot;1&quot;))
subject2.on(.next(&quot;A&quot;))
subject1.on(.next(&quot;2&quot;))
subject1.on(.next(&quot;3&quot;))
subject2.on(.next(&quot;B&quot;))
subject1.on(.completed)
subject2.on(.next(&quot;After completed&quot;))</code></pre>
<h2 id="zip"><code>zip</code></h2>
<pre><code class="language-swift">let disposeBag = DisposeBag()
let subject1 = PublishSubject&lt;String&gt;()
let subject2 = PublishSubject&lt;String&gt;()

Observable.zip(
    subject1.debug(&quot;🟢&quot;),
    subject2.debug(&quot;🔴&quot;)
) { (element1, element2) -&gt; String in
    &quot;🟢\(element1)  🔴\(element2)&quot;
}
.debug(&quot;combineLatest&quot;)
.subscribe()
.disposed(by: disposeBag)

subject1.on(.next(&quot;1&quot;))
subject2.on(.next(&quot;A&quot;))
subject1.on(.next(&quot;2&quot;))
subject1.on(.next(&quot;3&quot;))
subject2.on(.next(&quot;B&quot;))
subject1.on(.completed)
subject2.on(.next(&quot;After completed&quot;))</code></pre>
<h1 id="trigger">Trigger</h1>
<h2 id="withlatestfrom"><code>withLatestFrom</code></h2>
<ul>
<li>input <code>Observable</code>의 event가 방출된 이후에 기존 <code>Observable</code>의 새로운 element가 방출될 경우, 기존 <code>Observable</code> element와 input <code>Observable</code>의 element를 결합하여 방출한다.</li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()
let subject1 = PublishSubject&lt;String&gt;()
let subject2 = PublishSubject&lt;String&gt;()

subject1.withLatestFrom(subject2) {
    $0 + &quot; &quot; + $1
}
.debug(&quot;withLatestFrom&quot;)
.subscribe()
.disposed(by: disposeBag)

subject1.on(.next(&quot;1&quot;))
subject1.on(.next(&quot;2&quot;))
subject2.on(.next(&quot;A&quot;))
subject1.on(.next(&quot;3&quot;))
subject1.on(.next(&quot;4&quot;))
subject2.on(.next(&quot;B&quot;))
subject2.on(.next(&quot;C&quot;))
subject1.on(.next(&quot;5&quot;))
subject1.on(.completed)
subject2.on(.next(&quot;After completed&quot;))</code></pre>
<h2 id="amb"><code>amb</code></h2>
<ul>
<li>가장 먼저 Event가 발생한 sequence를 따르도록 한다.</li>
</ul>
<pre><code class="language-swift">let scheduler = MainScheduler.instance
let sequence1 = Observable&lt;Int&gt;.interval(
    .milliseconds(10),
    scheduler: scheduler
).take(5)
.map { &quot;🟢 \($0)&quot; }
let sequence2 = Observable&lt;Int&gt;.interval(
    .milliseconds(5),
    scheduler: scheduler
).take(5)
.map { &quot;🔴 \($0)&quot; }
let sequence3 = Observable&lt;Int&gt;.interval(
    .milliseconds(15),
    scheduler: scheduler
).take(5)
.map { &quot;🟡 \($0)&quot; }

Observable.amb([sequence1, sequence2, sequence3])
    .debug(&quot;amb&quot;)
    .subscribe()</code></pre>
<h2 id="switchlatest"><code>switchLatest</code></h2>
<ul>
<li>가장 최신의 Observable이 방출하는 Event를 구독자에게 전달한다.</li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()
struct Student {
    let name: String
    let scoreSubject: BehaviorSubject&lt;Int&gt;
}

let student🟢 = Student(
    name: &quot;🟢&quot;,
    scoreSubject: BehaviorSubject&lt;Int&gt;(value: 1)
)
let student🔴 = Student(
    name: &quot;🔴&quot;,
    scoreSubject: BehaviorSubject&lt;Int&gt;(value: 10)
)
let studentSubject = PublishSubject&lt;Observable&lt;Int&gt;&gt;()

studentSubject.switchLatest()
    .debug(&quot;switchLatest&quot;)
    .subscribe()
    .disposed(by: disposeBag)

studentSubject.on(.next(student🟢.scoreSubject))
student🟢.scoreSubject.on(.next(2))
studentSubject.on(.next(student🔴.scoreSubject))
student🟢.scoreSubject.on(.next(3))
student🔴.scoreSubject.on(.next(20))
student🟢.scoreSubject.on(.next(4))
student🔴.scoreSubject.on(.next(30))</code></pre>
<h1 id="element">Element</h1>
<h2 id="reduce"><code>reduce</code></h2>
<ul>
<li><code>Observable</code>이 완료되었을 경우, 가공되어진 값을 방출한다.</li>
</ul>
<pre><code class="language-swift">Observable.from(1...10)
.reduce(0, accumulator: +)
.debug(&quot;reduce&quot;)
.subscribe()</code></pre>
<h2 id="scan"><code>scan</code></h2>
<ul>
<li>가공되어진 값을 지속적으로 방출한다.</li>
</ul>
<pre><code class="language-swift">Observable.from(1...10)
.scan(0, accumulator: +)
.debug(&quot;scan&quot;)
.subscribe()</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxSwift] 5.Operator-Transforming]]></title>
            <link>https://velog.io/@dev-yong/RxSwift-5.Operator-Transforming</link>
            <guid>https://velog.io/@dev-yong/RxSwift-5.Operator-Transforming</guid>
            <pubDate>Sun, 09 Jan 2022 03:12:43 GMT</pubDate>
            <description><![CDATA[<h1 id="transforming-elements">Transforming Elements</h1>
<h2 id="toarray"><code>toArray</code></h2>
<ul>
<li>Sequence의 element를 array의 element로 변환한다.</li>
</ul>
<pre><code class="language-swift">Observable.of(1, 2, 3, 4, 5)
.toArray()
.subscribe {
    print(&quot;🟢&quot;, $0)
}</code></pre>
<h2 id="map"><code>map</code></h2>
<pre><code class="language-swift">Observable.from(1...5)
.map { $0 * 10 }
.subscribe {
    print(&quot;🟢&quot;, $0)
}</code></pre>
<h2 id="enumerated"><code>enumerated</code></h2>
<ul>
<li>방출된 element의 index와 value를 포함하는 tuple을 방출하도록 한다.</li>
</ul>
<pre><code class="language-swift">Observable.from(1...5)
.enumerated()
.subscribe {
    print(&quot;🟢&quot;, $0)
}</code></pre>
<h1 id="transforming-observables">Transforming Observables</h1>
<ul>
<li>🌟 Upstream에서 <code>.error</code>가 발생한다면, 생성된 <code>Observable</code>은 dispose 된다.</li>
</ul>
<h2 id="flatmap"><code>flatMap</code></h2>
<ul>
<li><code>Observable</code>에서 방출한 element를 새로운 <code>Observable</code>를 만들며, 만들어진 <code>Observable</code>의 element를 방출한다.
<img src="https://miro.medium.com/max/2004/0*loi3aa8FuMYFc55B.png" alt="flatMap"></li>
</ul>
<pre><code class="language-swift">struct Student {
    let name: String
    let scoreSubject: BehaviorSubject&lt;Int&gt;
}</code></pre>
<pre><code class="language-swift">let student🟢 = Student(
    name: &quot;🟢&quot;,
    scoreSubject: BehaviorSubject&lt;Int&gt;(value: 80)
)
let student🔴 = Student(
    name: &quot;🔴&quot;,
    scoreSubject: BehaviorSubject&lt;Int&gt;(value: 90)
)
let studentSubject = PublishSubject&lt;Student&gt;()

studentSubject.flatMap { $0.scoreSubject }
    .debug()
    .subscribe()
    .disposed(by: disposeBag)

studentSubject.onNext(student🟢) // 80

student🟢.scoreSubject.onNext(85) // 85

studentSubject.onNext(student🔴) // 90

student🟢.scoreSubject.onNext(95) // 95

student🔴.scoreSubject.onNext(100)  // 100</code></pre>
<h2 id="flatmapfirst"><code>flatMapFirst</code></h2>
<ul>
<li><code>Observable</code>에서 방출한 element를 새로운 <code>Observable</code>를 만들며,  만들어진 <code>Observable</code>의 element를 방출한다.</li>
<li>이전 observable이 완료되어야지만 다음 observable의 element를 받을 수 있다.
<img src="https://miro.medium.com/max/1400/0*siKC8C40lRTO6K3T.png" alt="flatMapLatest"></li>
</ul>
<pre><code class="language-swift">let student🟢 = Student(
    name: &quot;🟢&quot;,
    scoreSubject: BehaviorSubject&lt;Int&gt;(value: 80)
)
let student🔴 = Student(
    name: &quot;🔴&quot;,
    scoreSubject: BehaviorSubject&lt;Int&gt;(value: 90)
)
let studentSubject = PublishSubject&lt;Student&gt;()

studentSubject.flatMapFirst { $0.scoreSubject.debug($0.name) }
.debug(&quot;flatMapFirst&quot;)
.subscribe()
.disposed(by: disposeBag)

studentSubject.onNext(student🟢) // 80

student🟢.scoreSubject.onNext(85) // 85

studentSubject.onNext(student🔴) // 🌟 이전 observable(`student🟢`)이 완료되어야지만 다음 observable(student🔴)의 것을 방출한다.
student🔴.scoreSubject.onNext(100)

student🟢.scoreSubject.onNext(95) // 95

student🟢.scoreSubject.onCompleted()
studentSubject.onNext(student🔴) // 100

student🔴.scoreSubject.onNext(70)  // 70</code></pre>
<h2 id="flatmaplatest"><code>flatMapLatest</code></h2>
<ul>
<li><code>Observable</code>에서 방출한 element를 새로운 <code>Observable</code>를 만들며,  만들어진 <code>Observable</code>의 element를 방출한다.</li>
<li>새로이 발행된 element가 전달되면, 이전 <code>Observable</code>은 dispose하고 새로운 <code>Observable</code>을 만들고 element를 방출한다.<ul>
<li>이전 observable이 dipose된다. 다음 observable의 값만 방출한다.
<img src="https://miro.medium.com/max/1400/0*siKC8C40lRTO6K3T.png" alt="flatMapLatest"></li>
</ul>
</li>
</ul>
<pre><code class="language-swift">let student🟢 = Student(
    name: &quot;🟢&quot;,
    scoreSubject: BehaviorSubject&lt;Int&gt;(value: 80)
)
let student🔴 = Student(
    name: &quot;🔴&quot;,
    scoreSubject: BehaviorSubject&lt;Int&gt;(value: 90)
)
let studentSubject = PublishSubject&lt;Student&gt;()

studentSubject.flatMapLatest { $0.scoreSubject.debug($0.name) }
    .debug(&quot;flatMapLatest&quot;)
    .subscribe()
    .disposed(by: disposeBag)

studentSubject.onNext(student🟢) // 80

student🟢.scoreSubject.onNext(85) // 85

studentSubject.onNext(student🔴) // 90, 🌟 이전 observable(`student🟢`)이 dipose된다.

student🟢.scoreSubject.onNext(95) // 마지막 observable인 `student🔴`의 값만 방출된다.

student🔴.scoreSubject.onNext(100)  // 100</code></pre>
<h1 id="observing-events">Observing Events</h1>
<h2 id="materialize"><code>materialize</code></h2>
<ul>
<li><code>Observable&lt;Int&gt;</code> → <code>Observable&lt;Event&lt;Value&gt;&gt;</code></li>
</ul>
<h2 id="dematerialize"><code>dematerialize</code></h2>
<ul>
<li><code>Observable&lt;Event&lt;Value&gt;&gt;</code> → <code>Observable&lt;Int&gt;</code></li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()
struct Student {
    let name: String
    let scoreSubject: BehaviorSubject&lt;Int&gt;
}

let student🟢 = Student(
    name: &quot;🟢&quot;,
    scoreSubject: BehaviorSubject&lt;Int&gt;(value: 1)
)
let student🔴 = Student(
    name: &quot;🔴&quot;,
    scoreSubject: BehaviorSubject&lt;Int&gt;(value: 10)
)
let studentSubject = PublishSubject&lt;Student&gt;()

let materialized = studentSubject
    .flatMapLatest {
        $0.scoreSubject.materialize().debug($0.name)
    }
materialized
    .debug(&quot;materialized&quot;)
    .subscribe()
    .disposed(by: disposeBag)

materialized
    .dematerialize()
    .debug(&quot;dematerialized&quot;)
    .subscribe()
    .disposed(by: disposeBag)

studentSubject.on(.next(student🟢))
student🟢.scoreSubject.on(.next(2))
student🟢.scoreSubject.on(.error(CustomError.error))
studentSubject.on(.next(student🔴))
student🟢.scoreSubject.on(.next(3))
student🔴.scoreSubject.on(.next(20))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxSwift] 4.Operator-Filtering]]></title>
            <link>https://velog.io/@dev-yong/RxSwift-4.Filtering-Operator</link>
            <guid>https://velog.io/@dev-yong/RxSwift-4.Filtering-Operator</guid>
            <pubDate>Sun, 09 Jan 2022 03:10:37 GMT</pubDate>
            <description><![CDATA[<h1 id="ignoring">Ignoring</h1>
<h2 id="ignoreelements"><code>ignoreElements</code></h2>
<ul>
<li>Event 중, <strong><code>.next(value)</code>인 element들을 무시</strong>하고, <code>.error</code> 혹은 <code>.completed</code>만 허용한다.</li>
</ul>
<pre><code>let disposeBag = DisposeBag()
let subject = PublishSubject&lt;String&gt;()
subject
    .ignoreElements()
    .subscribe {
        print(&quot;🟢&quot;, $0)
    }.disposed(by: disposeBag)

subject.on(.next(&quot;1&quot;))
subject.on(.next(&quot;2&quot;))
subject.on(.next(&quot;3&quot;))
subject.on(.error(CustomError.error))
subject.on(.next(&quot;After error&quot;))</code></pre><h2 id="elementat"><code>elementAt</code></h2>
<ul>
<li>input번째 element들만 허용하도록 한다.</li>
</ul>
<pre><code class="language-swift">Observable.from(1...5)
.elementAt(2)
.subscribe {
    print(&quot;🟢&quot;, $0)
}</code></pre>
<h2 id="filter"><code>filter</code></h2>
<ul>
<li>input의 요구사항에 대한 element들만 허용하도록 한다.</li>
</ul>
<pre><code class="language-swift">Observable.from(1...5)
.filter { $0 % 2 == 0 }
.subscribe {
    print(&quot;🟢&quot;, $0)
}</code></pre>
<h1 id="skipping">Skipping</h1>
<h2 id="skip"><code>skip</code></h2>
<ul>
<li>0..&lt;n 범위의 element를 skip하도록 한다.</li>
</ul>
<pre><code class="language-swift">Observable.from(1...5)
.skip(2)
.subscribe {
    print(&quot;🟢&quot;, $0)
}</code></pre>
<h2 id="skipwhile"><code>skipWhile</code></h2>
<ul>
<li>input의 요구사항이 <code>true</code>인 동안에는 element를 skip한다.</li>
<li><code>false</code>가 된 이후로는 element를 skip하지 않는다.</li>
</ul>
<pre><code class="language-swift">Observable.from(1...5)
.skipWhile { $0 &lt; 3 }
.subscribe {
    print(&quot;🟢&quot;, $0)
}</code></pre>
<h2 id="skipuntil"><code>skipUntil</code></h2>
<ul>
<li>다른 observable이 event를 방출하기 전까지, element를 skip한다.</li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()

let subject = PublishSubject&lt;String&gt;()
let trigger = PublishSubject&lt;Int&gt;()

subject
    .skipUntil(trigger)
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

subject.on(.next(&quot;A&quot;))
subject.on(.next(&quot;B&quot;))

trigger.on(.next(0))

subject.on(.next(&quot;C&quot;))
trigger.on(.next(1))</code></pre>
<h1 id="taking">Taking</h1>
<h2 id="take"><code>take</code></h2>
<ul>
<li>0..&lt;n까지의 element를 취하도록 한다.</li>
</ul>
<pre><code class="language-swift">Observable.from(1...5)
.take(2)
.subscribe {
    print(&quot;🟢&quot;, $0)
}</code></pre>
<h2 id="takewhile"><code>takeWhile</code></h2>
<ul>
<li>input의 요구사항이 <code>true</code>인 동안에는 element를 취하도록 한다.</li>
<li><code>false</code>가 된 이후로는 element를 취하지 않는다.</li>
</ul>
<pre><code class="language-swift">Observable.from(1...5)
.takeWhile { $0 &lt; 3 }
.subscribe {
    print(&quot;🟢&quot;, $0)
}</code></pre>
<h2 id="takeuntil"><code>takeUntil</code></h2>
<ul>
<li>다른 observable이 event를 방출하기 전까지, element를 취한다.</li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()

let subject = PublishSubject&lt;String&gt;()
let trigger = PublishSubject&lt;Int&gt;()

subject
    .takeUntil(trigger)
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

subject.on(.next(&quot;A&quot;))
subject.on(.next(&quot;B&quot;))

trigger.on(.next(0))

subject.on(.next(&quot;C&quot;))
trigger.on(.next(1))</code></pre>
<h1 id="distinct">Distinct</h1>
<h2 id="distinctuntilchanged"><code>distinctUntilChanged</code></h2>
<ul>
<li>input의 요구사항에 따라, 중복해서 이어지는 element를 방출하지 않는다.</li>
</ul>
<pre><code class="language-swift">Observable.of(1, 1, 2, 3, 4, 4, 5)
.enumerated()
.distinctUntilChanged { $0.element == $1.element }
.subscribe {
    print(&quot;🟢&quot;, $0)
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxSwift] 3.Traits]]></title>
            <link>https://velog.io/@dev-yong/RxSwift-3.Traits</link>
            <guid>https://velog.io/@dev-yong/RxSwift-3.Traits</guid>
            <pubDate>Sun, 09 Jan 2022 03:04:11 GMT</pubDate>
            <description><![CDATA[<h1 id="rxswift-traits">RxSwift Traits</h1>
<ul>
<li>Event를 방출하면 sequence가 종료된다.<ul>
<li>element가 방출되면, <code>.completed</code>가 자동으로 방출된다.</li>
<li>즉, dispose 된다.</li>
</ul>
</li>
</ul>
<h2 id="single">Single</h2>
<ul>
<li><code>.success(value)</code> 혹은 <code>.error</code> 이벤트만 방출할 수 있다.</li>
</ul>
<pre><code class="language-swift">Single&lt;Void&gt;.create { single in

    single(.success(Void()))
    single(.error(CustomError.error))
//      single(.completed) ❌
    return Disposables.create()
}
.debug()
.subscribe()</code></pre>
<h2 id="completable">Completable</h2>
<ul>
<li><code>.completed</code> 혹은 <code>.error</code>만 방출하며, 값을 방출할 수 없다.</li>
</ul>
<pre><code class="language-swift">Completable.create { completable in

//      completable(.success) ❌
    completable(.error(CustomError.error))
    completable(.completed)

    return Disposables.create()
}
.debug()
.subscribe()
}</code></pre>
<h2 id="maybe">Maybe</h2>
<ul>
<li><code>success(value)</code> 혹은 <code>.completed</code> 혹은 <code>.error</code> 를 방출한다.</li>
</ul>
<pre><code class="language-swift">Maybe&lt;Void&gt;.create { maybe in

    maybe(.success(Void()))
    maybe(.error(CustomError.error))
    maybe(.completed)

    return Disposables.create()
}
.debug()
.subscribe()</code></pre>
<h1 id="rxcocoa-traits">RxCocoa Traits</h1>
<h2 id="driver">Driver</h2>
<ul>
<li><code>.error</code>를 방출하지 않는다.</li>
<li>Observe가 Main scheduler에서 일어난다.</li>
<li>Side Effect를 공유한다. (<code>.share(replay: 1, scope: .whileConnected)</code>)</li>
</ul>
<pre><code class="language-swift">typealias Driver&lt;Element&gt; = SharedSequence&lt;DriverSharingStrategy, Element&gt;</code></pre>
<ul>
<li>의도된 UseCase는 어플리케이션을 Drive하는 시퀀스를 모델링한다.<ul>
<li>CoreData 모델로부터 UI를 drive한다.</li>
<li>다른 UI 요소(binding)의 값을 사용하여 UI를 drive한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-swift">[AS-IS]
fetchResult(query)
   .observeOn(MainScheduler.instance)
   .catchErrorJustReturn([])
   .share(replay: 1)
[TO-BE]
fetchResult(query)
   .asDriver(onErrorJustReturn: [])</code></pre>
<pre><code class="language-swift">func fetchAutoCompleteItems(_ query: String?) -&gt; Observable&lt;[String]&gt; {
    return .empty()
}

let resultTextField = UITextField()
let queryTextField = UITextField()

//  let results = queryTextField.rx.text
//      .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
//      .flatMapLatest { query in
//          fetchAutoCompleteItems(query)
//      }
//
//  let results = queryTextField.rx.text
//      .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
//      .flatMapLatest { query in
//          fetchAutoCompleteItems(query)
//              .observeOn(MainScheduler.instance)
//              .catchErrorJustReturn([])
//      }
//      .share(replay: 1)

let results = queryTextField.rx.text
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
    }
    .asDriver(onErrorJustReturn: [])

results.map { &quot;\($0.count)&quot; }
    .drive(resultTextField.rx.text)
    .disposed(by: disposeBag)</code></pre>
<h2 id="controlproperty--controlevent">ControlProperty / ControlEvent</h2>
<h3 id="controlproperty">ControlProperty</h3>
<ul>
<li>UI Element의 property를 나타내기 위한 Trait이다.</li>
<li>실패하지 않는다.</li>
<li><code>share(replay: 1)</code> 처럼 행위를 한다.<ul>
<li>Stateful하다.</li>
<li>Subscription(즉, subscribe 호출) 시 마지막 요소가 생성된 경우 즉시 재생된다.</li>
<li>Subscriber에게 구독 즉시 초치값을 전송한다.</li>
</ul>
</li>
<li>base가 deallocated될 때 complete된다.</li>
<li>Main scheduler에서 subscribe되는 것이 보장된다. (<code>subscribeOn(ConcurrentMainScheduler.instance)</code>)<ul>
<li><code>MainScheduler.instance</code> 에서 event가 전달된다.</li>
</ul>
</li>
</ul>
<pre><code class="language-swift">extension Reactive where Base: UISearchBar {

    var value: ControlProperty&lt;String?&gt; {
        let source = Observable&lt;String?&gt;.deferred { [weak base] () -&gt; Observable&lt;String?&gt; in
            guard let base = base else { return .empty() }
            let text = base.text
            return base.rx.delegate
                .methodInvoked(#selector(UISearchBarDelegate.searchBar(_:textDidChange:)))
                .map { return $0[1] as? String }
                .startWith(text)
        }
        let bindingObserver = Binder(self.base) { (searchBar, text: String?) in
            searchBar.text = text
        }
        return ControlProperty(values: source, valueSink: bindingObserver)
    }

}

extension Reactive where Base: UISegmentedControl {

    var selectedSegmentIndex: ControlProperty&lt;Int&gt; {
        base.rx.controlProperty(
            editingEvents: [.allEditingEvents, .valueChanged],
            getter: {
                $0.selectedSegmentIndex
            },
            setter: {
                $0.selectedSegmentIndex = $1
            }
        )
    }

}</code></pre>
<h3 id="controlevent">ControlEvent</h3>
<ul>
<li>절대 실패하지 않는다.</li>
<li>Subscriber에게 초긱밧을 전송하지 않는다.</li>
<li>base가 deallocated될 때 complete된다.</li>
<li>Main scheduler에서 subscribe되는 것이 보장된다. (<code>subscribeOn(ConcurrentMainScheduler.instance)</code>)<ul>
<li><code>MainScheduler.instance</code> 에서 event가 전달된다.</li>
</ul>
</li>
</ul>
<pre><code class="language-swift">extension Reactive where Base: UICollectionView {

    var itemSelected: ControlEvent&lt;IndexPath&gt; {
        let source = delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didSelectItemAt:)))
            .map { a in
                return a[1] as! IndexPath
            }

        return ControlEvent(events: source)
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxSwift] 2. Subject]]></title>
            <link>https://velog.io/@dev-yong/RxSwift-2.-Subject</link>
            <guid>https://velog.io/@dev-yong/RxSwift-2.-Subject</guid>
            <pubDate>Sun, 09 Jan 2022 02:55:35 GMT</pubDate>
            <description><![CDATA[<h1 id="subject">Subject</h1>
<ul>
<li><code>Observable</code>이자 <code>Observer</code>이다.</li>
<li><code>.next(value)</code>를 받고, 수신할 때마다 <code>Subscriber</code>에게 방출한다.</li>
<li>Sequence가 종료된 Subject를 subscription할 경우, 마지막 Event(<code>.error</code> 혹은 <code>.completed</code>) 를 방출한다.</li>
<li>Sequence가 종료된 Subject에 대하여 <code>dispose</code>를 호출하면 오류가 출력된다.</li>
</ul>
<h2 id="relay와의-차이점">Relay와의 차이점</h2>
<ul>
<li>Relay는 <code>complete</code>나 <code>error</code>를 받지 않는다.</li>
<li>Relay는 observable이 종료되어도, relay 내의 subject 종료되지 않는다.<ul>
<li>따라서, <strong>observable의 종료가 절대적으로 보장되지 않는다면, relay.bind 하는 것은 바람직하지 않다.</strong></li>
</ul>
</li>
</ul>
<h2 id="publishsubject"><code>PublishSubject</code></h2>
<ul>
<li>초기값이 존재하지 않으며, <strong>새로운 값만을 <code>Subscriber</code>에게 방출</strong>한다.</li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()
let subject = PublishSubject&lt;String&gt;()
subject.on(.next(&quot;Before subscribed&quot;))

// Subscription 1
subject.subscribe {
    print(&quot;🟢&quot;, $0)
}

subject.on(.next(&quot;1&quot;))
subject.on(.next(&quot;2&quot;))

// Subscription 2
subject.subscribe {
    print(&quot;🔴&quot;, $0)
}

subject.on(.next(&quot;3&quot;))
subject.on(.error(CustomError.error))
subject.on(.next(&quot;After error&quot;))

// Subscription 3
subject.subscribe {
    print(&quot;🟡&quot;, $0)
}.disposed(by: disposeBag)

subject.on(.next(&quot;4&quot;))</code></pre>
<h2 id="behaviorsubject"><code>BehaviorSubject</code></h2>
<ul>
<li>하나의 초기값을 갖으며, 새로운 <code>Subscriber</code>에게 <strong>초기값 또는 최신 값을 방출</strong>한다.</li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()
let subject = BehaviorSubject&lt;String&gt;(value: &quot;Initial value&quot;)
subject.on(.next(&quot;Before subscribed&quot;))

// Subscription 1
subject.subscribe {
    print(&quot;🟢&quot;, $0)
}

subject.on(.next(&quot;1&quot;))
subject.on(.next(&quot;2&quot;))

// Subscription 2
subject.subscribe {
    print(&quot;🔴&quot;, $0)
}

subject.on(.next(&quot;3&quot;))
subject.on(.error(CustomError.error))
subject.on(.next(&quot;After error&quot;))

// Subscription 3
subject.subscribe {
    print(&quot;🟡&quot;, $0)
}.disposed(by: disposeBag)

subject.on(.next(&quot;4&quot;))</code></pre>
<h2 id="replaysubject"><code>ReplaySubject</code></h2>
<ul>
<li>버퍼 사이즈 만큼의 값들을 유지하며, 새로운 <code>Subscriber</code>에게 <strong>버퍼 사이즈만큼 값을 방출</strong>한다.</li>
<li>버퍼는 메모리에 할당되어져 있기 때문에, 이미지와 같이 큰 용량의 것을 버퍼로 갖지 않도록 한다.</li>
<li><code>.error</code> 가 발생하여도 우선 버퍼에 있는 값들은 전달된다.</li>
</ul>
<pre><code class="language-swift">let disposeBag = DisposeBag()

let subject = ReplaySubject&lt;String&gt;.create(bufferSize: 2)
subject.on(.next(&quot;Before subscribed&quot;))

// Subscription 1
subject.subscribe {
    print(&quot;🟢&quot;, $0)
}

subject.on(.next(&quot;1&quot;))
subject.on(.next(&quot;2&quot;))
subject.on(.next(&quot;3&quot;))

// Subscription 2
subject.subscribe {
    print(&quot;🔴&quot;, $0)
}

subject.on(.next(&quot;4&quot;))
subject.on(.error(CustomError.error))
subject.on(.next(&quot;After error&quot;))


// Subscription 3
subject.subscribe {
    print(&quot;🟡&quot;, $0)
}.disposed(by: disposeBag)

subject.on(.next(&quot;5&quot;))</code></pre>
<h2 id="asyncsubject"><code>AsyncSubject</code></h2>
<ul>
<li><code>.comleted</code>되어야만 값이 나온다.</li>
<li><code>.completed</code> 이전의 event에 대해서는 어떠한 것도 방출하지 않는다.<ul>
<li>subscribe하여도 마찬가지로 <code>.comleted</code> 되기 전 까지는 아무런 값을 방출하지 않는다.</li>
</ul>
</li>
<li>종료 이벤트를 받으면 <code>.next(lastElement) + .completed</code> 를 방출한다.</li>
<li>종료된 이후에 subscribe할 경우, <code>.next(lastElement) + .completed</code> 를 받게 된다.</li>
<li>만약 값이 없이 그냥 <code>.completed</code>된 경우는 값 없이 그냥 completed만 받게 되며, error의 경우에는 <code>.error</code>를 받게 된다.</li>
</ul>
<pre><code class="language-swift">let subject = AsyncSubject&lt;Int&gt;()
subject.onNext(0)
subject.onNext(1)

subject.debug(&quot;🟢&quot;)
    .subscribe()
    .disposed(by: disposeBag)

subject.onNext(2)

subject.onCompleted()

subject.onNext(3)

subject.debug(&quot;🔴&quot;)
    .subscribe()
    .disposed(by: disposeBag)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxSwift] 1. Observable]]></title>
            <link>https://velog.io/@dev-yong/1.-Observable</link>
            <guid>https://velog.io/@dev-yong/1.-Observable</guid>
            <pubDate>Sun, 09 Jan 2022 02:42:52 GMT</pubDate>
            <description><![CDATA[<h1 id="basics">Basics</h1>
<ul>
<li>모든 <code>Observable</code> sequence은 단지 sequence이다</li>
<li>Sequence는 0개 혹은 더 많은 element를 갖을 수 있다.</li>
<li><code>error</code> 혹은 <code>completed</code> 이벤트를 받을 때, sequence는 더이상 element를 생산해낼 수 없다.</li>
</ul>
<pre><code class="language-swift">enum _Event&lt;Element&gt; {

    case next(Element)
    case error(Swift.Error)
    case completed

}</code></pre>
<pre><code class="language-swift">protocol _ObserverType {

    associatedtype Element
    func on(_ event: Event&lt;Element&gt;)

}</code></pre>
<pre><code class="language-swift">protocol _ObservableType {

    associatedtype Element

    func subscribe&lt;Observer: ObserverType&gt;(
        _ observer: Observer
    ) -&gt; Disposable where Observer.Element == Element
}</code></pre>
<pre><code class="language-swift">class _Observable&lt;Element&gt;: ObservableType {

    func subscribe&lt;Observer&gt;(
        _ observer: Observer
    ) -&gt; Disposable
    where Observer : ObserverType, Element == Observer.Element {
        fatalError()
    }

}</code></pre>
<h1 id="creating">Creating</h1>
<ul>
<li><strong><code>Observable</code>은 sequence의 생성 방법과 element 요소 생성에 사용되는 parameter를 정의할 뿐</strong>이다.</li>
<li><code>subscribe</code> 메서드가 호출되어야 sequence가 시작된다.</li>
</ul>
<pre><code class="language-swift">func syncObservable&lt;E&gt;(_ element: E) -&gt; Observable&lt;E&gt; {
    return Observable.create { observer in
        observer.on(.next(element))
        observer.on(.completed)
        return Disposables.create()
    }
}</code></pre>
<pre><code class="language-swift">func asyncObservable(_ interval: DispatchTimeInterval) -&gt; Observable&lt;Int&gt; {
    return Observable.create { observer in
        print(&quot;Subscribed&quot;)
        let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
        timer.schedule(deadline: DispatchTime.now() + interval, repeating: interval)

        let cancel = Disposables.create {
            print(&quot;Disposed&quot;)
            timer.cancel()
        }

        var next = 0
        timer.setEventHandler {
            if cancel.isDisposed {
                return
            }
            observer.on(.next(next))
            next += 1
        }
        timer.resume()

        return cancel
    }
}</code></pre>
<h2 id="empty"><code>empty</code></h2>
<ul>
<li>element를 하나도 갖지 않는 Observable을 생성한다.</li>
<li><code>completed</code> 이벤트만 방출한다.</li>
<li>즉시 종료할 수 있는 Observable 반환하고자 할 때 시용한다.</li>
</ul>
<pre><code class="language-swift">Observable&lt;Void&gt;.empty()
.subscribe(
    onNext: { print(&quot;Element :&quot;, $0) },
    onCompleted: { print(&quot;Completed&quot;) }
)</code></pre>
<h2 id="never"><code>never</code></h2>
<ul>
<li><code>completed</code> 이벤트마저 방출되지 않는, 종료되지 않는 Observable을 반환한다.</li>
</ul>
<pre><code>Observable&lt;Void&gt;.never()
.subscribe(
    onNext: { print(&quot;Element : &quot;, $0) },
    onCompleted: { print(&quot;Completed&quot;) }
)</code></pre><h2 id="deferred"><code>deferred</code></h2>
<ul>
<li>“연기하다”</li>
<li>실제 Observable이 만들어지는 시점을 미룬다.</li>
<li>Observable을 만들어내는 factory closure를 인자로 받는다.</li>
<li>실제로 <strong>구독이 일어나는 시점에서야 Observable을 만들어 낸다.</strong></li>
</ul>
<pre><code class="language-swift"> static func _deferred(_ observableFactory: @escaping () throws -&gt; Observable&lt;Element&gt;)</code></pre>
<pre><code class="language-swift">var flag = false
let deferred = Observable&lt;Int&gt;.deferred {
    flag.toggle()
    return flag ? Observable.from([1, 2, 3]) : Observable.from([4, 5, 6])
}

deferred
    .debug()
    .subscribe()
    .disposed(by: disposeBag)

deferred
    .debug()
    .subscribe()
    .disposed(by: disposeBag)</code></pre>
<h1 id="disposing">Disposing</h1>
<ul>
<li><code>dispose</code>를 직접 호출하는 것은 bad code smell이 나기 때문에 지양하도록 한다.<ul>
<li><code>DisposeBag</code>, <code>takeUntil</code>과 같은 메커니즘을 이용하여 subscription을 dispose하는 것이 더 좋은 방법이다.</li>
</ul>
</li>
<li><code>dispose</code>가 호출되면, 해당 seqeunce는 더이상 작동하지 않는다.</li>
<li>만일 <code>dispose()</code> 혹은 <code>DisposeBag</code>에 disposable을 추가하는 등의 행위를 하지 않는다면? Observable이 완료되지 않으면 메모리 누수가 날 것이다.</li>
</ul>
<pre><code class="language-swift">public protocol _Disposable {

    func dispose()
}</code></pre>
<h2 id="dispose-bags">Dispose Bags</h2>
<ul>
<li><code>DisposeBag</code>이 deallocate될 때, 추가되어져있던 <code>Disposable</code>들의 <code>dispose</code>를 호출한다.<ul>
<li><code>dispose</code> 메서드를 갖지 않으며, 즉 명시적인 호출을 허용하지 않는다.</li>
<li>바로 정리가 필요할 경우 새롭게 생성한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-swift">    self.disposeBag = DisposeBag()</code></pre>
<h1 id="hot-vs-cold-observable">Hot vs Cold Observable</h1>
<h2 id="hot">Hot</h2>
<ul>
<li>생성과 동시에 이벤트를 방출하기 시작한다.<ul>
<li>Subscribe 되는 시점과 상관없이 Observer들에게 이벤트를 중간부터 전송한다.</li>
<li>생성과 동시에 리소스를 소모한다.</li>
</ul>
</li>
<li>여러 Observer가 하나의 Observable을 공유할 수 있다.</li>
<li><code>ConnectableObservable</code> 로 불리우기도 한다.</li>
<li>e.g. <code>.connect()</code></li>
</ul>
<h2 id="cold">Cold</h2>
<ul>
<li>Observer가 Subscribe하는 시점부터 이벤트를 생성하여 방출한다.<ul>
<li>Subscribe되기 전에는 리소스를 소모하지 않는다.</li>
</ul>
</li>
<li>Observer마다 별도의 Observable 인스턴스를 갖는다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Clean Architecture] 3부. 설계 원칙]]></title>
            <link>https://velog.io/@dev-yong/Clean-Architecture-3%EB%B6%80.-%EC%84%A4%EA%B3%84-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@dev-yong/Clean-Architecture-3%EB%B6%80.-%EC%84%A4%EA%B3%84-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Sat, 25 Sep 2021 09:14:44 GMT</pubDate>
            <description><![CDATA[<h1 id="solid-원칙">SOLID 원칙</h1>
<ul>
<li><p>함수와 데이터를 <strong><code>클래스</code>로 배치하는 방법</strong>과 이들 <strong><code>클래스</code>들을 서로 결합하는 방법</strong>을 설명한다.</p>
</li>
<li><p><code>중간 수준</code> 의 구조를 아래와 같이 만들기 위한 목적을 지닌다.</p>
<ul>
<li>변경에 유연한</li>
<li>이해하기 쉬운</li>
<li>다양하게 쓰일 수 있는 컴포넌트의 기반이 되는  </li>
</ul>
</li>
</ul>
<blockquote>
<p>🤔 <code>코드수준</code> , <code>중간수준</code> , <code>고수준</code> 이란 무엇인가?
<code>코드수준</code> : 실제 소스 코드</p>
<p>설계 원칙(SOLID)이 <code>중간 수준</code>의 소프트웨어 구조를 형성하는데 작용한다.</p>
<p><code>중간수준</code> : 모듈 수준, (컴포넌트 원칙)</p>
<p><code>고수준</code> : 아키텍처</p>
</blockquote>
<blockquote>
<p>🤔 클래스, 모듈의 개념적 차이는? </p>
<ul>
<li>동일한 개념을 뜻한다.<ul>
<li>클래스 : 단순히 함수와 데이터를 결합한 집합 (62p)</li>
<li>모듈 : 단순히 함수와 데이터로 응집된 집합 (67p)</li>
</ul>
</li>
</ul>
</blockquote>
<h1 id="srp-단일-책임-원칙">SRP: 단일 책임 원칙</h1>
<h2 id="오해">오해</h2>
<ul>
<li><del>모듈은 단 하나의 일만 해야한다.</del> ❌</li>
<li>함수는 단 하나의 일만 해야한다. ✅<ul>
<li>이 원칙은 SOLID 원칙에 속하지 않는다.</li>
</ul>
</li>
</ul>
<h2 id="정의">정의</h2>
<blockquote>
<p>&quot;A class should have only one reason to change&quot;</p>
</blockquote>
<ul>
<li>SRP에서는 &quot;변경의 이유(reason to change)&quot;가 될 <strong>책임(responsibility)</strong>을 규정한다.</li>
<li>&quot;변경의 이유&quot;란 바로 <strong>Actor</strong>(변경을 요청하는 집단, 사용자와 이해관계자)를 지칭한다.</li>
<li>따라서, 아래와 같이 기술될 수 있다.</li>
</ul>
<p><span style='background-color:#fff5b1'><strong>하나의 클래스</strong>은 오직 <strong>하나의 Actor에 대해서만 책임</strong>을 갖는다.</span></p>
<ul>
<li><strong>단일 Actor를 책임지는 코드는 응집성(Cohesion)</strong>으로 묶인다.<ul>
<li><span style='background-color:#ffdce0'>서로 다른 Actor가 의존하는 코드가 있다면, 코드를 분리</span>하도록 한다.</li>
</ul>
</li>
</ul>
<h1 id="ocp-개방-폐쇄-원칙">OCP: 개방-폐쇄 원칙</h1>
<h2 id="정의-1">정의</h2>
<blockquote>
<p>&quot;You should be able to extend a classes behavior, without modifying it.&quot;</p>
</blockquote>
<p><span style='background-color:#fff5b1'>클래스의 <strong>행위는 확장</strong>할 수 있어야 하지만, 이때 <strong>개체를 변경해서는 안 된다.</strong></span></p>
<ul>
<li>Open for Extension, 확장에는 열려있어야 하지만</li>
<li>Closed for Modification, 변경에는 닫혀있어야 한다.</li>
</ul>
<h3 id="변경을-최소화하려면">변경을 최소화하려면?</h3>
<ul>
<li>서로 다른 <code>목적</code>으로 변경되는 <code>요소</code>를 적절하게 분리한다. (SRP, 단일 책임 원칙)<ul>
<li><code>목적</code> : 변경의 이유, Actor의 요구사항</li>
<li><code>요소</code> : 클래스의 내용</li>
</ul>
</li>
<li>변경되는 요소 사이의 의존성을 체계화한다. (DIP, 의존성 역전 원칙)</li>
</ul>
<h2 id="best-practice">Best Practice</h2>
<ul>
<li>변경을 최소화하기 위하여 책임을 분리(SRP)하고 행위가 확장될 때 변경이 발생하지 않도록(OCP)  <span style='background-color:#ffdce0'><strong>처리 과정을 클래스 단위로 분할</strong>하고, <strong>클래스는 컴포넌트 단위로 분리</strong></span>한다.<ul>
<li><code>﹦ (이중선)</code> : 컴포넌트 단위<ul>
<li>화살표와 오직 한 방향으로만 교차한다. (<strong>컴포넌트 관계는 단방향</strong>으로만 이루어진다.)</li>
</ul>
</li>
<li><code>→ (열린 화살표)</code> : 사용<ul>
<li>A → B: A가 B를 호출한다.</li>
</ul>
</li>
<li><code>⇾ (닫힌 화살표)</code> : 구현 혹은 상속<ul>
<li>A ⇾ B: A가 B를 상속(혹은 인터페이스를 채택)하여 수직(혹은 수평)확장을 한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://images.velog.io/images/dev-yong/post/cd7b5d9e-99b4-4078-aef8-a55362e529d8/image.png" alt=""></p>
<ul>
<li>화살표는 변경으로부터 보호하려는 컴포넌트를 향하도록 그려진다.<ul>
<li><code>View</code>에서 발생한 변경으로부터 <code>Presenter</code>를 보호하고자 한다. </li>
<li><code>Presenter</code>에서 발생한 변경으로부터 <code>Controller</code>를 보호하고자 한다.</li>
<li><code>Interactor는</code> 다른 모든 것에서 발생한 변경으로부터 보호하고자 한다.</li>
</ul>
</li>
<li>컴포넌트 계층구조를 이와 같이 조직화하면 <strong>저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호</strong>할 수 있다.</li>
</ul>
<h3 id="인터페이스의-목적">인터페이스의 목적</h3>
<h4 id="방향성-제어-의존성-역전">방향성 제어 (의존성 역전)</h4>
<ul>
<li><code>FinancialReportGenerator</code> → <code>FinancialDataGateway&lt;I&gt;</code>  ⇽ <code>FinancialDataMapper</code> <h4 id="정보-은닉">정보 은닉</h4>
</li>
<li><code>FinancialReportRequester</code><ul>
<li>Interactor 내부에 대해 너무 많이 알지 못하도록 막기 위해서 존재한다.</li>
<li>없을 경우, Controller는 FinancialEntities에 대해 <code>Transitive Dependency</code> 를 가지게 된다.</li>
<li><code>Transitive Dependency</code>을 가지게 되면, 소프트웨어 엔티티는 ‘자신이 직접 사용하지 않는 요소에는 절대로 의존해서는 안 된다’는 소프트웨어 원칙을 위반하게 된다.</li>
</ul>
</li>
</ul>
<h1 id="lsp-리스코프-치환-원칙">LSP: 리스코프 치환 원칙</h1>
<h2 id="정의-2">정의</h2>
<blockquote>
<p>&quot;Derived classes must be substitutable for their base classes.&quot;</p>
</blockquote>
<p><span style='background-color:#fff5b1'>클래스는 프로그램의 <strong>정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 치환</strong>할 수 있어야 한다.</span></p>
<ul>
<li>사용자(<code>Billing</code>)의 행위가 사용하는 <strong>타입(<code>License&lt;Interface&gt;</code>)에 의존하지 않고 있어, 하위 타입들(<code>PersonalLicense</code>, <code>Business License</code>)로 치환</strong>할 수 있다.<ul>
<li>타입에 의존하게 된다면, 타입을 치환할 수 없게 된다.
<img src="https://images.velog.io/images/dev-yong/post/df7a4f49-11dd-4cda-a4e3-0b284dc7b0e4/image.png" alt=""></li>
</ul>
</li>
</ul>
<h1 id="isp-인터페이스-분리-원칙">ISP: 인터페이스 분리 원칙</h1>
<h2 id="정의-3">정의</h2>
<blockquote>
<p>&quot;Make fine grained interfaces that are client specific.&quot;</p>
</blockquote>
<p><span style='background-color:#fff5b1'><strong>클라이언트별로 세분화된 인터페이스</strong>를 만들어라 </span></p>
<ul>
<li><span style='background-color:#ffdce0'><strong>오퍼레이션을 인터페이스 단위로 나눠라.</strong> </span></li>
<li>오퍼레이션을 인터페이스 단위로 나누지 않는다면,<ul>
<li>무관한 오퍼레이션을 변경하더라도 필요치 않은 배포가 필요로 하다.</li>
<li>필요 이상으로 많은 것을 포함하는 모듈에 의존하는 것은 불필요한 컴파일·배포를 강제한다. </li>
</ul>
</li>
</ul>
<h1 id="dip-의존성-역전-원칙">DIP: 의존성 역전 원칙</h1>
<h2 id="정의-4">정의</h2>
<blockquote>
<p>&quot;Depend on abstractions, not on concretions.&quot;</p>
</blockquote>
<p><span style='background-color:#fff5b1'>구체화(Concretion)에 의존하지말고, 추상화(Absraction)에 의존하라.</span></p>
<ul>
<li><p>추상화에 의존하며, 구체화에 의존하지 않는 시스템을 유연성이 극대화된 시스템이라고 한다.</p>
<ul>
<li>구체화에 의존하는 경우(<code>String</code>)가 존재하지만, 이는 변경되는 일이 거의 없고 엄격하게 통제된다.</li>
<li><strong>안정성이 보장되어진 환경에 대해서는 구체화에 대한 의존성을 용납</strong>한다.</li>
<li><strong>변동성이 큰 구체화에 대한 의존을 피하도록 한다.</strong></li>
<li><strong>인터페이스는 구현체보다 변동성이 낮다.</strong></li>
</ul>
</li>
<li><p>안정된 소프트웨어 아키텍처란 변동성이 큰 구현체에 의존하는 일은 지양하고, <strong>안정된 추상 인터페이스를 선호하는 아키텍처</strong>이다.</p>
</li>
</ul>
<h2 id="실천법">실천법</h2>
<ul>
<li><span style='background-color:#ffdce0'>변동성이 큰 구현체를 참조하지 않는다.</span><ul>
<li>인터페이스를 참조하라.</li>
<li>객체 생성을 Abstract Factory를 사용하도록 강제한다.</li>
</ul>
</li>
<li><span style='background-color:#ffdce0'>변동성이 큰 구현체로부터 파생하지 말라.</span><ul>
<li>수직 확장을 지양하라.</li>
<li>변경이 더욱 어려워진다.</li>
</ul>
</li>
<li><span style='background-color:#ffdce0'>구체함수를 overried 하지 말라.</span><ul>
<li>소스코드 의존성을 제고할 수 없고, 상속하게 되버린다.</li>
</ul>
</li>
</ul>
<h3 id="abstract-factory">Abstract Factory</h3>
<ul>
<li>객체를 생성하려면 해당 객체를 구체적으로 정의한 코드에 대하여 소스코드 의존성이 발생하니, 이때 사용할 수 있도록 한다.</li>
</ul>
<p><img src="https://images.velog.io/images/dev-yong/post/27b3e21d-85e6-45f6-b80f-1d0cf7a6bcab/image.png" alt=""></p>
<ul>
<li><code>Application</code>이 <code>Service</code> 인터페이스를 통하여 <code>ConcreteImpl</code>를 사용한다.<ul>
<li><code>ConcreteImpl</code>의 인스턴스 생성이 필요하다.</li>
<li><code>ConcreteImpl</code>에 대한 소스코드 의존성이 발생하는 것을 막기 위하여 <code>ServiceFactory</code>를 이용한다.</li>
</ul>
</li>
<li>소스코드 의존성은 곡선(아키텍처의 경계)과 교차할 때 모두 Absract Component로 향한다.<ul>
<li>Abstract Component : 고수준의 비즈니스 로직</li>
<li>Concrete Component : 비즈니스 로직을 다루기 위한 세부사항들</li>
</ul>
</li>
<li><span style='background-color:#dcffe4'>의존성은 제어흐름과 반대방향으로 역전되어 흐른다. 이를 <strong>Dependency Inversion(의존성 역전)</strong></span>이라 칭한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Swift 5.4] Result Builder]]></title>
            <link>https://velog.io/@dev-yong/Swift-Result-Builder</link>
            <guid>https://velog.io/@dev-yong/Swift-Result-Builder</guid>
            <pubDate>Thu, 08 Apr 2021 06:12:57 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p><code>@resultBuilder</code></p>
</li>
<li><p>Swift 5.1에 등장하였던 비공식의 function builder가 Swift 5.4 에서 공식적으로 추가되었다.</p>
</li>
<li><p>일련의 <strong>구성 요소들로부터 결과값을 암시적으로 구축</strong>할 수 있도록 한다. </p>
</li>
<li><p>함수의 표현식문에서 부분적인 결과를 수집하고 이를 결과값으로 결합하기 위한 임베디드된 DSL로 사용할 수 있다.</p>
<ul>
<li>빌더 변환의 힘은 의도적으로 제한되므로, 결과값이 원래 코드의 dynamic semantics를 보존한다.</li>
<li>함수의 원래 명령문은 여전히 정상적으로 실행된다.</li>
<li>일반적인 semantics에서 무시되는 값이 실제로 결과값에 수집된다.</li>
<li>빌더 변환을 위한 임시(ad hoc) 프로토콜의 사용은 새로운 종류의 명령문을 지원할 것인지, 아니면 변환의 세부 사항을 커스터마이징할 것인지에 대한 다양한 미래의 확장 여지를 남겨 둔다.</li>
</ul>
</li>
</ul>
<h1 id="motivation">Motivation</h1>
<pre><code class="language-swift">return body([
  division([
    header1(&quot;Chapter 1. Loomings.&quot;),
    paragraph([&quot;Call me Ishmael. Some years ago&quot;]),
    paragraph([&quot;There is now your insular city&quot;])
  ]),
  division([
    header1(&quot;Chapter 2. The Carpet-Bag.&quot;),
    paragraph([&quot;I stuffed a shirt or two&quot;])
  ])
])</code></pre>
<ul>
<li><p>쉼표, 괄호, 대괄호 등 많은 구두점이 존재한다. </p>
<ul>
<li>피상적인 문제로, <strong>내용에서 집중을 분산</strong>시키기 때문에 피하는 것이 좋다.</li>
</ul>
</li>
<li><p>children을 위해 array literal을 사용하고 있기 때문에 type-checker는 <strong>Element가 동일한 타입을 갖도록 요구</strong>할 것이다.</p>
</li>
<li><p>가장 큰 문제는 <strong>이 계층 구조를 변경하는 것이 어색하다</strong>는 것이다.</p>
</li>
</ul>
<pre><code class="language-swift">division((useChapterTitles ? [header1(&quot;Chapter 1. Loomings.&quot;)] : []) +
    [paragraph([&quot;Call me Ishmael. Some years ago&quot;]),
     paragraph([&quot;There is now your insular city&quot;])])</code></pre>
<h1 id="detail">Detail</h1>
<h2 id="result-builder-attribute">Result builder Attribute</h2>
<ul>
<li><code>var</code> 및 <code>subscript</code>의 경우 선언은 getter를 정의해야하며 속성은 해당 getter의 속성인 것처럼 처리된다.<ul>
<li>이러한 방식으로 사용되는 result builder attribute는 result builder transform 함수의 본문에 적용되도록 한다.</li>
</ul>
</li>
<li>프로토콜 요구 사항의 매개 변수를 포함하여 함수 유형의 매개 변수에 대한 속성으로도 사용될 수 있다.</li>
</ul>
<h2 id="methods">Methods</h2>
<ul>
<li>Result builder method는 result builder 유형에서 호출 할 수 있는 <code>static</code> 메소드이다.<ul>
<li><code>BuilderType.&lt;methodName&gt;(&lt;arguments&gt;)</code></li>
</ul>
</li>
</ul>
<pre><code class="language-swift">@resultBuilder
struct ExampleResultBuilder {

  /// 변환 된 함수의 개별 명령문 표현식 유형. 
  /// `buildExpression()`이 제공되지 않은 경우 기본값은 Component이다.
  typealias Expression = ...

  /// 모든 빌드 메소드를 통해 전달되는 부분 결과의 유형
  typealias Component = ...

  /// 최종 반환 된 결과의 유형. 
  /// `buildFinalResult()`가 제공되지 않은 경우 기본값은 Component이다.
  typealias FinalResult = ...

  /// Required by every result builder to build combined results from
  /// statement blocks.
  static func buildBlock(_ components: Component...) -&gt; Component { ... }

  /// If declared, provides contextual type information for statement
  /// expressions to translate them into partial results.
  static func buildExpression(_ expression: Expression) -&gt; Component { ... }

  /// Enables support for `if` statements that do not have an `else`.
  static func buildOptional(_ component: Component?) -&gt; Component { ... }

  /// With buildEither(second:), enables support for &#39;if-else&#39; and &#39;switch&#39;
  /// statements by folding conditional results into a single result.
  static func buildEither(first component: Component) -&gt; Component { ... }

  /// With buildEither(first:), enables support for &#39;if-else&#39; and &#39;switch&#39;
  /// statements by folding conditional results into a single result.
  static func buildEither(second component: Component) -&gt; Component { ... }

  /// Enables support for &#39;for..in&#39; loops by combining the
  /// results of all iterations into a single result.
  static func buildArray(_ components: [Component]) -&gt; Component { ... }

  /// If declared, this will be called on the partial result of an &#39;if
  /// #available&#39; block to allow the result builder to erase type
  /// information.
  static func buildLimitedAvailability(_ component: Component) -&gt; Component { ... }

  /// If declared, this will be called on the partial result from the outermost
  /// block statement to produce the final returned result.
  static func buildFinalResult(_ component: Component) -&gt; FinalResult { ... }
}</code></pre>
<h2 id="transform">Transform</h2>
<ul>
<li>Result builder transform은 명령문 블록과 그 안의 개별 명령문에서 작동하는 재귀 변환이다.</li>
</ul>
<h3 id="statement-blocks">Statement blocks</h3>
<ul>
<li>Statment 블록 내에서, 개별 statement는 별도로 연결되는 statement의 시퀀스로 표현된다.</li>
<li>각각의 시퀀스는 선택적으로 단일의 부분적인 결과를 생성할 수 있는 데, 이는 나중에 블록에서 사용될 수 있는 표현이다.<ul>
<li>변환이 모든 statment에 적용된 후, <code>buildBlock</code> 에 대한 호출이 생성되어 결합된 결과를 형성하며, 모든 부분적인 결과들은 unlabelled arguments로 표시된다.</li>
</ul>
</li>
</ul>
<h3 id="expression-statements">Expression statements</h3>
<ul>
<li><code>buildExpression</code> 메소드를 선언하는 경우, 변환은 이를 unlabelled arguments수로 expression-statement를 전달한다. <ul>
<li>이 call expression은 이후 expression stament로 사용된다. </li>
<li>이 호출은 statement-expression과 함께 타입 체크가 되며 해당 유형에 영향을 미칠 수 있다.</li>
</ul>
</li>
<li>statement-expression은 statement-expression의 결과 유형의 고유 변수를 초기화하는 데 사용된다.<ul>
<li>이 변수는 포함 블록의 일부 결과로 처리된다.</li>
<li>이 변수에 대한 참조는 표현식 유형에 영향을 주지 않도록 독립적으로 유형 검사된다.</li>
</ul>
</li>
</ul>
<h3 id="assignments">Assignments</h3>
<ul>
<li>Assigment를 수행하는 expression statement는 항상 <code>()</code>를 반환하지만 다른 모든 expression statements와 동일한 방식으로 처리된다.</li>
<li>Result builder는 <code>()</code> 를 반환하는 expression을 다루는 것을 <code>buildExpression</code> 을 override하여 선택할 수 있다.</li>
</ul>
<pre><code class="language-swift">static func buildExpression(_: ()) -&gt; Component { ... }</code></pre>
<h3 id="selection-statements">Selection statements</h3>
<p><code>if / else</code>  그리고 <code>switch</code> statement는 케이스에 따라 조건부로 값을 생성한다.</p>
<pre><code class="language-swift">if i == 0 {
  &quot;0&quot;
}

📦
var vCase0: String?
if i == 0 {
  vCase0 = &quot;0&quot;
}
let v0 = BuilderType.buildOptional(vCase0)</code></pre>
<pre><code class="language-swift">if i == 0 {
  &quot;0&quot;
} else if i == 1 {
  &quot;1&quot;
} else {
  generateFibTree(i)
}

📦
let vMerged: PartialResult
if i == 0 {
  vMerged = BuilderType.buildEither(first: &quot;0&quot;)
} else if i == 1 {
  vMerged = BuilderType.buildEither(second:
        BuilderType.buildEither(first: &quot;1&quot;))
} else {
  vMerged = BuilderType.buildEither(second:
        BuilderType.buildEither(second: generateFibTree(i)))
}</code></pre>
<h3 id="imperative-control-flow-statements">Imperative control-flow statements</h3>
<ul>
<li><code>return</code> 은 속성을 몇시적으로 제공하는 <code>func</code>, <code>getter</code> 에만 적용된다.</li>
<li><code>break</code>, <code>continue</code>, <code>guard</code> 는 지원되지 않는다.</li>
</ul>
<h3 id="exception-handling-statements">Exception-handling statements</h3>
<ul>
<li><p><code>throw</code>, <code>defer</code>, <code>do</code></p>
</li>
<li><p><code>for .. in</code> 문은 루프의 각 반복을 실행하여, 모든 반복의 부분적 결과를 배열로 수집한다.</p>
<ul>
<li>그리고 해당 배열이 <code>buildArray</code>로 전달된다.</li>
</ul>
</li>
</ul>
<h2 id="referecne">Referecne</h2>
<ul>
<li><a href="https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md">swift-evolution-result-builders</a></li>
<li><a href="https://github.com/carson-katri/awesome-function-builders">awesome-function-builders</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WWDC] High Performance Auto Layout]]></title>
            <link>https://velog.io/@dev-yong/WWDC-High-Performance-Auto-Layout</link>
            <guid>https://velog.io/@dev-yong/WWDC-High-Performance-Auto-Layout</guid>
            <pubDate>Fri, 12 Mar 2021 02:26:34 GMT</pubDate>
            <description><![CDATA[<h1 id="internals-and-intuition">Internals and intuition</h1>
<h2 id="the-render-loop">The Render Loop</h2>
<blockquote>
<p><strong>TL;DR</strong></p>
<ul>
<li>초당 120번 실행된다.</li>
<li>Update Cosntraints, Layout, Display 3가지의 단계로 구성되어 있다.</li>
<li>불필요한 작업을 피할 때 매우 유용하다.</li>
</ul>
</blockquote>
<ul>
<li><p><strong>Render Loop는</strong> 잠재적으로 <strong>초당 120번 실행되는 프로세스</strong>이다.</p>
</li>
<li><p>Render Loop는 모든 콘텐츠가 각 프레임 에 대해 준비되었는지 확인한다.</p>
</li>
<li><p>이는 <strong>Update Constraints</strong>, <strong>Layout</strong>, <strong>Display</strong> 3가지의 단계로 구성되어져 있다. </p>
<ol>
<li><p>모든 view들이 Leaf 에서 부터 window까지 View Hierarchy를 따라 올라가며 <code>updateConstraints</code> 를 수신한다.</p>
</li>
<li><p>모든 view들이 Window 부터 Leaf까지 내려가며 <code>layoutSubviews()</code> 를 수신한다.</p>
</li>
<li><p>마지막으로, 필요한 경우 모든 view가 그려진다.</p>
</li>
</ol>
</li>
</ul>
<p><img src="https://images.velog.io/images/dev-yong/post/683c38ba-3139-480e-90aa-3021875f22f8/image.png" alt="The Render Loop - Phase"></p>
<ul>
<li>각 단계는 동일한 목적을 갖는 평행한 method 세트를 지니고 있다.<ul>
<li>이것들의 목적은 <strong>낭비되는 작업을 피하며 작업을 연기하고 완전히 건너뛰게</strong>하는 것이다.</li>
<li>예를 들어, UILabel의 크기를 구성하는 데에 있어 많은 속성들이 존재한다.<ul>
<li>속성이 변경될 때마다 텍스트 사이즈를 재측정하는 방법이 존재한다.</li>
<li>그러나 일반적으로 이러한 항목을 연속적으로 변경하기 때문에 매우 비효율적이다.</li>
<li>처음 Label을 설정할 때, 많은 property setter를 호출할 것이고, 각각의 setter에서 텍스트 사이즈를 다시 측정한다면 중간 것들이 모두 낭비될 것이기에 마지막에 측정되기만을 원할 것이다. </li>
<li>이것이 Render Loop가 주는 이점이다.</li>
<li>Property setter에서 <code>setNeedsUpdateConstraints()</code> 를 호출하면 프레임이 화면으로 이동하기 직전에 <code>updateConstraints()</code> 를 호출하게 할 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://images.velog.io/images/dev-yong/post/57ba188f-e69f-44c5-a334-9b7b20a12042/image.png" alt="The Render Loop - methods"></p>
<h3 id="example">Example</h3>
<pre><code class="language-swift">var myConstraints: [NSLayoutConstraints] = []

override func updateConstraints() {
      // 1. 모든 constraints에 대하여 비활성화한다.
    NSLayoutConstraint.deactivate(myConstraints) 
    myConstraints.removeAll()
      // 2. Layout을 구현하는 constraints를 생성한다.
    let views = [&quot;text1&quot;:text1, &quot;text2&quot;:text2]
    myConstraints += NSLayoutConstraint.constraints(withVisualFormat: &quot;H:|-[text1]-[text2]&quot;,
                                                    options: [.alignAllFirstBaseline],
                                                    metrics: nil, views: views)
    myConstraints += NSLayoutConstraint.constraints(withVisualFormat: &quot;V:|-[text1]-|&quot;,
                                                    options: [],
                                                    metrics: nil, views: views)
      // 3. 모든 constraints를 활성화한다.
    NSLayoutConstraint.activate(myConstraints)

    super.updateConstraints()
}</code></pre>
<ul>
<li><code>updateConstraints()</code> 에서 모든 constaints를 deactivate / activate 하는 것은<ul>
<li><code>layoutSubviews()</code> 에서  모든 subviews를 remove / add 하는 것과 동일하다.</li>
<li>불필요한 작업이 발생하고 있기 때문에,  한 번 이상은 작업을 수행하지 않도록 한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-swift">var myConstraints: [NSLayoutConstraints] = []

override func updateConstraints() {
    // 불필요하게 모든 constraints를 deactivate/activate 하지않도록 한 번 이상은 작업을 수행하지 않도록한다.

    if myConstraints.isEmpty {

        NSLayoutConstraint.deactivate(myConstraints)
        myConstraints.removeAll()

        let views = [&quot;text1&quot;:text1, &quot;text2&quot;:text2]
        myConstraints += NSLayoutConstraint.constraints(withVisualFormat: &quot;H:|-[text1]-[text2]&quot;,
                                                        options: [.alignAllFirstBaseline],
                                                        metrics: nil, views: views)
        myConstraints += NSLayoutConstraint.constraints(withVisualFormat: &quot;V:|-[text1]-|&quot;,
                                                        options: [],
                                                        metrics: nil, views: views)

        NSLayoutConstraint.activate(myConstraints)
    }
    super.updateConstraints()
}
</code></pre>
<h2 id="activating-a-constraint">Activating a Constraint</h2>
<p><img src="https://images.velog.io/images/dev-yong/post/c84f0c2d-43d5-4031-92d9-d60c504a9208/image.png" alt=""></p>
<ul>
<li>Constraints를 추가하고자 하는 view 가 존재한다.</li>
<li>View는 Window에 있다.</li>
<li>Window는 <strong>Engine</strong>이라고 하는 internal 객체를 지니고 있다(hanging off).</li>
<li><strong>Engine</strong>이 Auto Layout의 계산의 핵심이다.</li>
</ul>
<p><img src="https://images.velog.io/images/dev-yong/post/9fd530ad-6d65-4afc-8858-8f96339af039/image.png" alt=""></p>
<ul>
<li>Cosntraints를 view에 추가하면, constraints에 해당하는 방정식(equation)을 만들고 해당 방정식을 Engine에 추가한다.<ul>
<li>만일 방정식을 &quot;X에 대한 해결&quot;이라고 말한다면, &quot;X는 변수&quot;이다.</li>
</ul>
</li>
</ul>
<p><img src="https://images.velog.io/images/dev-yong/post/b49679ae-a128-4aab-845a-0ea3cf268dba/image.png" alt=""></p>
<ol>
<li>각 방정식들이 Engine에 추가된다.</li>
<li>Engine이 이러한 변수 중 하나에 값을 할당 할 때마다 view에 변수의 출처를 알리고 이것이 변경되었다고 말한다.</li>
<li>Update Cosntraints 단계로 발생되며, <code>setNeedsLayout()</code> 를 받았기 때문에, 어떠한 지점에서 Layout 단계로 이동한다.</li>
<li>UIView는 <code>layoutSubViews()</code>를 받게 된다.<ul>
<li>이는, Engine에서 프레임으로 데이터를 복사하는 것이다.</li>
<li>View는 Engine에 변수의 값이 무엇인지 물어볼 것이다.</li>
<li>Engine은 그 값을 알려주고, <code>setCenter</code>, <code>setBounds</code> 를 호출한다.</li>
</ul>
</li>
</ol>
<h2 id="unrelated-views-dont-interact">Unrelated Views Don&#39;t Interact</h2>
<ul>
<li><strong>연관되지 않은 View들에 대하여 Engine은 개별적인 방정식을 추가</strong>하게되고, 이는 <strong>linear 성능</strong>을 보여주게 된다.</li>
</ul>
<p><img src="https://images.velog.io/images/dev-yong/post/407e722c-bd3b-459f-ae31-8a4872d6133b/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/dev-yong/post/e9b34a34-27e4-48d9-bda5-0e285bd91b29/image.png" alt=""></p>
<blockquote>
<p><strong>Engine은 Layout Cache이고 Dependency Tracker이다.</strong></p>
<ul>
<li>어떤 constraints가 어떤 View에 영향을 미치는지 이해하고, 변경하면 필요한 것만 업데이트한다.</li>
</ul>
</blockquote>
<h1 id="building-efficient-layouts">Building Efficient Layouts</h1>
<h2 id="avoid-constraint-churn">Avoid Constraint Churn</h2>
<ul>
<li>모든 <strong>constraints를 지우는 것을 피하라.</strong><ul>
<li>필요 없이 frame을 배치하게 되거나, 다시 배치할 필요 없는 view가 전달될 것이다.</li>
</ul>
</li>
<li>static constaints는 한번만 추가하라.<ul>
<li>잠재적인 layout에 공통적으로 적용되는 Constraints이 있는 경우 하나를 추가 한 다음 그대로 두어라</li>
</ul>
</li>
<li>Constraints가 변경이 필요할 때만 변경하여라.</li>
<li>View를 <strong>remove 보다 hide</strong>하도록 하라.</li>
</ul>
<h2 id="intrinsic-content-size">Intrinsic Content Size</h2>
<ul>
<li>모든 view들이 <code>intrinsicContentSize</code>가 필요하지는 않다.</li>
<li>Non-View 컨텐츠를 갖는 View (e.g. <code>UIImageView</code>, <code>UILabel</code>) 의 경우, 내용의 크기(e.g. image size, text size)를 반환한다.</li>
<li><code>UIView</code>는 <code>intrinsicContentSize</code> 를 이용하여 constraints를 만든다.</li>
<li><code>intrinsicContentSize</code> 는 <strong>engine에 넣을 크기 정보를 전달하는 방법</strong>이다.</li>
</ul>
<h2 id="system-layout-size-fitting-size">System Layout Size Fitting Size</h2>
<ul>
<li><code>systemLayoutSizeFitting(_:)</code>는 <strong>engine에서 크기 정보를 다시 가져 오는 방법</strong>이다.</li>
<li>생각보다 가벼운 작업이 아니다.</li>
</ul>
<h3 id="getting-size-from-the-engine">Getting Size from the Engine</h3>
<ol>
<li><code>systemLayoutSistemFitting(_:)</code> 가 호출되면, Engine이 생성된다.</li>
<li>Engine에 Cosntraints가 추가된다.</li>
<li>Layout이 해결된다.</li>
<li>상위 view들의 frame 크기가 반환된다.</li>
<li>Engine이 폐기된다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WWDC] Mysteries of AutoLayout, Part 2]]></title>
            <link>https://velog.io/@dev-yong/Mysteries-of-AutoLayout-Part-2</link>
            <guid>https://velog.io/@dev-yong/Mysteries-of-AutoLayout-Part-2</guid>
            <pubDate>Thu, 04 Mar 2021 09:20:08 GMT</pubDate>
            <description><![CDATA[<h1 id="the-layout-cycle">The Layout Cycle</h1>
<h2 id="inside-the-black-box">Inside the Black Box</h2>
<ul>
<li>Black box의 내부에서는 아래와 같이 진행된다.<ol>
<li>View, Constraint, Property, IntrinsicContentSize 등을 구성한다.</li>
<li>Layout Engine이 계산을 한다.</li>
<li>Layout 얻는다.</li>
</ol>
</li>
</ul>
<p><img src="https://images.velog.io/images/dev-yong/post/0eb9813b-1703-4077-9687-b097fa87cf93/image.png" alt="Inside the Black Box"></p>
<h2 id="the-layout-cycle-1">The Layout Cycle</h2>
<blockquote>
<p><strong>TL;DR</strong></p>
<ul>
<li><code>frame</code> 이 즉각적으로 변경될 것이라고 기대하지 말아라.</li>
<li><code>layoutSubViews()</code>를 override할 때 주의를 기울여라.</li>
</ul>
</blockquote>
<p><img src="https://images.velog.io/images/dev-yong/post/0949d4e6-305b-479b-93b9-76a92ced3468/image.png" alt=""></p>
<ul>
<li>우리는 계산된 layout이 달라질 필요가 있는 Cosntraints가 바뀔 때까지 <strong>Application Run Loop</strong>를 반복하는 것으로 시작한다.</li>
<li>이로 인해 <strong>Deferred Layout Pass</strong>가 예약된다.</li>
<li>Layout Pass가 나오면, Hierarchy를 살펴보고 <strong>View에 대한 모든 frame을 업데이트</strong>한다.</li>
</ul>
<h2 id="the-layout-cycle---example">The Layout Cycle - Example</h2>
<ol>
<li>Layout을 변경하면 <strong>layout engine의 모든 것은 이미 변경</strong>되었지만 <strong>UI는 아직 업데이트되지 않은 상태</strong>이다. </li>
</ol>
<p><img src="https://images.velog.io/images/dev-yong/post/40d87740-f0c4-46cb-a7b7-2376465a6844/image.png" alt="Layout Changed"></p>
<ol start="2">
<li>그런 다음 Layout Pass가 진행되면, <strong>UI가 실제로 변경</strong>된다. </li>
</ol>
<ul>
<li>UI는 실제로 Layout Engine이 생각하는 것과 일치하도록 변경된다.</li>
</ul>
<p><img src="https://images.velog.io/images/dev-yong/post/2a3fd1d6-191f-4d10-ac9d-bd3278ca08b1/image.png" alt="UI Updated"></p>
<h2 id="constraint-changes">Constraint Changes</h2>
<ul>
<li><p>생성된 Constraints는 수학적 표현식으로 변환되고 Layout Engine 내에 유지된다.</p>
</li>
<li><p>표현식은 모든 constraints의 변경에 영향을 받는다.</p>
<ul>
<li>Constraints를 activate / deactivate하거나 constraints의 우선 순위 또는 상수를 변경하는 것과 같은 몇 가지 명백한 사항이 포함된다.</li>
<li>또한, View hierarchy를 조작하거나 재구성하는 것(view의 추가 및 삭제)과 같은 덜 분명한 것도 포함된다. </li>
<li>이는 간접적으로 cosntraints의 변경을 유발할 수 있다.</li>
<li>표현식은 특정 View의 <code>origin</code>이나 <code>size</code>와 같은 것을 나타내는 변수로 구성된다.</li>
</ul>
</li>
<li><p><strong>Constraint가 변경</strong>되면, <strong>Layout Engine이 layout을 재계산</strong>한다.</p>
<ul>
<li>Layout이 재계산될 때, 표현식의 변수들은 새로운 값을 받는다.</li>
<li>이런 일이 발생하면, 그들(<del>변수? 이것이 지칭하는 무엇일까</del>)이 나타내는 view들에 알림이 전송되고, <code>superview</code>가 layout이 필요한 것으로 표시된다.<ul>
<li>즉, <strong>View들이  <code>superview.setNeedsLayout</code> 를 호출</strong>한다.</li>
</ul>
</li>
<li>이것이 실제로 <strong>&quot;Deferred Layout Pass&quot;가 예약되는 원인</strong>이다.</li>
<li>Layout enigne에서 <strong>frame이 실제로 변경되지만 View Hierarchy에서는 아직 변경되지 않는</strong> 곳이다.</li>
</ul>
</li>
</ul>
<h2 id="deferred-layout-pass">Deferred Layout Pass</h2>
<ul>
<li><strong>올바른 위치에 있지 않은 모든 View의 위치를 변경</strong> 하도록 한다.</li>
<li>Deferred Layout Pass에는 View hierarchy를 통과하는 &quot;Update Constraints&quot;, &quot;Reassign view frames&quot;가 있다.</li>
</ul>
<h3 id="update-constraints"><a href="https://developer.apple.com/documentation/uikit/uiview/1622512-updateconstraints">Update Constraints</a></h3>
<ul>
<li><p>Constraints에 대한 보류 중인 변경 사항이 있으면, View Hierarchy를 통과하여 모든 View의 위치를 변경하기 전에 해당 변경사항이 지금 발생하는지 확인한다.</p>
</li>
<li><p>View는 <strong><code>updateConstratins()</code> 호출을 명시적으로 요청</strong>하기 위하여 <strong><code>setNeedsUpdateConstaints()</code> 를 호출</strong>한다.</p>
<ul>
<li><p>그리고 이것은 <code>setNeedsDisplay()</code>와 거의 같은 방식으로 작동한다.</p>
</li>
<li><p><code>setNeedsUpdateConstraints()</code> 를 호출하면 얼마 후 <code>updateConstraints()</code>가 호출된다.</p>
</li>
<li><p>이 모든 것은 View가 다음 Layout Pass를 위하여 제때에 Constraints를 변경하는 기회를 가질 수 있는 방법이지만, 실제로는 필요하지 않은 경우가 많다.</p>
<ul>
<li>이상적으로, <strong>최초의 constriants 설정은 Interface Builder에서 발생</strong>하여야 한다.</li>
<li>만일 정말 <strong>constraints를 프로그래밍적으로 할당해야하는 경우에는 <code>viewDidLoad</code> 와 같은 위치가 훨씬 좋다</strong>.</li>
<li>반면에, 해당 로직을 연관된 다른 코드와 분리하여 나중에 실행되는 별도의 메소드로 옮긴다면, 코드를 따라가기 훨씬 어려워지므로 가독성과 유지 관리가 어려워진다.</li>
</ul>
</li>
<li><p><code>updateConstraints()</code>의 사용은 성능으로 귀결된다.</p>
<ul>
<li>Constraints을 변경하는 것이 너무 느리다면 <code>updateConstraints()</code> 가 도움이 될 수 있다.<ul>
<li><strong>Layout Engine이 <code>updateCosntraint</code>에서 발생하는 모든 constraint change를 batch로 처리</strong>할 수 있기 때문에, <code>updateConstraints()</code> 내에서 constraints를 변경하는 것이 다른 시간에 contstraints를 변경하는 것보다 더 빠르다.</li>
<li>이는 Cosntratins 각각을 개별적으로 activate하는 것과는 대조적으로 Constraints Array에 대해 <code>NSLayoutConstraint.activate()</code>를 호출하여 얻는 것과 동일한 종류의 성능 이점이다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Update Constraints Pass가 완료되면 <strong>Constraints가 모두 최신 상태임</strong>을 알 수 있으며 View의 위치를 변경할 준비가 된 것이다.</p>
</li>
</ul>
<h3 id="reassign-view-frames">Reassign view frames</h3>
<ul>
<li>여기에서 Top-Down으로 View Hierarchy 를 탐색하고 <strong>Layout이 필요한 것으로 표시된 모든 뷰에서 <code>layoutSubviews()</code>를 호출</strong>한다.<ul>
<li>Reciever들의 위치를 재조정하기 위한 것이 아닌, <strong>subview의 위치를 재조정하기 위하여</strong> 호출하도록 한다.</li>
<li>Layout Engine으로부터 subview들에 대한 frame을 읽은 다음 할당한다.</li>
</ul>
</li>
<li><strong>Constraints를 이용하여 표현할 수 없는 layout을 필요한 경우에만 <code>layoutSubviews()</code>를 override</strong>하여야 한다.<ul>
<li>override를 할 경우, 어떠한 view들은 이미 배치되어져 있지만, 또다른 view들은 그렇지 않을 수 있다.</li>
<li>배치되지 않은 view도 아마 곧 배치될 것이므로 약간 섬세한 순간이다.</li>
</ul>
</li>
<li>Custom Layout을 위하여 <code>layoutSubViews()</code> 을 override할 경우, 몇 가지 규칙이 존재한다.<ul>
<li>🌟 Do<ul>
<li><code>super.layoutSubviews()</code> 호출하라.</li>
<li>하위 트리 내의 layout을 무효화하라.</li>
</ul>
</li>
<li>❌ Don&#39;t<ul>
<li><code>setNeedsUpdateconstraints()</code>를 호출하지 말아라.</li>
<li>하위 트리 외부의 layout을 무효화하지 말아라.<ul>
<li>Layout feedback loop가 발생하며 무한 루프에 빠질 수 있다.t</li>
</ul>
</li>
<li>무차별적으로 constraints를 수정하지 말아라.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="interacting-with-legacy-layout">Interacting with Legacy Layout</h1>
<ul>
<li>과거부터 현재까지, Layout을 구성하는 방법은 변경되어져 왔다.<ul>
<li>전통적으로 frame을 설정하는 것만으로 view를 배치하였다.</li>
<li>다음은, superview의 size가 변경될 때 view의 size를 조정하는 방법을 지정하는 <code>autoresizingMask</code>가 있었다.</li>
<li>다음으로, AutoLayout에서 constraints으로 모든 작업을 수행하게 되었다.</li>
</ul>
</li>
<li>여전히 <code>view.frame</code> 을 직접 set하면 위치로 이동을 하지만, Framework가 <strong>Layout Engine에서 frame을 복사하여 적용하는 경우, 언제든지 해당 frame을 덮어 쓸 수 있다.</strong><ul>
<li>문제는 가끔 frame을 설정해야한다는 것이다.<ul>
<li>예를 들어 <code>layoutSubviews()</code>를 재정의할 경우, 해당 View의 frame 설정해야 할 수 있다.</li>
<li>운 좋게도 이를 위한 <code>translatesAutoresizingMaskIntoConstraints</code>가 존재한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="translatesautoresizingmaskintoconstraints"><code>translatesAutoresizingMaskIntoConstraints</code></h2>
<blockquote>
<p><strong>TL;DR</strong></p>
<ul>
<li>frame을 직접적으로 설정할 때 이 flag를 사용하라.</li>
<li>Cosntraints를 사용할 때 이 flag를 false로 하여라.</li>
</ul>
</blockquote>
<ul>
<li>Legacy Layout 시스템에서와 같은 방식으로 작동하지만 AutoLayout 세계에서 작동한다.</li>
<li>만일 이 flag가 true이며 <strong>frame을 설정하게 될 경우, framework은 Layout Engine에서 frame을 강제하는 constraints를 생성</strong>한다.<ul>
<li>이는 즉, 원하는 만큼 frame을 설정할 수 있으며 AutoLayout을 사용하여 view의 위치를 유지할 수 있다는 것이다. </li>
<li>Constraints는 <code>autoresizingMask</code>의 행동을 구현한다.</li>
<li>Cosntraints를 사용하여 이 view와 관련된 다른 view들을 배치할 수 있다.</li>
</ul>
</li>
<li>*<em>Constraints를 사용할 때에는 <code>view.translatesAutoresizingMaskIntoConstraints = false</code> *</em>로 하여라.<ul>
<li>프로그래밍방식으로 생성된 view의 경우 기본값은 true이다.</li>
</ul>
</li>
</ul>
<h1 id="constraint-creation">Constraint Creation</h1>
<pre><code class="language-swift"> // NSLayoutConstraint(item:b, attribute:.Top, relatedBy:.Equal, toItem:view, attribute:.Top, multiplier:1, constant:10)

 b.topAnchor.constraintEqualToAnchor(view.topAnchor, constant:10)</code></pre>
<ul>
<li>Cosntraint의 생성을 위하여<code>NSLayoutConstraint</code> factory method를 사용하였지만 가독성이 떨어져, 그 대신 <strong>Layout anchor</strong>가 새로이 도입되었다. </li>
</ul>
<h1 id="constraining-negative-space">Constraining Negative Space</h1>
<ul>
<li>때때로 명확하지 않은 몇 가지 종류의 Layout이 있다.<ul>
<li>전통적으로, 명확하지 않은 layout은 Dummy View를 사용하여 해결하고는 하였다.</li>
<li>하지만, 모든 View에 layer가 연결된 iOS에서는 비효율적이다. 이를 위하여 <code>LayoutGuide</code>가 도입되었다.</li>
</ul>
</li>
</ul>
<h2 id="layoutguide">LayoutGuide</h2>
<pre><code class="language-swift">let guide = UILayoutGuide()
view.addLayoutGuide(guide)</code></pre>
<ul>
<li>LayoutGuide는 단순히 Layout engine에서 사각형을 나타낸다.</li>
<li>Anchor 객체를 노출하므로 constraints 생성 구문에서 작동한다.</li>
<li>Layout anchor는 margin에서 사용할 수 없다.</li>
<li><code>UIView</code>는 <code>layoutMarginsGuide</code>를 노출한다.</li>
</ul>
<h1 id="debugging-your-layout">Debugging Your Layout</h1>
<ul>
<li>Constraints에 indentifier를 추가하도록 하라.</li>
<li>Accessibility idnetifier를 설정하면 해당 identifier가 view와 쌍을 이루는 log에 표시되므로 원하는 view를 찾을 수 있다. </li>
<li>Layout Guide에 identifier를 설정할 수 있다.</li>
<li><code>constraintsAffectingLayoutForAxis</code>를 사용하라.<ul>
<li>한 축 또는 다른 축에서 해당 view에 영향을 미치는 constraints만 알려준다.</li>
</ul>
</li>
</ul>
<h1 id="resolving-ambiguity">Resolving Ambiguity</h1>
<ul>
<li>너무 적은 constraints 혹은 priority의 충돌로 인하여 발생할 수 있다.</li>
</ul>
<h2 id="diagnostic-tools">Diagnostic tools</h2>
<ul>
<li><code>po view._autolayoutTrace</code></li>
<li>View Debugger</li>
<li><code>exerciseAmbiguityInLayout</code></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>