<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts on Kelly Dev</title><link>https://kelly-chui.github.io/posts/</link><description>Recent content in Posts on Kelly Dev</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 18 Aug 2026 17:52:46 +0900</lastBuildDate><atom:link href="https://kelly-chui.github.io/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>StackDay. 0. Concept</title><link>https://kelly-chui.github.io/posts/stackday-dev-log-concept/</link><pubDate>Sat, 08 Aug 2026 15:36:21 +0900</pubDate><guid>https://kelly-chui.github.io/posts/stackday-dev-log-concept/</guid><description>&lt;h2 id="시작하기"&gt;시작하기&lt;/h2&gt;
&lt;p&gt;애플 리마인더나 마이크로소프트 To do를 쓰면 매일 해야하는 일들을 관리할 수 있다.&lt;/p&gt;
&lt;p&gt;나도 매일 LeetCode Daily를 풀고, Bing 출석체크를 하고, 매일 TIL 을 적으려고 한다.&lt;/p&gt;
&lt;figure class="media-caption align-center"&gt;&lt;img src="https://kelly-chui.github.io/posts/stackday-dev-log-concept/image-001-optimized-image.webp" alt="" loading="lazy" width="360"&gt;
&lt;/figure&gt;
&lt;p&gt;하지만 기본적으로 Reminder, Todo 앱과 습관 형성 앱은 차이가 있다.&lt;/p&gt;
&lt;p&gt;첫 번째로 기능 요구사항이 다르다. 애플 리마인더는 매일 반복해야 하는 태스크를 하지 않았을 경우에, 날짜가 새로 갱신되지 않고, 그 날짜에 계속 남아있는다. 마이크로소프트 To do는 하루를 빠뜨리면 그 태스크가 사라지지 않고 누적된다.&lt;/p&gt;
&lt;p&gt;내가 원하는 습관 형성 앱은 리마인더의 방식, 마이크로소프트 투 두의 방식 등등을 포괄할 수 있는 앱이었다.&lt;/p&gt;</description></item><item><title>dev-data-server-light. 0. Concept</title><link>https://kelly-chui.github.io/posts/devbox-light-server-00-concept/</link><pubDate>Wed, 24 Jun 2026 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devbox-light-server-00-concept/</guid><description>&lt;h2 id="시작하기"&gt;시작하기&lt;/h2&gt;
&lt;p&gt;iOS 프로젝트를 진행하다 보면 생각보다 자주 간단한 API 서버가 필요해진다. 회원가입 화면을 만들거나, 목록 조회 기능을 붙이거나, 이미지 업로드를 테스트할 때도 서버가 필요하다. 물론 실제 백엔드를 구축할 수도 있고, Supabase나 Firebase 같은 BaaS를 사용할 수도 있다. 하지만 작은 개인 프로젝트나 프로토타입 단계에서는 좋은 설계는 아니라고 생각한다.&lt;/p&gt;
&lt;p&gt;특히 처음부터 특정 서비스에 의존하기 시작하면, 정작 내가 만들고 싶은 앱의 기능보다 인프라 설정과 서비스 사용법을 익히는 데 더 많은 시간을 쓰게 된다. 앱 개발을 위한 도구가 필요했는데, 어느 순간 도구를 사용하기 위한 공부를 하고 있는 상황이 되는 것이다.&lt;/p&gt;</description></item><item><title>JavaScript Basics. 01. 자바스크립트 기본 환경 설치 및 기본 문법</title><link>https://kelly-chui.github.io/posts/javascript-basics-01-environment-and-syntax/</link><pubDate>Sat, 31 May 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/javascript-basics-01-environment-and-syntax/</guid><description>&lt;h1 id="환경-설치"&gt;환경 설치&lt;/h1&gt;
&lt;h2 id="자바스크립트란"&gt;자바스크립트란?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;HTML과 CSS와 함께 클라이언트 측 웹 프로그래밍을 할 수 있도록 돕는 프로그래밍 언어&lt;/li&gt;
&lt;li&gt;C, C++, 자바와 같은 언어에 비해 문법이 쉽고 브라우저에서 바로 실행됨&lt;/li&gt;
&lt;li&gt;조금만 배워도 HTML, CSS와 함께 바로 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="컴파일러-언어와-인터프리터-언어"&gt;컴파일러 언어와 인터프리터 언어&lt;/h3&gt;
&lt;p&gt;자바스크립트는 인터프리터 언어로 작성과 동시에 바로 실행할 수 있는 언어이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로그래밍은 일반적으로 컴파일러와 인터프리터라는 과정을 통하여 프로그래밍 진행&lt;/li&gt;
&lt;li&gt;바로 작성한 프로그램을 확인할 수 있는 언어를 인터프리터 언어, 컴파일러 언어는 소스 코드를 기계어로 번역하는 과정이 필요함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="기반-환경-구축하기"&gt;기반 환경 구축하기&lt;/h3&gt;
&lt;p&gt;ES6부터 CLI를 활용해야한다. node.js, npm, react, git등을 활용하려면 터미널을 활용해야 한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Functions (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-01-functions-1/</link><pubDate>Thu, 25 May 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-01-functions-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="functions"&gt;Functions&lt;/h2&gt;
&lt;p&gt;함수는 특정 작업을 수행하는 독립적인 코드 덩어리이다. 함수에 이름을 지어서 그 함수가 무엇을 하는지를 나타내고, 필요할때 &amp;ldquo;호출(call)&amp;ldquo;할 수 있다.&lt;/p&gt;
&lt;p&gt;스위프트의 함수는 파라미터의 이름이 없는 C-스타일 함수와 파라미터의 이름과 아규먼트 레이블이 모두 있는 Objective-C 스타일 함수를 동시에 표현할 수 있다. 파라미터는 디폴트 값을 가질 수 있고, 파라미터로 받아 값을 변경 시킬 수 있는 in-out 파라미터로도 전달 될 수 있다.&lt;/p&gt;
&lt;p&gt;모든 함수는 파라미터들의 타입과 리턴 타입으로 이루어진 타입을 가지고 있다. 스위프트의 다른 타입들과 마찬가지로 다른 함수에 대한 파라미터로 이 타입을 전달 할 수 있고, 리턴 값으로 사용할 수도 있다. 또한 함수는 중첩할 수 있어 함수 내부에서 함수를 작성 하여 특정 기능에 대한 캡슐화를 할 수 있다.&lt;/p&gt;</description></item><item><title>StackDay. 엔티티와 계산 책임 분리하기</title><link>https://kelly-chui.github.io/posts/stackday-dev-log-derived-value-responsibility/</link><pubDate>Wed, 12 Aug 2026 16:51:04 +0900</pubDate><guid>https://kelly-chui.github.io/posts/stackday-dev-log-derived-value-responsibility/</guid><description>&lt;p&gt;앞서 도메인, 데이터 모델링한 내용을 실제 코드로 옮기기 시작했다.&lt;/p&gt;
&lt;p&gt;모델 자체의 구조는 이미 정해져 있었지만 코드로 구현하니 &lt;code&gt;Habit Entry&lt;/code&gt;같은 파생 값들은 어디서 생성해야 하는지 책임이 정해지지 않았다.&lt;/p&gt;
&lt;h2 id="completion"&gt;Completion&lt;/h2&gt;
&lt;p&gt;먼저 습관을 수행했다는 사실을 기록하는 &lt;code&gt;Completion&lt;/code&gt;을 구현했다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Equatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;habitID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;completedOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;recordedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;Completion&lt;/code&gt;은 저장되는 원본 데이터이므로 완료 사실을 표현하는 데 필요한 값만 가진다.&lt;/p&gt;
&lt;p&gt;미래의 날짜인지, &lt;code&gt;Habit&lt;/code&gt;의 시작일 이전인지, 같은 날짜의 &lt;code&gt;Completion&lt;/code&gt;이 이미 존재하는지와 같은 검증은 &lt;code&gt;Completion&lt;/code&gt; 하나만으로 판단할 수 없기 때문에 넣지 않았다.&lt;/p&gt;</description></item><item><title>StackDay. 데이터 모델링, Entry가 아닌 Completion을 관리해야 하는 이유</title><link>https://kelly-chui.github.io/posts/stackday-dev-log-entry-vs-completion/</link><pubDate>Tue, 11 Aug 2026 16:48:37 +0900</pubDate><guid>https://kelly-chui.github.io/posts/stackday-dev-log-entry-vs-completion/</guid><description>&lt;p&gt;StackDay의 MVP 범위와 정책을 가지고 앱의 도메인과 데이터를 모델링한다.&lt;/p&gt;
&lt;p&gt;도메인 모델링 -&amp;gt; 데이터 모델링 -&amp;gt; 유즈케이스 검증 순서로 진행하고, 각각의 단계가 이전 단계를 검증하는 방향이다.&lt;/p&gt;
&lt;h2 id="앱의-핵심-동작"&gt;앱의 핵심 동작&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://kelly-chui.github.io/posts/stackday-dev-log-concept/"&gt;이전 포스트&lt;/a&gt;에서 정한 앱의 컨셉과 정책에서부터, 이 앱이 어떤 동작을 해야 할지 먼저 정리했다.&lt;/p&gt;
&lt;pre class="mermaid" data-mermaid-source="flowchart&amp;#43;LR%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;A%5B%EC%8A%B5%EA%B4%80&amp;#43;%EC%83%9D%EC%84%B1%5D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;B%5B%EC%8A%B5%EA%B4%80&amp;#43;%EC%B6%94%EC%A0%81%5D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;C%5B%EB%A7%A4%EC%9D%BC&amp;#43;%EC%88%98%ED%96%89&amp;#43;%EC%97%AC%EB%B6%80&amp;#43;%EA%B8%B0%EB%A1%9D%5D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;D%5B%EC%8A%A4%ED%8A%B8%EB%A6%AD%EA%B3%BC&amp;#43;%ED%86%B5%EA%B3%84&amp;#43;%EA%B3%84%EC%82%B0%5D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;E%5B%EC%8A%B5%EA%B4%80&amp;#43;%EC%A2%85%EB%A3%8C%5D%0A%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;A&amp;#43;--%3E&amp;#43;B&amp;#43;--%3E&amp;#43;C&amp;#43;--%3E&amp;#43;D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;B&amp;#43;--%3E&amp;#43;E"&gt;&lt;/pre&gt;&lt;p&gt;사용자가 습관을 만들면 그날부터 습관의 추적이 시작된다. 사용자는 매일 습관을 수행했는지 기록하고, 앱은 누적된 기록을 바탕으로 현재 스트릭과 통계를 보여준다. 더 이상 이어가지 않을 습관은 추적을 종료할 수 있다.&lt;/p&gt;</description></item><item><title>dev-data-server-light. 1. System</title><link>https://kelly-chui.github.io/posts/devbox-light-server-01-system-module/</link><pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devbox-light-server-01-system-module/</guid><description>&lt;p&gt;프로젝트에서 제일 간단한 기능과 구조를 가지기 때문에 가장 먼저 System 모듈을 만들었다.&lt;/p&gt;
&lt;h2 id="system-모듈의-기능"&gt;System 모듈의 기능&lt;/h2&gt;
&lt;p&gt;System 모듈이 제공하는 기능은 단 두 가지뿐이다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GET /system/status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GET /system/uptime&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;status&lt;/code&gt;는 서버가 정상적으로 동작하는지 알려주고, &lt;code&gt;uptime&lt;/code&gt;은 서버가 실행된 이후 얼마나 시간이 지났는지를 반환한다.&lt;/p&gt;
&lt;p&gt;비즈니스 로직도 없고, 데이터베이스도 필요하지 않으며, 파일 시스템도 다루지 않기 때문에 구조를 검증하는데 가장 좋다고 판단했다.&lt;/p&gt;
&lt;h2 id="구조-검증"&gt;구조 검증&lt;/h2&gt;
&lt;p&gt;System 모듈을 만들면서 확인하고 싶었던 것은 기능이 아니라 구조였다. 당시에 생각했던 구조는 다음과 같았다.&lt;/p&gt;</description></item><item><title>JavaScript Basics. 02. 연산자와 명령문</title><link>https://kelly-chui.github.io/posts/javascript-basics-02-operators-and-statements/</link><pubDate>Sun, 01 Jun 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/javascript-basics-02-operators-and-statements/</guid><description>&lt;h1 id="기본-명령어"&gt;기본 명령어&lt;/h1&gt;
&lt;h3 id="표현식"&gt;표현식&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;문장 표현식이 하나 이상 모일 경우, 마지막에 &lt;strong&gt;종결 의미로 세미콜론(;)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="프로그램"&gt;프로그램&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;문장이 모이면 프로그램이 됨&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;문장을 작성하고, 다음 줄에 문장을 작성하면 앞 문장 끝에 세미콜론이 없어도 자바스크립트 &lt;strong&gt;엔진이 자동으로 세미콜론 추가&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트 엔진이 세미콜론을 생략한 줄이 다음 줄과 이어지고 있다고 판단하면?&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 세미콜론을 안붙이면
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 이렇게 해석될 수 있다
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="예악어"&gt;예악어&lt;/h3&gt;
&lt;p&gt;자바스크립트 문법을 규정짓기 위해 자바스크립트 언어 사양에서 사용하는 특수한 키워드는 식별자로 사용하지 않는 편이 좋다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Functions (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-02-functions-2/</link><pubDate>Fri, 26 May 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-02-functions-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="function-argument-labels-and-parameter-names"&gt;Function Argument Labels and Parameter Names&lt;/h3&gt;
&lt;p&gt;함수의 파라미터들은 각각 아규먼트 레이블(Argument Labels)과 파라미터 이름(Parameter Names)을 가지고 있다. 아규먼트 레이블은 함수를 호출할 때 인자와 함께 사용되며, 파라미터 이름은 함수를 구현할 때 사용한다. 기본적으로, 파라미터 이름이 인자 레이블이 된다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;someFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstParameterName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secondParameterName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// In the function body, firstParameterName and secondParameterName&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// refer to the argument values for the first and second parameters.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;someFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstParameterName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secondParameterName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;모든 파라미터들은 고유한 이름을 가져야 한다. 여러 개의 파라미터들이 같은 아규먼트 레이블을 가질 수도 있지만, 고유한 아규먼트 레이블을 사용하면 코드의 가독성이 증가한다.&lt;/p&gt;</description></item><item><title>dev-data-server-light. 2. Database CRUD</title><link>https://kelly-chui.github.io/posts/devbox-light-server-02-db-crud/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devbox-light-server-02-db-crud/</guid><description>&lt;p&gt;시스템 모듈을 만든 이후에, 아마도 가장 많이 쓰게 될 DB 모듈을 만들었다.&lt;/p&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/devbox-light-server-02-db-crud/image-001-optimized-image.webp"&gt;&lt;p&gt;당시에는 구조도 시스템 모듈과 동일하다고 생각했다. Router가 DB 인터페이스에 의존하고, 이를 구현한 구현체를 주입받는 방식으로 DIP와 DI를 동시에 적용했다.&lt;/p&gt;
&lt;p&gt;이 과정 중에서&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;naming bias 해결하기&lt;/li&gt;
&lt;li&gt;id 생성 책임 위치&lt;/li&gt;
&lt;li&gt;기존 아키텍처의 문제점 -&amp;gt; 서비스 레이어 도입해서 해결하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;에 대한 고민을 했다. 아래는 고민과 해결 과정이다.&lt;/p&gt;
&lt;h2 id="crud부터-만들기"&gt;CRUD부터 만들기&lt;/h2&gt;
&lt;p&gt;Mock Server라도 최소한 CRUD는 제공해야 한다. 처음부터 구현체를 만들 필요는 없으므로, 라우터와 인터페이스까지만 만들도록 프롬프트를 작성했다.&lt;/p&gt;</description></item><item><title>JavaScript Basics. 03. 자바스크립트 사용자 정의 자료형 활용</title><link>https://kelly-chui.github.io/posts/javascript-basics-03-custom-types/</link><pubDate>Sun, 01 Jun 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/javascript-basics-03-custom-types/</guid><description>&lt;h2 id="조건문"&gt;조건문&lt;/h2&gt;
&lt;h3 id="if-조건문"&gt;if 조건문&lt;/h3&gt;
&lt;p&gt;bool 표현식이 &lt;code&gt;true&lt;/code&gt;이면 문장을 실행, &lt;code&gt;false&lt;/code&gt;이면 문장을 무시&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;짝수입니다!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;홀수입니다!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;오전입니다.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;오후입니다.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="if-else-조건문"&gt;if else 조건문&lt;/h3&gt;
&lt;p&gt;조건이 합당하지 않은 경우의 표현식도 포함하는 조건문&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;짝수입니다!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;홀수입니다!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;오전입니다.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;오후입니다.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="중첩-조건문"&gt;중첩 조건문&lt;/h3&gt;
&lt;p&gt;조건문이 중첩된 형태&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;아침 먹을 시간입니다.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;점심 먹을 시간입니다.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;저녁 먹을 시간입니다.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="if-else-if-조건문"&gt;if else if 조건문&lt;/h3&gt;
&lt;p&gt;중복되지 않는 세 가지 이상의 조건을 구분할 때 사용&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Functions (3)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-03-functions-3/</link><pubDate>Sat, 27 May 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-03-functions-3/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="function-types"&gt;Function Types&lt;/h3&gt;
&lt;p&gt;모든 함수는 파라미터들의 타입들과 리턴 값의 타입으로 이루어진 특정한 &lt;em&gt;함수 타입&lt;/em&gt; 을 가지고 있다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;addTwoInts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;multiplyTwoInts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;위의 예시에서 두 함수는 두 개의 정수 값을 받아 정수 값을 리턴한다. 두 함수 모두 (Int, Int) -&amp;gt; Int 타입이며, 이는 &amp;ldquo;두 개의 Int 타입 파라미터를 받아 Int 타입을 리턴하는 함수&amp;rdquo; 라는 의미이다.&lt;/p&gt;
&lt;p&gt;파라미터와 리턴 값이 없는 함수의 예시이다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;printHelloWorld&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;hello, world&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이 함수의 타입은 () -&amp;gt; Void 이고 &amp;ldquo;파라미터가 없고 리턴값이 Void인 함수&amp;rdquo; 라는 의미를 가진다.&lt;/p&gt;</description></item><item><title>dev-data-server-light. 3. Database Implementation</title><link>https://kelly-chui.github.io/posts/devbox-light-server-03-db-implementation/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devbox-light-server-03-db-implementation/</guid><description>&lt;p&gt;DB 모듈의 인터페이스와 서비스 레이어를 만들었으므로, 실제 구현체를 만들어야 한다. 우선 인 메모리(In-Memory)로 가볍게 구현체를 만들었다.&lt;/p&gt;
&lt;h2 id="인-메모리-데이터-저장소"&gt;인 메모리 데이터 저장소&lt;/h2&gt;
&lt;p&gt;인 메모리 방식을 선택한 이유는 첫째로 처음부터 SQL이나 JSON 파일 기반 저장소를 이용해서 구현체를 만드는 것 보다는, 가장 단순한 구현체를 만들어서 DB 기능을 검증하고 빠르게 다음 기능을 만들기 위해서이다. 외부 의존성도 없고, 설정도 필요 없고, 테스트도 쉽다.&lt;/p&gt;
&lt;h2 id="구현하기"&gt;구현하기&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;RecordStore&lt;/code&gt; 인터페이스를 다시 보면 다음과 같다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RecordStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;StoredRecord&lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;StoredRecord&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="na"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;: &lt;span class="kt"&gt;RecordData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;StoredRecord&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;: &lt;span class="kt"&gt;RecordData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;구현체는 이 계약을 그대로 만족하면 된다. 중요한 것은 구현체는 Express를 몰라야하고 순수하게 TypeScript 코드로 짜여야 한다. HTTP 요청이나 라우팅 같은 것은 Router가 책임져야 한다.&lt;/p&gt;</description></item><item><title>JavaScript Basics. 04. 자바스크립트 함수</title><link>https://kelly-chui.github.io/posts/javascript-basics-04-functions/</link><pubDate>Mon, 02 Jun 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/javascript-basics-04-functions/</guid><description>&lt;h2 id="함수-생성-방법"&gt;함수 생성 방법&lt;/h2&gt;
&lt;h3 id="익명-함수"&gt;익명 함수&lt;/h3&gt;
&lt;p&gt;이름을 붙이지 않고 함수 생성&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;함수를 호출하면 함수 내부의 코드 덩어리가 모두 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;helloWorld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 함수 호출
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 함수 자체를 호출
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Hello, World!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// f () {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// console.log(&amp;#34;Hello, World!&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;// }&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="선언적-함수"&gt;선언적 함수&lt;/h3&gt;
&lt;p&gt;이름을 붙여 함수를 생성&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 함수 호출
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 함수 자체를 호출
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Hello, World!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// f () {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// console.log(&amp;#34;Hello, World!&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;// }&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="화살표-함수"&gt;화살표 함수&lt;/h3&gt;
&lt;p&gt;하나의 표현식을 리턴하는 함수를 만들 때는 중괄호 생략 가능&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Closures (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-04-closures-1/</link><pubDate>Sun, 28 May 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-04-closures-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="closures"&gt;Closures&lt;/h2&gt;
&lt;p&gt;클로저는 코드 내부에서 전달되고 사용할 수 있는 독립적인 코드 블록이다. 클로저는 C, Objective-C의 블록이나 다른 언어들의 lambda와 유사하다.&lt;/p&gt;
&lt;p&gt;클로저는 자신이 정의된 컨텍스트에 있는 상수나 변수의 참조를 캡처하고 저장할 수 있다. 이를 캡쳐한 상수나 변수에 대한 클로징 오버(closing over)라고 한다. 이 경우에 스위프트가 모든 메모리 관리를 해준다.&lt;/p&gt;
&lt;p&gt;함수(Function)는 클로저의 특수한 케이스이며, 클로저는 다음과 같은 세가지 형태가 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;어떠한 값도 캡처하지 않고, 이름이 있는 전역 함수&lt;/li&gt;
&lt;li&gt;자신을 둘러 싼 함수 내부의 값을 캡처하고, 이름이 있는 중첩 함수&lt;/li&gt;
&lt;li&gt;자신을 둘러싼 컨텍스트의 값을 캡처하고, 이름이 없는 클로저 표현식&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;일반적인 상황에서 다음과 같은 최적화를 통해서 클로저를 짧고 난잡하지 않게 만들 수 있다.&lt;/p&gt;</description></item><item><title>dev-data-server-light. 4. File Storage</title><link>https://kelly-chui.github.io/posts/devbox-light-server-04-file-storage/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devbox-light-server-04-file-storage/</guid><description>&lt;p&gt;DB 모듈을 마무리하고, 다음으로 구현한 것은 File Storage 모듈이다. DB 모듈과 크게 다르지 않을 것이라고 생각했다. 우선 CRUD 비슷한 메소드를 처리하고, 구조는 DB 모듈에서 충분히 다듬었기 때문이다. 하지만 실제로는 DB보다 인터페이스를 어떻게 정의할 것인지에 대한 고민이 더 많았다.&lt;/p&gt;
&lt;h2 id="인터페이스-만들기"&gt;인터페이스 만들기&lt;/h2&gt;
&lt;p&gt;처음 작성한 프롬프트는 다음과 같다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Create the initial FileStorage contract in src/storage.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Requirements:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Define a FileStorage interface.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- The interface is responsible only for storing and retrieving binary file contents.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- It should not manage file metadata, ids, ownership, permissions, or database records.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Use Buffer for file contents.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Include only the minimal operations required for file storage:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - save(path: string, content: Buffer): Promise&amp;lt;void&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - read(path: string): Promise&amp;lt;Buffer&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - delete(path: string): Promise&amp;lt;void&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - exists(path: string): Promise&amp;lt;boolean&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Add minimal domain errors if necessary.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;For this step, create only the contract and related types.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Do not implement any storage implementation yet.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;로컬 파일 시스템을 의식하고 있어서 의심 없이 &lt;code&gt;path&lt;/code&gt;라는 이름을 사용했는데, 이 때문에 인터페이스가 구현체를 닮아버리게 되었다. &lt;code&gt;path&lt;/code&gt;는 과연 인터페이스를 대표할 수 있는 네이밍인가?&lt;/p&gt;</description></item><item><title>JavaScript Basics. 05. 자바스크립트 내장 객체</title><link>https://kelly-chui.github.io/posts/javascript-basics-05-built-in-objects/</link><pubDate>Tue, 03 Jun 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/javascript-basics-05-built-in-objects/</guid><description>&lt;h2 id="객체"&gt;객체&lt;/h2&gt;
&lt;h3 id="배열"&gt;배열&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;기본 형태&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;사과&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;바나나&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;망고&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;딸기&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// &amp;#39;사과&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;/ &amp;#39;바나나&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;배열은 요소에 접근할 때 인덱스를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="객체-기본"&gt;객체 기본&lt;/h3&gt;
&lt;p&gt;배열과 다르게 키를 통해서 접근하게 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;객체 선언&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;제품명&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;7D 건조 망고&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;유형&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;당절임&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;원산지&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;필리핀&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="err"&gt;// {제품명: &amp;#39;7D 건조 망고&amp;#39;, 유형: &amp;#39;당절임&amp;#39;, 원산지: &amp;#39;필리핀&amp;#39;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;객체 접근&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;제품명&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// &amp;#39;7D 건조 망고&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;유형&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// 당절임
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;원산지&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// 필리핀
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;제품명&lt;/span&gt; &lt;span class="c1"&gt;// &amp;#39;7D 건조 망고&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;유형&lt;/span&gt; &lt;span class="c1"&gt;// &amp;#39;당절임&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;원산지&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;/ &amp;#39;필리핀&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="객체와-반복문"&gt;객체와 반복문&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;for in&lt;/code&gt; 반복문을 사용해 객체에 반복문을 적용&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;바나나&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="메소드"&gt;메소드&lt;/h3&gt;
&lt;p&gt;객체의 속성 중 자료형이 함수인 속성&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Closures (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-05-closures-2/</link><pubDate>Mon, 29 May 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-05-closures-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="capturing-values"&gt;Capturing Values&lt;/h3&gt;
&lt;p&gt;클로저는 자신이 정의된 컨텍스트에서 상수나 변수를 캡처할 수 있다. 캡처한 상수나 변수가 정의되어있는 범위가 더 이상 존재하지 않아도 클로저의 본문에서 그 변수나 상수를 참조하고 수정할 수 있다.&lt;/p&gt;
&lt;p&gt;스위프트에서 값을 캡쳐하는 가장 간단한 형태의 클로저는 중첩 함수이다. 중첩 함수는 외부 함수(둘러싸고 있는 함수)내부에 선언된 변수나 상수, 그리고 아규먼트를 캡처할 수 있다.&lt;/p&gt;
&lt;p&gt;다음 예시는 incrementer라는 중첩 함수를 가지고 있는 makeIncrementer 함수의 예시이다. incrementer() 함수는 runningTotal과 amount 두개의 변수를 캡처한다. 이 값을 캡처한 다음에 incrementer는 호출될 때마다 amount만큼 runningTotal을 증가시키는 클로저가 되고, makeIncrementer에 의해 리턴 된다.&lt;/p&gt;</description></item><item><title>devbox-light-server (5). Auth</title><link>https://kelly-chui.github.io/posts/devbox-light-server-05-auth/</link><pubDate>Sat, 08 Aug 2026 16:27:42 +0900</pubDate><guid>https://kelly-chui.github.io/posts/devbox-light-server-05-auth/</guid><description>&lt;h2 id="auth-설계하기"&gt;Auth 설계하기&lt;/h2&gt;
&lt;p&gt;개발용 목서버에서 실제 서비스 수준의 인증 시스템을 구현하지 않고도, 클라이언트에서 사용하는 로그인과 인증 흐름을 테스트할 수 있도록 Auth 기능을 추가했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;로그인 요청을 보낸다.&lt;/li&gt;
&lt;li&gt;access token을 발급받는다.&lt;/li&gt;
&lt;li&gt;Authorization 헤더에 token을 실어 요청한다.&lt;/li&gt;
&lt;li&gt;현재 로그인된 유저를 확인한다.&lt;/li&gt;
&lt;li&gt;로그아웃으로 token을 무효화한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;JWT, 비밀번호, OAuth, Refresh Token, 보안 같은 기능은 의도적으로 제외했다. 프로덕션 인증 로직을 구현하려는 글이 아니라, 개발용 Mock 서버에서 클라이언트의 로그인, 인증, 로그아웃 흐름을 확인하는 것이 목적이기 때문이다.&lt;/p&gt;
&lt;h3 id="최소한의-auth-구조"&gt;최소한의 Auth 구조&lt;/h3&gt;
&lt;p&gt;첫 단계에서는 &lt;code&gt;DatabaseService&lt;/code&gt;를 그대로 사용했다. &lt;code&gt;users&lt;/code&gt; 컬렉션을 만들어서 그 레코드 ID를 유저 ID로 사용해 로그인할 수 있도록 했다.&lt;/p&gt;</description></item><item><title>JavaScript Basics. 06. 브라우저 및 문서 객체</title><link>https://kelly-chui.github.io/posts/javascript-basics-06-browser-and-document-object/</link><pubDate>Fri, 06 Jun 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/javascript-basics-06-browser-and-document-object/</guid><description>&lt;h2 id="브라우저-객체-모델"&gt;브라우저 객체 모델&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;웹 브라우저와 관련된 객체의 집합&lt;/li&gt;
&lt;li&gt;대표적인 브라우저 객체 모델
&lt;ul&gt;
&lt;li&gt;window, location, navigator, history, screen, document…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;img alt="Screenshot 2025-06-06 at 12.30.02\u202fPM.png" loading="lazy" src="https://kelly-chui.github.io/posts/javascript-basics-06-browser-and-document-object/image-001-optimized-image.webp"&gt;&lt;h3 id="window-객체"&gt;&lt;code&gt;window&lt;/code&gt; 객체&lt;/h3&gt;
&lt;p&gt;브라우저 기반 자바스크립트의 최상위 객체&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;alert()&lt;/code&gt;, &lt;code&gt;prompt()&lt;/code&gt;함수 모두 window 객체의 메소드&lt;/li&gt;
&lt;li&gt;&lt;code&gt;open(URL, name, features, replace)&lt;/code&gt;메소드를 통해서 window 객체 생성 가능
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;URL&lt;/code&gt;: 열고자 하는 HTML 페이지의 URL&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;: 윈도우 간 통신하는 데 사용하는 윈도우 이름&lt;/li&gt;
&lt;li&gt;&lt;code&gt;features&lt;/code&gt;: 윈도우 출력 모양을 지정하는 옵션
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;height&lt;/code&gt;: 새 윈도우의 높이&lt;/li&gt;
&lt;li&gt;&lt;code&gt;width&lt;/code&gt;: 새 윈도우의 너비&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;replace&lt;/code&gt;: URL이 새로운 것인가, 기존의 문서를 대체할 것인가를 나타내는 부울리언 값&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;window 객체는 자신의 형태와 위치를 변경할 수 있도록 메소드 제공
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;moveBy(x, y)&lt;/code&gt;: 윈도우의 위치를 상대적으로 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;moveTo(x, y)&lt;/code&gt;: 윈도우의 위치를 절대적으로 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resizeBy(x, y)&lt;/code&gt;: 윈도우의 크기를 상대적으로 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resizeTo(x, y)&lt;/code&gt;: 윈도우의 크기를 절대적으로 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;close()&lt;/code&gt;: 윈도우를 닫습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="screen-객체"&gt;&lt;code&gt;screen&lt;/code&gt; 객체&lt;/h3&gt;
&lt;p&gt;웹 브라우저의 화면이 아닌 운영체제 화면의 속성을 가치는 객체&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Closures (3)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-06-closures-3/</link><pubDate>Mon, 29 May 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-06-closures-3/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="escaping-closures"&gt;Escaping Closures&lt;/h3&gt;
&lt;p&gt;클로저가 함수의 아규먼트로 전달이 됐는데, 함수가 리턴된 이후에 클로저가 호출되면 이를 함수를 탈출(escape)했다고 한다. 클로저를 파라미터로 받는 함수를 작성할 때, 파라미터의 타입 뒤에 @escaping을 작성해서 그 클로저가 탈출 가능하다는 것을 나타낼 수 있다.&lt;/p&gt;
&lt;p&gt;클로저가 함수를 탈출하는 한가지 방법은 외부 변수에 저장되는 것이다. 예를 들어 많은 비동기 작업을 시작 하는 함수들은 클로저 아규먼트를 컴플리션 핸들러(completion handler)로 사용한다. 함수는 비동기 작업을 시작하고 리턴되지만, 클로저는 비동기 작업이 끝날때까지 호출되지 않는다. 따라서 나중에 호출되기 위해 탈출이 필요하다. 예를 들면:&lt;/p&gt;</description></item><item><title>devbox-light-server (6). Json 영속성</title><link>https://kelly-chui.github.io/posts/devbox-light-server-06-persistence/</link><pubDate>Mon, 10 Aug 2026 14:14:08 +0900</pubDate><guid>https://kelly-chui.github.io/posts/devbox-light-server-06-persistence/</guid><description>&lt;p&gt;MVP의 마지막 기능으로 서버를 재시작해도 데이터가 사라지지 않도록 영속성을 추가했다.&lt;/p&gt;
&lt;p&gt;이번 포스트에서는 devbox-light-server에 DB, Storage, Token Storage에서 영속성을 가진 저장 방식을 구현해서 in-memory 대신 사용할 수 있도록 만든다.&lt;/p&gt;
&lt;p&gt;프로토타입이었던 dev-data-server에서는 시작부터 SQL 의존성을 가지고 시작했지만, devbox-light-server는 아주 얇은 in-memory 구현만 가지고 있다.&lt;/p&gt;
&lt;p&gt;MVP 마지막에 영속성을 넣는 이유는 서버를 구현할 때, 특정 구현에 휘둘리지 않기 위해서다. dev-data-server에서 SQL을 빠르게 들여와서, 결국 프로젝트가 SQL 래퍼가 된 느낌이 있다.&lt;/p&gt;
&lt;p&gt;당연히 Service 레이어는 Repository의 인터페이스에만 의존하므로 어떤 방법을 쓰든 단순히 구현체만 갈아 끼우고, 의존성 생성, 연결 부분을 제외하고는 어떠한 코드에도 변화가 없어야 한다.&lt;/p&gt;</description></item><item><title>JavaScript Basics. 07. 자바스크립트 예외처리 및 이벤트 처리</title><link>https://kelly-chui.github.io/posts/javascript-basics-07-errors-and-events/</link><pubDate>Mon, 09 Jun 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/javascript-basics-07-errors-and-events/</guid><description>&lt;h2 id="예외-처리"&gt;예외 처리&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;예외 → 실행에 문제가 발생하면 자동 중단됨&lt;/li&gt;
&lt;li&gt;예외 처리 → 오류에 대처할 수 있게 하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="예외와-기본-예외-처리"&gt;예외와 기본 예외 처리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;TypeError 기본 예외 처리
&lt;ul&gt;
&lt;li&gt;사전에 해당 데이터가 &lt;code&gt;undefined&lt;/code&gt;인지 조건문으로 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="고급-예외-처리"&gt;고급 예외 처리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;try&lt;/code&gt;, &lt;code&gt;catch&lt;/code&gt;, &lt;code&gt;finally&lt;/code&gt; 구문&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 예외 발생
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 예외 처리
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 무조건 실행
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;catch&lt;/code&gt;구문, &lt;code&gt;finally&lt;/code&gt;구문 생략 가능&lt;/p&gt;
&lt;h3 id="예외-객체"&gt;예외 객체&lt;/h3&gt;
&lt;p&gt;예외가 발생하면 어떤 예외가 발생했는지 정보를 전달함, &lt;code&gt;catch&lt;/code&gt; 구문의 괄호 안의 변수&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Enumerations (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-07-enumerations-1/</link><pubDate>Tue, 30 May 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-07-enumerations-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="enumerations"&gt;Enumerations&lt;/h2&gt;
&lt;p&gt;열거형은 연관된 값들을 공통된 타입으로 묶은 그룹이며, 타입 안전(type-safe)하게 사용하게 해준다.&lt;/p&gt;
&lt;p&gt;스위프트의 열거형은 유연하다. 각 케이스에 값을 할당하지 않아도 되며, 스트링, 캐릭터, 정수, 실수등을 각 열거형 케이스에 제공할 수 있다.&lt;/p&gt;
&lt;p&gt;스위프트에서 열거형은 그 자체로 일급 객체다. 컴퓨티드 프로퍼티(computed properties)나 인스턴스 메소드(instance method)와 같은, 기존에는 클래스에서만 사용할 수 있었던 많은 기능들을 가져왔고, 초기 케이스 값을 설정하는 이니셜라이저도 정의할 수 있으며, 프로토콜을 상속 받을 수도 있다.&lt;/p&gt;
&lt;h3 id="enumeration-syntax"&gt;Enumeration Syntax&lt;/h3&gt;
&lt;p&gt;열거형을 작성할 때는 enum 키워드를 사용하여 한 쌍의 중괄호 안에 작성한다.&lt;/p&gt;</description></item><item><title>devbox-light-server (7). Dockerizing, MVP 마무리</title><link>https://kelly-chui.github.io/posts/devbox-light-server-07-dockerizing/</link><pubDate>Mon, 10 Aug 2026 17:42:56 +0900</pubDate><guid>https://kelly-chui.github.io/posts/devbox-light-server-07-dockerizing/</guid><description>&lt;h2 id="도커라이징"&gt;도커라이징&lt;/h2&gt;
&lt;p&gt;영속성까지 추가했으므로 이제 서버를 하나의 실행 단위로 묶을 차례다.&lt;/p&gt;
&lt;p&gt;지금까지는 로컬에서 Node.js를 설치하고, 의존성을 설치하고, TypeScript를 빌드한 다음 서버를 실행했다. 혼자 개발할 때는 큰 문제가 없지만, 이 서버를 다른 환경에서 실행하려면 매번 같은 준비 과정을 반복해야 한다.&lt;/p&gt;
&lt;p&gt;도커라이징은 이 과정을 이미지 안에 고정하는 작업이다. 이 프로젝트에서는 Express 서버를 빌드하고, 실행에 필요한 파일만 담은 뒤, &lt;code&gt;.data&lt;/code&gt; 디렉터리를 기준으로 데이터를 유지할 수 있도록 구성했다.&lt;/p&gt;
&lt;h3 id="dockerfile-작성하기"&gt;Dockerfile 작성하기&lt;/h3&gt;
&lt;p&gt;Dockerfile은 두 단계로 나눴다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:24-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; package.json package-lock.json ./&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; npm ci&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; tsconfig.json tsconfig.build.json ./&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; src ./src&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; npm run build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;첫 번째 &lt;code&gt;build&lt;/code&gt; 단계에서는 개발 의존성을 포함해서 설치하고 TypeScript를 빌드한다. &lt;code&gt;npm run build&lt;/code&gt;를 실행하면 &lt;code&gt;dist&lt;/code&gt; 디렉터리가 만들어진다.&lt;/p&gt;</description></item><item><title>JavaScript Basics. 08. 자바스크립트 API 활용 및 그림판 프로그램 만들기</title><link>https://kelly-chui.github.io/posts/javascript-basics-08-api-and-paint-app/</link><pubDate>Mon, 09 Jun 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/javascript-basics-08-api-and-paint-app/</guid><description>&lt;h2 id="api-활용"&gt;API 활용&lt;/h2&gt;
&lt;h3 id="클라이언트-측-고유-기술-요소"&gt;클라이언트 측 고유 기술 요소&lt;/h3&gt;
&lt;p&gt;웹브라우저에서 동작하는 자바스크립트를 클라이언트 측 자바스크립트라 함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클라이언트 측 자바스크립트 구성
&lt;ul&gt;
&lt;li&gt;ECMAScript가 규정한 코어 언어와 웹 브라우저의 API(Application Program Interface)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;웹 브라우저의 주요 API
&lt;ul&gt;
&lt;li&gt;window 인터페이스
&lt;ul&gt;
&lt;li&gt;자바스크립트로 브라우저 및 창을 제어하는 기능 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DOM
&lt;ul&gt;
&lt;li&gt;자바스크립트 HTML 문서의 요소를 제어하는 기능 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;XMLHttpRequest
&lt;ul&gt;
&lt;li&gt;서버와 비동기로 통신하는 기능 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="서버-측-자바스크립트의-고유-기술-요소"&gt;서버 측 자바스크립트의 고유 기술 요소&lt;/h3&gt;
&lt;p&gt;웹 서버에서 동작하는 자바스크립트&lt;/p&gt;
&lt;p&gt;웹 서버를 구현하는데 Perl, PHP, Python, Ruby 등의 프로그래밍 언어를 이용&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Enumerations (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-08-enumerations-2/</link><pubDate>Wed, 31 May 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-08-enumerations-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="raw-values"&gt;Raw Values&lt;/h3&gt;
&lt;p&gt;Associated Values 섹션에서는 열거형의 케이스가 다른 타입의 연관값을 저장하는 방법을 보여줬다. 연관 값 대신, 열거형 케이스들은 같은 타입의 디폴트 값-원시 값(raw values)-을 미리 가지고 있을 수도 있다.&lt;/p&gt;
&lt;p&gt;다음은 ASCII값을 원시값으로 사용하는 예시이다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;ASCIIControlCharacter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Character&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;tab&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;lineFeed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;carriageReturn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;열거형 ASCIIControlCharacter의 원시 값은 Character 타입으로 정의되어 있고, ASCII 제어 문자로 설정되어 있다.&lt;/p&gt;
&lt;p&gt;원시 값은 어떠한 자료형이든 될 수 있으며, 각 원시 값은 열거형을 선언할 때 고유해야 한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Structures And Classes (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-09-structures-and-classes-1/</link><pubDate>Thu, 01 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-09-structures-and-classes-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="structures-and-classes"&gt;Structures and Classes&lt;/h2&gt;
&lt;p&gt;스트럭처와 클래스는 범용성 있는 유연한 구조이다. 상수, 변수, 함수를 작성하는 것 처럼 프로퍼티와 메소드를 작성하여 스트럭처와 클래스에 기능을 부여할 수 있다.&lt;/p&gt;
&lt;h3 id="comparing-structures-and-classes"&gt;Comparing Structures and Classes&lt;/h3&gt;
&lt;p&gt;스트럭처와 클래스는 많은 공통점을 가지고 있다. 둘 다 다음과 행동을 할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;값을 저장하기 위한 프로퍼티 정의&lt;/li&gt;
&lt;li&gt;기능(functionality)을 위한 메소드 정의&lt;/li&gt;
&lt;li&gt;서브스크립트 구문을 통하여 값에 접근할 수 있게 하는 서브스크립트 정의&lt;/li&gt;
&lt;li&gt;초기 값을 설정하기 위한 이니셜라이저 정의&lt;/li&gt;
&lt;li&gt;기본 구현보다 확장된 기능을 위한 확장성&lt;/li&gt;
&lt;li&gt;특정 종류에 대한 표준 기능을 제공하는 프로토콜 준수&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;클래스는 스트럭처가 없는 추가 기능들이 있다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Structures And Classes (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-10-structures-and-classes-2/</link><pubDate>Thu, 01 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-10-structures-and-classes-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="structures-and-enumerations-are-value-types"&gt;Structures and Enumerations Are Value Types&lt;/h3&gt;
&lt;p&gt;값 타입은 변수나 상수에 할당되거나 함수에 전달될 때, 값이 복사되는 타입이다. 스위프트의 정수, 실수, 부울, 문자열, 배열, 딕셔너리와 같은 기본 타입들은 모두 값 타입이고 실제로 스트럭처로 구현되어있다.&lt;/p&gt;
&lt;p&gt;스위프트에서 모든 스트럭처와 열거형과 그 값 타입 프로퍼티들은 코드 안에서 복사되어 전달된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
배열, 딕셔너리, 문자열과 같은 콜렉션은 전달(혹은 할당)될 때, 즉시 복사되지 않고 수정 작업이 이루어졌을때 복사를 하는 최적화가 되어있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;hd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Resolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;cinema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hd&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이 예시는 &lt;a href="https://junmusu.tistory.com/79" title="앞선 예시"&gt;앞선 예시&lt;/a&gt;에 나온 스트럭처 Resolution의 상수 인스턴스 hd를 선언하고 프로퍼티를 초기화한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Properties (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-11-properties-1/</link><pubDate>Fri, 02 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-11-properties-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="properties"&gt;Properties&lt;/h2&gt;
&lt;p&gt;프로퍼티는 특정 클래스, 스트럭처, 열거형에 값을 연관시킨다. 저장 프로퍼티는 변수나 상수의 값을 인스턴스의 일부로 저장하고, 컴퓨티드 프로퍼티는 값을 저장하기보다는 계산한다. 컴퓨티드 프로퍼티와 다르게, 저장 프로퍼티는 열거형에서 쓸 수 없다.&lt;/p&gt;
&lt;p&gt;저장 프로퍼티와 컴퓨티드 프로퍼티는 인스턴스와 연결되는 반면, 다르게 타입 프로퍼티는 타입 그 자체에 연결된다.&lt;/p&gt;
&lt;p&gt;추가로 프로퍼티 값의 변화를 모니터링 하고, 커스텀 액션으로 반응할 수 있는 프로퍼티 옵저버를 사용할 수도 있다. 프로퍼티 옵저버는 저장 프로퍼티에서 직접 정의하여 추가할 수 있으며, 서브클래스한태 상속된다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Properties (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-12-properties-2/</link><pubDate>Fri, 02 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-12-properties-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="property-observers"&gt;Property Observers&lt;/h3&gt;
&lt;p&gt;프로퍼티 옵저버(Property Observer)는 프로퍼티 값의 변화를 관측하고 반응한다. 프로퍼티 옵저버는 새로 설정된 값이 이전의 값과 같더라도, 프로퍼티의 값이 설정 될 때 마다 반응한다.&lt;/p&gt;
&lt;p&gt;프로퍼티 옵저버는 다음의 위치들에 추가할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;직접 정의한 저장 프로퍼티&lt;/li&gt;
&lt;li&gt;상속받은 저장 프로퍼티&lt;/li&gt;
&lt;li&gt;상속받은 컴퓨티드 프로퍼티&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;서브클래스에서 상속받은 프로퍼티를 오버라이드 하여 프로퍼티 옵저버를 추가할 수 있다. 직접 정의한 컴퓨티드 프로퍼티의 경우에는 setter를 사용하면 프로퍼티 옵저버를 사용하지 않고도 값의 변화를 관측하고 응답할 수 있다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Properties (3)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-13-properties-3/</link><pubDate>Mon, 05 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-13-properties-3/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="type-properties"&gt;Type Properties&lt;/h3&gt;
&lt;p&gt;인스턴스 프로퍼티는 특정 타입의 인스턴스에 속하는 프로퍼티이다. 새로운 인스턴스를 만들때 마다, 인스턴스는 다른 인스턴스와 구분되는 프로퍼티 집합이 생긴다.&lt;/p&gt;
&lt;p&gt;인스턴스가 아닌 타입 자체에 속하는 프로퍼티도 있다. 이러한 프로퍼티를 타입 프로퍼티라고 하며, 얼마나 많은 그 타입의 인스턴스를 생성했느냐에 관련없이 단 하나만 존재한다.&lt;/p&gt;
&lt;p&gt;타입 프로퍼티는 특정 타입의 모든 인스턴스들이 보편적으로 사용할 값을 정의할 때 유용하다. (C의 static이랑 유사하다)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
타입 그 자체는 이니셜라이저가 없기 때문에, 인스턴스 프로퍼티와 다르게 타입 프로퍼티는 저장 프로퍼티일 때 반드시 디폴트 값을 줘야한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Methods (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-14-methods-1/</link><pubDate>Tue, 06 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-14-methods-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="methods"&gt;Methods&lt;/h2&gt;
&lt;p&gt;메소드는 특정 타입에 연관된 함수이다. 클래스, 스트럭처, 열거형은 인스턴스로 작업하기 위한 기능이나 작업은 캡슐화 하기 위해 인스턴스 메소드를 선언할 수 있고, 타입 그 자체에 연관된 타입 메소드도 선언할 수 있다.&lt;/p&gt;
&lt;h3 id="instance-methods"&gt;Instance Methods&lt;/h3&gt;
&lt;p&gt;인스턴스 메소드는 특정 클래스, 스트럭처, 열거형에 속하는 함수이다. 인스턴스 프로퍼티에 접근, 수정하는 방법을 제공하거나 인스턴스의 목적에 맞는 기능을 제공하여 인스턴스가 기능을 가지게 한다. 인스턴스 메소드는 함수와 똑같은 구문을 사용한다.&lt;/p&gt;
&lt;p&gt;인스턴스 메소드는 자신이 선언되어 있는 타입의 인스턴스 메소드나 타입에 암시적으로 접근 권한을 가지고 있다. 인스턴스 메소드는 자신이 선언되어 있는 타입의 인스턴스에서만 호출할 수 있으며, 밖에서 독립적으로 사용할 수 없다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Methods (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-15-methods-2/</link><pubDate>Tue, 06 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-15-methods-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="type-methods"&gt;Type Methods&lt;/h3&gt;
&lt;p&gt;이전에 설명했던 인스턴스 메소드는 특정 타입의 인스턴스가 호출한다. 타입 그 자체에 연관된 메소드는 타입 메소드라고하며, func 키워드 앞에 static 키워드를 작성하여 타입 메소드임을 나타낸다. 클래스는 서브클래스가 타입 메소드를 오버라이드 할 수 있도록 class 키워드를 대신 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;타입 메소드는 인스턴스 메소드처럼 닷 구문을 통해서 호출되지만, 인스턴스가 아닌 타입을 통해서 호출해야 한다. 다음은 클래스 SomeClass가 타입 메소드를 호출하는 예시이다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;func&lt;/span&gt; &lt;span class="n"&gt;someTypeMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// type method implementation goes here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;SomeClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;someTypeMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;타입 메소드의 본문에서 self 프로퍼티는 타입의 인스턴스가 아닌 타입 자체를 뜻한다. 따라서 인스턴스 메소드처럼 이를 통해 타입 프로퍼티와 타입 메소드 프로퍼티를 구분할 수 있다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Subscripts (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-16-subscripts-1/</link><pubDate>Wed, 07 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-16-subscripts-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="subscripts"&gt;Subscripts&lt;/h2&gt;
&lt;p&gt;클래스, 스트럭처, 열거형은 컬렉션, 리스트, 시퀀스에서 요소에 접근할 때 사용하는 서브스크립트를 정의할 수 있다. 서브스크립트는 별도의 메소드 없이 인덱스로 값을 설정하거나 검색할 수 있다. 예를 들면, 배열 인스턴스에서 요소에 접근할 때, someArray[index]와 같이 사용하고, 딕셔너리에서 someDictionary[key]처럼 사용하는 것이 서브스크립트다.&lt;/p&gt;
&lt;p&gt;하나의 타입에 여러 개의 서브스크립트를 정의할 수 있다. 이러한 경우에는 서브스크립트에 전달되는 인덱스의 타입에 따라 적절한 서브스크립트가 선택된다. 또한 서브스크립트는 단일 차원으로 한정되지 않고, 다수의 파라미터를 받는 서브스크립트를 정의할 수 있다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Subscripts (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-17-subscripts-2/</link><pubDate>Wed, 07 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-17-subscripts-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="subscript-options"&gt;Subscript Options&lt;/h3&gt;
&lt;p&gt;서브스크립트는 파라미터의 수와 타입에 제한없이 받을 수 있으며, 리턴 값도 타입에 제한이 없다.&lt;/p&gt;
&lt;p&gt;함수처럼 서브스크립트는 가변 파라미터를 받고, 디폴트 값을 설정해줄 수 있다. 그러나 함수와 다르게 in-out 파라미터는 사용할 수 없다.&lt;/p&gt;
&lt;p&gt;클래스나 스트럭처는 필요한 수만큼 서브스크립트를 제공할 수 있고, 대괄호 안에 전달된 값의 타입에 따라서 적합한 서브스크립트를 추론한다. 서브스크립트를 여러개 정의하는 것을 &lt;em&gt;서브스크립트 오버로딩&lt;/em&gt; 이라고도 한다.&lt;/p&gt;
&lt;p&gt;서브스크립트는 하나의 파라미터를 받는 것이 일반적이지만, 필요한 경우 여러개의 파라미터를 받도록 정의할 수도 있다. 아래의 예시는 Double 타입 2차원 행렬을 나타내는 Matrix 스트럭처를 정의한다. Matrix 스트럭처의 서브스크립트는 두 개의 정수 파라미터를 받는다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Inheritance (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-18-inheritance-1/</link><pubDate>Thu, 08 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-18-inheritance-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="inheritance"&gt;Inheritance&lt;/h2&gt;
&lt;p&gt;클래스는 메소드, 프로퍼티등 클래스의 특성들을 다른 클래스에서 상속 받을 수 있다. 한 클래스가 다른 클래스로부터 상속을 받았을 상속 받은 클래스를 &lt;em&gt;서브클래스&lt;/em&gt; 라고 하고, 상속 해준 클래스를 &lt;em&gt;슈퍼클래스&lt;/em&gt; 라고 한다. 상속은 클래스가 다른 타입들과 구별되는 중요한 특성이다.&lt;/p&gt;
&lt;p&gt;스위프트에서 서브클래스는 슈퍼클래스의 메소드, 프로퍼티, 서브스크립트를 접근하고, 호출할 수 있다. 그리고 이러한 메소드, 프로퍼티, 서브스크립트를 오버라이딩 하여 자신에 적합하게 동작을 수정할 수도 있다. 스위프트는 오버라이드한 정의가 슈퍼 클래스의 정의에 매치되는지 확인함으로써 오버라이드가 정확하게 되도록 도와준다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Inheritance (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-19-inheritance-2/</link><pubDate>Thu, 08 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-19-inheritance-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="overriding"&gt;Overriding&lt;/h3&gt;
&lt;p&gt;서브클래스는 상속 받을 수 있는 인스턴스 메소드, 타입 메소드, 인스턴스 프로퍼티, 타입 프로퍼티, 서브스크립트에 자신만의 커스텀 구현을 할 수 있다. 이것을 &lt;em&gt;오버라이딩&lt;/em&gt; 이라고 한다.&lt;/p&gt;
&lt;p&gt;상속될 수 있는 특성을 오버라이드 하려면, 오버라이드를 하고 있는 정의 앞에 override 키워드를 붙인다. 이렇게 하면 실수로 중복 정의를 한 것이 아닌 오버라이드 했다는 것을 명확하게 보여줄 수 있다. 실수로 오버라이드 하는 것은 예측하지 못한 결과를 가져온다. 따라서 override 키워드 없이 오버라이드를 하면 컴파일할 때 에러로 검출된다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Initialization (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-20-initialization-1/</link><pubDate>Tue, 13 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-20-initialization-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="initialization"&gt;Initialization&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;이니셜라이제이션(Initialization)&lt;/em&gt; 은 클래스, 스트럭처, 열거형의 인스턴스를 사용하기 위해 준비하는 프로세스다. 이 프로세스는 인스턴스의 각 저장 프로퍼티에 초기 값을 설정하고, 인스턴스가 사용될 준비가 되기 전에 필요한 초기 설정을 하는 작업이 포함된다.&lt;/p&gt;
&lt;p&gt;이니셜라이제이션은 특정 타입의 새로운 인스턴스를 만들기 위해 호출하는 특별한 메소드인 이니셜라이저를 정의함으로써 구현할 수 있다. Objective-C의 이니셜라이저와 다르게 스위프트의 이니셜라이저는 값을 리턴하지 않는다. 이니셜라이저의 가장 중요한 역할은 새 인스턴스가 처음으로 사용되기 전에 정확하게 초기화 되는 것을 보장하는 것이다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Initialization (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-21-initialization-2/</link><pubDate>Wed, 14 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-21-initialization-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="default-initializers"&gt;Default Initializers&lt;/h3&gt;
&lt;p&gt;스위프트는 모든 프로퍼티에 디폴트 값을 제공하며, 이니셜라이저를 하나도 가지고 있지 않은 클래스나 스트럭처에게 &lt;em&gt;디폴트 이니셜라이저&lt;/em&gt; 를 제공한다. 디폴트 이니셜라이저는 모든 프로퍼티가 디폴트 값으로 설정된 새로운 인스턴스를 만든다.&lt;/p&gt;
&lt;p&gt;이 예시는 쇼핑 리스트에 있는 품목의 이름, 수량, 구매 여부를 캡슐화한 ShoppingListItem 클래스를 정의한다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShoppingListItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;quantity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;purchased&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ShoppingListItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;ShoppingListItem 클래스의 모든 프로퍼티가 디폴트 값을 가지고 있고, 슈퍼클래스가 없는 베이스 클래스이기 때문에, ShoppingListItem 클래스는 자동적으로 디폴트 이니셜라이저를 가지게 된다.(name 프로퍼티는 옵셔널 프로퍼티이기 때문에 디폴트 값을 주지 않아도 자동적으로 nil이 자동적으로 디폴트 값이 된다) 위의 예시에서 디폴트 이니셜라이저를 이용해 ShoppingListItem의 새 인스턴스를 만든다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Initialization (3)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-22-initialization-3/</link><pubDate>Fri, 16 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-22-initialization-3/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h4 id="initializer-inheritance-and-overriding"&gt;Initializer Inheritance and Overriding&lt;/h4&gt;
&lt;p&gt;Objective-C의 서브클래스와 다르게 스위프트의 서브클래스는 기본적으로 슈퍼클래스의 이니셜라이저를 상속받지 않는다. 이러한 스위프트의 접근방식은 슈퍼클래스에서 상속받은 간단한 이니셜라이저가 서브클래스의 완전히 초기화 되지 않은 새 인스턴스를 만드는 것을 방지한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
슈퍼클래스의 이니셜라이저는 특정 상황에서는 상속되지만, 안전하고 적합할 때만 상속된다. 자세한 내용은 아래의 Automatic Initializer Inheritance에 나온다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;서브클래스가 하나 이상의 슈퍼클래스와 동일한 이니셜라이저를 가지고 있으려면, 서브클래스 내부에 직접 구현하여 제공할 수 있다.&lt;/p&gt;
&lt;p&gt;슈퍼클래스의 데지그네이티드 이니셜라이저와 일치하는 이니셜라이저를 서브클래스에 작성할 때, 그 데지그네이티드 이니셜라이저의 오버라이드를 효과적으로 제공할 수 있다. 그러므로 반드시 서브클래스의 이니셜라이저의 정의 앞에 override를 작성해야 한다. 자동적으로 제공된 디폴트 이니셜라이저라도 마찬가지다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Initialization (4)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-23-initialization-4/</link><pubDate>Sat, 17 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-23-initialization-4/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="failable-initializers"&gt;Failable Initializers&lt;/h3&gt;
&lt;p&gt;이니셜라이제이션이 실패할 수 있는 클래스, 스트럭처, 열거형을 정의하는 것이 유용하게 쓰일 때가 있다. 유효하지 않은 파라미터 값, 필요한 외부 리소스의 부재, 이니셜라이제이션이 실패해야하는 조건 등으로 이러한 실패를 트리거할 수 있다.&lt;/p&gt;
&lt;p&gt;실패할 수 있는 이니셜라이제이션 조건을 다루기 위해서는, 하나 이상의 실패 가능한(failable) 이니셜라이저를 클래스, 스트럭처, 열거형의 정의의 한 파트로 정의한다. 실패 가능한 이니셜라이저는 init 키워드 뒤에 ?를 붙여서 표시할 수 있다.(init?)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
동일한 타입과 이름을 가진 파라미터들로 실패 가능한 이니셜라이저와 실패 불가능한 이니셜라이저를 동시에 정의할 수 없다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Deinitialization</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-24-deinitialization/</link><pubDate>Sun, 18 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-24-deinitialization/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="deinitialization"&gt;Deinitialization&lt;/h2&gt;
&lt;p&gt;디이니셜라이저는 클래스의 인스턴스가 할당 해제되기 전에 직전에 호출된다. 이니셜라이저가 init 키워드를 사용하여 작성하는 것 처럼, deinit 키워드를 사용하여 디이니셜라이저를 작성할 수 있다. 디이니셜라이저는 클래스 타입에만 사용 가능하다.&lt;/p&gt;
&lt;h3 id="how-deinitialization-works"&gt;How Deinitialization Works&lt;/h3&gt;
&lt;p&gt;스위프트는 인스턴스가 더 이상 필요하지 않을때, 자동적으로 할당을 해제해여 리소스를 확보한다. 스위프트는 automatic reference counting (ARC)를 통해 메모리를 관리한다. 일반적으로 인스턴스가 할당 해제될때 수동적으로 처리를 해줄 필요가 없다. 하지만 자체적인 리소스로 작업을 할 경우에는, 수동으로 추가적인 처리를 해줘야 하는 경우가 있다. 예를 들면, 파일을 열고 거기에 데이터를 작성하는 커스텀 클래스가 있을 때, 그 클래스의 인스턴스가 할당 해제되기 전에 파일을 먼저 닫아줘야 한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Optional Chaining (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-25-optional-chaining-1/</link><pubDate>Mon, 19 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-25-optional-chaining-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="optional-chaining"&gt;Optional Chaining&lt;/h2&gt;
&lt;p&gt;옵셔널 체이닝은 nil일 수도 있는 프로퍼티, 메소드, 서브스크립트를 호출하고 쿼리하는 프로세스이다. 만약 옵셔널에 값이 있다면, 프로퍼티, 메소드, 서브스크립트 호출은 성공하고, nil이라면 프로퍼티, 메소드, 서브스크립트 호출은 nil을 리턴한다. 다수의 쿼리를 하나의 체인으로 엮을 수 있으며, 그중 하나라도 nil값을 가진다면 전체적인 체인은 안전하게 실패한다.&lt;/p&gt;
&lt;h3 id="optional-chaining-as-an-alternative-to-forced-unwrapping"&gt;Optional Chaining as an Alternative to Forced Unwrapping&lt;/h3&gt;
&lt;p&gt;호출하고자 하는 프로퍼티, 메소드, 서브스크립트가 nil이 아니라면, 옵셔널 값 뒤에 물음표(?)를 붙여 옵셔널 체이닝을 특정할 수 있다. 이것은 옵셔널 값 뒤에 느낌표(!)를 붙여 강제 언래핑을 하는 것과 매우 비슷하다. 가장 큰 차이점은, 옵셔널 체이닝은 만약 옵셔널이 nil이라면 안전하게 실패하고, 강제 언래핑은 옵셔널이 nil이면 런타임 에러를 발생시킨다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Optional Chaining (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-26-optional-chaining-2/</link><pubDate>Tue, 20 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-26-optional-chaining-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="linking-multiple-levels-of-chaining"&gt;Linking Multiple Levels of Chaining&lt;/h3&gt;
&lt;p&gt;다중 레벨의 옵셔널 체이닝을 서로 연결하여 모델 내부의 프로퍼티, 메소드, 서브스크립트로 깊게 내려갈 수 있다. 하지만 다중 레벨 옵셔널 체이닝은 리턴된 값에 옵셔널 단계를 증가시키지 않는다.(주: 옵셔널 체이닝을 하면 항상 옵셔널 값이 리턴되는데, 옵셔널 체이닝 여러개를 합친다 해서 이중 옵셔널, 삼중 옵셔널이 되지 않는다는 뜻)&lt;/p&gt;
&lt;p&gt;다른말로 설명하자면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;옵셔널이 아닌 값을 검색하려고 시도하면, 옵셔널 체이닝 때문에 옵셔널 값이 된다.&lt;/li&gt;
&lt;li&gt;옵셔널 값을 검색하려고 시도하면, 체이닝 때문에 옵셔널이 추가되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그러므로:&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Error Handling (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-27-error-handling-1/</link><pubDate>Wed, 21 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-27-error-handling-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="error-handling"&gt;Error Handling&lt;/h2&gt;
&lt;p&gt;에러 핸들링은 프로그램이 에러 상황에서 응답하고 회복하는 프로세스이다. 스위프트는 런타임에 복구 가능한 에러를 던지고(throwing), 포착하고(catching), 전파하고(propagating), 조정하는(manipulating)데 최적화된 지원을 해준다.&lt;/p&gt;
&lt;p&gt;일부 연산들은 항상 실행을 마치거나 의미있는 결과를 생성하는 것을 보장하지 않는다. 옵셔널은 값의 부재를 나타내는 사용되자만, 연산이 실패한 경우에는 코드가 그에 응답할 수 있도록 실패한 이유를 아는 것이 유용하다.&lt;/p&gt;
&lt;p&gt;예를 들면, 디스크에 저장되어있는 파일을 읽고 처리하는 작업을 생각해보자. 이 작업에는 특정 경로에 없는 파일을 포함하거나, 파일에 대한 읽기 권한이 없거나, 파일을 호환 가능한 포맷으로 인코딩 하지 못하는 등의 수많은 실패 요소들이 있다. 이러한 상황들을 구분하면, 프로그램이 해결할 수 있는 에러는 해결하고, 해결 불가능한 에러는 사용자들에게 알릴 수 있다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Error Handling (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-28-error-handling-2/</link><pubDate>Thu, 22 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-28-error-handling-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h4 id="handling-errors-using-do-catch"&gt;Handling Errors Using Do-Catch&lt;/h4&gt;
&lt;p&gt;do-catch 문을 사용하면 코드 블록을 실행해서 에러를 처리할 수 있다. 에러가 do절에서 발생하면, 그 에러를 처리할 수 있는 catch절과 매치된다.&lt;/p&gt;
&lt;p&gt;다음은 do-catch문의 일반적인 형태이다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;expression&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;statements&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;statements&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;statements&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;statements&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;statements&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;catch절 뒤에 (에러의) 패턴을 작성하여, 그 catch절이 처리할 수 있는 에러를 나타낸다. catch절이 패턴을 가지고 있지 않다면, 이 catch절은 모든 에러와 매치될 수 있으며, error라는 이름을 가진 로컬 상수에 바인딩 된다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Concurrency (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-29-concurrency-1/</link><pubDate>Fri, 23 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-29-concurrency-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="concurrency"&gt;Concurrency&lt;/h2&gt;
&lt;p&gt;스위프트는 구조화된 방식의 비동기적이고 병렬 수행되는 코드 작성에 대한 지원이 내장되어있다. &lt;em&gt;비동기(Asynchronuous)&lt;/em&gt; 코드는 일시중단 되었다가 나중에 재개할 수 있지만, 한번에 프로그램의 한 부분만 실행된다. 프로그램에서 코드를 중단하고 재개하면 네트워크에서 데이터를 불러오거나, 파일 파싱과 같은 긴 시간이 걸리는 작업을 계속 진행하면서 UI 업데이트와 같은 짧은 시간이 걸리는 작업을 계속할 수 있다. &lt;em&gt;병렬 코드(Parallel code)&lt;/em&gt; 는 동시에 실행되는 코드들을 의미한다 — 예를 들어 4코어 프로세서를 가진 컴퓨터는 각각의 코어가 코드 하나씩을 실행하게 하여, 네 개의 코드를 동시에 실행할 수 있다. 병렬과 비동기 코드를 사용하는 프로그램은 한번에 여러 작업을 수행하고, 외부 시스템을 기다리는 작업을 일시 중단한다. 이 챕터의 나머지 부분에서 용어 &lt;em&gt;동시성(concurrency)&lt;/em&gt; 는 비동기 및 병렬 코드를 지칭한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Concurrency (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-30-concurrency-2/</link><pubDate>Mon, 26 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-30-concurrency-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="asynchronous-sequences"&gt;Asynchronous Sequences&lt;/h3&gt;
&lt;p&gt;이전 섹션의 listPhotos(inGallery:) 함수는 모든 배열의 원소들이 준비된 후, 비동기적으로 전체 배열을 리턴한다. 다른 접근 방법으로는 &lt;em&gt;비동기 시퀀스(asynchronous Sequence)&lt;/em&gt; 를 사용하여 컬렉션의 요소 하나를 한 번에 하나씩 기다리는 것이다. 다음은 비동기 시퀀스를 반복하는 모습이다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;Foundation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FileHandle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standardInput&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;일반적인 for-in 루프를 사용하는 대신에, 위의 예시는 for 다음에 await를 작성한다. 비동기 함수나 메소드를 호출할 때 처럼 await를 쓰는 것은 정지 가능한 지점을 나타낸다. for-await-in 루프는 각 반복이 시작될 때, 다음 원소가 준비될 때 까지 기다리는 동안 실행을 정지한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Concurrency (3)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-31-concurrency-3/</link><pubDate>Mon, 26 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-31-concurrency-3/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="actors"&gt;Actors&lt;/h3&gt;
&lt;p&gt;태스크를 이용하여 프로그램을 분리된 동시성 조각으로 나눌 수 있다. 태스크는 서로 독립적이기 때문에 동시에 실행되어도 안전하다. 하지만 가끔씩 태스크들 사이에 정보를 공유해야 하는 경우가 있다. 액터는 동시성 코드 사이에 정보를 안전하게 공유하도록 해준다.&lt;/p&gt;
&lt;p&gt;클래스처럼 액터는 레퍼런스 타입이다. 따라서 Classes Are Reference Types에서 값 타입과 레퍼런스 타입을 비교한 것이 액터에도 적용된다. 클래스와 다르게, 액터는 한번에 하나의 작업만 변경 가능한 상태(mutable state)에 접근하도록 허용하기 때문에, 여러 태스크가 액터의 한 인스턴스에 상호작용하는 것을 안전하게 해준다. 예를 들어 다음은 온도를 기록하는 액터이다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Macro (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-32-macro-1/</link><pubDate>Thu, 14 Aug 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-32-macro-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;p&gt;매크로는 컴파일할 때 소스 코드를 변환하여, 반복적인 코드를 직접 작성하지 않게 해준다. 컴파일하는 동안, Swift는 평상시처럼 코드를 빌드하기 전에 매크로들을 확장한다.&lt;/p&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/the-swift-programming-language-32-macro-1/image-002-optimized-image.webp"&gt;&lt;p&gt;매크로를 확장하는 것은 항상 추가하는(additive) 작업이다: 매크로는 새로운 코드를 더하지만, 기존에 존재하는 코드를 삭제하거나 수정하지 않는다.&lt;/p&gt;
&lt;p&gt;매크로 입력과, 매크로 확장의 출력 둘 다 구문적으로 유효한 Swift 코드인지 체크된다. 마찬가지로, 매크로에 전달되는 값과 매크로로 생성된 코드 안의 값이 정확한 타입인지 검사된다. 추가적으로, 매크로를 확장할 때 구현부에서 에러를 마주치면, 컴파일러는 이를 컴파일 에러로 처리한다. 이러한 보장은 매크로를 사용하는 코드를 더 쉽게 이해하게 해주고, 매크로를 잘못 사용하거나 매크로의 구현에 버그가 있는 이슈들을 더 쉽게 알아차리게 해준다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Macro (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-33-macro-2/</link><pubDate>Fri, 15 Aug 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-33-macro-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="macro-expansion"&gt;Macro Expansion&lt;/h3&gt;
&lt;p&gt;매크로를 사용한 Swift 코드를 빌드 할 때, 코드를 확장하기 위해 매크로의 구현을 불러오게 된다.&lt;/p&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/the-swift-programming-language-33-macro-2/image-002-optimized-image.webp"&gt;&lt;p&gt;구체적으로 Swift는 다음과 같은 방법으로 매크로를 확장한다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;컴파일러가 코드를 읽어, 구문의 in-memory 표현을 만든다.&lt;/li&gt;
&lt;li&gt;컴파일러가 이 in-memory 표현의 일부를 매크로를 확장시키는 매크로 구현부에 보낸다.&lt;/li&gt;
&lt;li&gt;컴파일러가 매크로 호출을 확장된 형태로 대체한다.&lt;/li&gt;
&lt;li&gt;컴파일러가 확장된 소스 코드를 사용하여 컴파일을 계속 진행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;구체적인 단계들을 살펴보기 위해, 다음을 생각해보자:&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;magicNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fourCharacterCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ABCD&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;#fourCharacterCode&lt;/code&gt; 매크로는 4개의 캐릭터로 구성된 문자열을 받아, 그 문자열의 ASCII 값들을 이어 붙인 32비트 언사인드 인티저를 리턴한다. 일부 파일 포맷은 데이터를 식별하기 위해 이런 형태의 인티저를 사용하는데, 컴팩트 하면서도 디버거에서 읽을 수 있기 때문이다. 아래의 Implementing a Macro 섹션에서 이 매크로를 구현하는 방법을 보여준다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Type Casting</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-34-type-casting/</link><pubDate>Tue, 27 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-34-type-casting/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="type-casting"&gt;Type Casting&lt;/h2&gt;
&lt;p&gt;타입 캐스팅(Type casting)은 인스턴스의 타입을 체크하거나, 해당 클래스가 소속된 계층의 다른 슈퍼클래스나 서브클래스로 취급하는 방법이다.&lt;/p&gt;
&lt;p&gt;스위프트의 타입 캐스팅은 is 혹은 as 연산자로 구현된다. 이 두 연산자는 값의 타입을 체크하거나, 다른 타입으로 값을 캐스트하는 간단하고 표현적인 방법을 제공한다.&lt;/p&gt;
&lt;p&gt;타입이 프로토콜을 준수하고 있는지 여부를 확인하기 위해 타입 캐스팅을 사용할 수도 있다.&lt;/p&gt;
&lt;h3 id="defining-a-class-hierarchy-for-type-casting"&gt;Defining a Class Hierarchy for Type Casting&lt;/h3&gt;
&lt;p&gt;클래스와 서브클래스들의 계층에서 특정 클래스의 타입을 체크하거나 그 클래스의 인스턴스를 같은 계층의 다른 클래스로 캐스트할 때 사용할 수 있다. 아래의 코드 세 개는 앞으로 나올 예시들에서 쓰일 클래스의 계층과, 이 클래스들의 인스턴스를 담고 있는 배열을 정의한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Nested Types</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-35-nested-types/</link><pubDate>Wed, 28 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-35-nested-types/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="nested-types"&gt;Nested Types&lt;/h2&gt;
&lt;p&gt;열거형은 특정 클래스나 스트럭처의 기능을 지원하도록 자주 생성된다. 비슷한 이유로, 클래스나 스트럭처를 복잡한 컨텍스트 내부에서 순수하게 유틸 용도로 사용하기 위해 정의할 수도 있다. 이를 위해 스위프트에서는 &lt;em&gt;중첩 타입(nested types)&lt;/em&gt; 을 정의할 수 있다. 중첩 타입을 지원하는 타입의 정의 내부에서 중첩 열거형, 클래스, 스트럭처를 중첩시킬 수 있다.&lt;/p&gt;
&lt;h3 id="nested-types-in-action"&gt;Nested Types in Action&lt;/h3&gt;
&lt;p&gt;아래의 예시는 블랙잭 카드 게임에서 사용하는 플레잉 카드를 모델링하는 BlackjackCard라는 스트럭처를 정의한다. BlackjackCard 스트럭처는 두 개의 중첩 열거형 타입 Suit와 Rank를 가지고 있다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Extensions (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-36-extensions-1/</link><pubDate>Thu, 29 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-36-extensions-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="extensions"&gt;Extensions&lt;/h2&gt;
&lt;p&gt;익스텐션(Extensions)은 이미 존재하는 클래스, 스트럭처, 열거형, 프로토콜에 새로운 기능을 추가한다. 이는 원본 코드에 접근할 수 없는 타입(&lt;em&gt;retroactive modeling&lt;/em&gt; 라고도 함)에 대한 확장도 가능하다.&lt;/p&gt;
&lt;p&gt;스위프트의 익스텐션은 다음과 같은 행동을 할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컴퓨티드 인스턴스 프로퍼티 혹은 컴퓨티드 타입 프로퍼티 추가&lt;/li&gt;
&lt;li&gt;인스턴스 메소드 혹은 타입 메소드 정의&lt;/li&gt;
&lt;li&gt;새로운 이니셜라이저 제공&lt;/li&gt;
&lt;li&gt;서브스크립트 정의&lt;/li&gt;
&lt;li&gt;새로운 중첩 타입 정의 및 사용&lt;/li&gt;
&lt;li&gt;이미 존재하는 타입이 프로토콜 준수하도록 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스위프트에서는 프로토콜도 확장(extend)하여 요구사항 구현을 제공하거나, 그 프로토콜을 준수하고 있는 타입들이 사용할 수 있는 추가 기능을 제공할 수도 있다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Extensions (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-37-extensions-2/</link><pubDate>Thu, 29 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-37-extensions-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="methods"&gt;Methods&lt;/h3&gt;
&lt;p&gt;익스텐션은 이미 존재하는 타입에 새 인스턴스 메소드와 타입 메소드를 추가할 수 있다. 다음의 예시는 새 인스턴스 메소드 repetitions을 Int 타입에 추가한다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;repetitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mf"&gt;0.&lt;/span&gt;&lt;span class="p"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;repetitions(task:) 메소드는 하나의 () -&amp;gt; Void 타입 아규먼트를 받는다.&lt;/p&gt;
&lt;p&gt;이 익스텐션을 정의한 후에는 repetitions(task:)메소드를 어떠한 정수에서도 호출할 수 있다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="mf"&gt;3.&lt;/span&gt;&lt;span class="n"&gt;repetitions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Hello!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Hello!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Hello!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Hello!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id="mutating-instance-methods"&gt;Mutating Instance Methods&lt;/h4&gt;
&lt;p&gt;익스텐션에서 추가된 인스턴스 메소드는 인스턴스 자체를 수정(mutate)할 수도 있다. self나 self의 프로퍼티를 수정하는 스트럭처나 열거형은 메소드도 기존 구현의 뮤테이팅 메소드처럼 반드시 mutating으로 지정되어야 한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Protocols (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-38-protocols-1/</link><pubDate>Fri, 30 Jun 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-38-protocols-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="protocols"&gt;Protocols&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;프로토콜(protocol)&lt;/em&gt; 은 특정 작업이나 기능에 적합한 메소드, 프로퍼티, 기타 요구사항에 대한 청사진을 정의한다. 프로토콜은 해당 요구사항들을 실제로 구현하기 위해 클래스, 스트럭처, 열거형에서 도입할 수 있다. 프로토콜의 요구사항들을 만족하는 타입을 프로토콜을 &lt;em&gt;준수한다고(conform)&lt;/em&gt; 말한다.&lt;/p&gt;
&lt;p&gt;프로토콜을 준수하고 있는 타입이 반드시 구현해야하는 요구사항을 지정하는 것 외에도, 프로토콜을 확장하여 이러한 요구사항들의 일부를 구현하거나, 그 프로토콜을 준수하고 있는 타입이 사용할 수 있는 기능을 추가할 수 있다.&lt;/p&gt;
&lt;h3 id="protocol-syntax"&gt;Protocol Syntax&lt;/h3&gt;
&lt;p&gt;프로토콜은 클래스, 스트럭처, 열거형과 매우 비슷한 방법으로 정의한다:&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Protocols (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-39-protocols-2/</link><pubDate>Sat, 01 Jul 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-39-protocols-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="protocols-as-types"&gt;Protocols as Types&lt;/h3&gt;
&lt;p&gt;프로토콜은 어떠한 기능도 실제로 구현하지는 않는다. 하지만 코드에서 프로토콜을 타입으로 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;프로토콜을 타입으로 사용하는 가장 일반적인 방법은 프로토콜을 제네릭 제약조건(generic constraint)로 사용하는 것이다. 제네릭 제약조건이 있는 코드는 프로토콜을 준수하는 모든 타입에서 사용할 수 있으며, 특정 타입은 API를 사용되는 코드에서 선택된다. 예를 들어 제네릭 아규먼트를 받는 함수를 호출하면, 호출자가 타입을 선택한다.&lt;/p&gt;
&lt;p&gt;불투명한 타입(opaque type)의 코드는 프로토콜을 준수하는 일부 타입에서 동작한다. 실제 타입은 컴파일 타임에 알수 있고, API 구현은 그 타입을 선택하지만 그 타입의 식별자는 클라이언트에게 숨겨져 있다. 불투명한 타입을 사용하는 것은 추상화 레이어를 통해 API의 디테일한 구현이 유출되는 것을 방지하게 해준다. — 예를 들어, 함수의 지정된 리턴 타입을 숨기고, 리턴 값의 타입이 주어진 프로토콜을 준수한다는 것만을 보장하게 한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Protocols (3)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-40-protocols-3/</link><pubDate>Sun, 02 Jul 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-40-protocols-3/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="adding-protocol-conformance-with-and-extension"&gt;Adding Protocol Conformance with and Extension&lt;/h3&gt;
&lt;p&gt;새로운 프로토콜을 도입하고 준수하기 위해 이미 존재하는 타입을 확장할 수 있다. 그 타입의 원본 소스코드에 접근하지 못하더라도 가능하다. 익스텐션은 이미 존재하는 타입에 새로운 프로퍼티, 메소드, 서브스크립트를 추가할 수 있으므로, 프로토콜에서 요구하는 모든 요구사항들을 추가할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
타입의 이미 존재하는 인스턴스는 인스턴스의 타입의 익스텐션에 프로토콜 준수가 추가되었을 때, 자동적으로 프로토콜을 준수하게 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;예를 들어, TextRespresentable 프로토콜은 텍스트로 표현할 수 있는 방법이 있는 모든 타입에서 구현 가능하다:&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Protocols (4)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-41-protocols-4/</link><pubDate>Mon, 03 Jul 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-41-protocols-4/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="protocol-composition"&gt;Protocol Composition&lt;/h3&gt;
&lt;p&gt;하나의 타입이 여러개의 프로토콜을 동시에 준수하도록 하는 것이 유용할 때가 있다. &lt;em&gt;프로토콜 구성(protocol composition)&lt;/em&gt; 을 통해 여러 개의 프로토콜을 하나의 프로토콜로 결합할 수 있다. 프로토콜 구성은 내부에 있는 모든 프로토콜의 요구사항을 결합한 임시 로컬 프로토콜을 정의한 것처럼 동작한다. 하지만 프로토콜 구성은 어떠한 새로운 프로토콜 타입도 정의하지 않는다.&lt;/p&gt;
&lt;p&gt;프로토콜 구성은 SomeProtocol &amp;amp; AnotherProtocol의 형태를 가진다. 앰퍼샌드로 구분하여 필요한 수 만큼 프로토콜을 나열할 수 있다. 프로토콜 리스트에 추가로 프로토콜 구성은 필요로 하는 슈퍼클래스로 지정하기 위해 하나의 클래스 타입을 포함할 수도 있다.(주: 프로토콜 구성을 준수하려면 특정 클래스의 서브클래스이어야 할 때, 슈퍼클래스도 그 프로토콜 구성 내부에 포함시킨다.)&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Protocols (5)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-42-protocols-5/</link><pubDate>Tue, 04 Jul 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-42-protocols-5/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="protocol-extensions"&gt;Protocol Extensions&lt;/h3&gt;
&lt;p&gt;프로토콜은 메소드, 이니셜라이저, 서브스크립트, 컴퓨티드 프로퍼티를 해당 프로퍼티를 준수하는 타입들에게 제공하도록 확장될 수 있다. 이는 각 타입의 개별 준수나 전역 함수가 아니라 프로토콜 스스로 동작을 정의할 수 있게 해준다.&lt;/p&gt;
&lt;p&gt;예를 들어, RandomNumberGenerator 프로토콜은 random() 메소드 요구사항의 결과값을 사용하여 랜덤 Bool 값을 리턴하는 randomBool() 메소드를 제공하도록 확장될 수 있다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;RandomNumberGenerator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;randomBool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;프로토콜의 익스텐션을 만들면, 해당 프로토콜을 준수하는 모든 타입은 추가적인 수정 없이 모든 이러한 메소드 구현을 자동적으로 얻게 된다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Generics (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-43-generics-1/</link><pubDate>Tue, 01 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-43-generics-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="generics"&gt;Generics&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;제네릭 코드(Generic code)&lt;/em&gt; 는 정의한 요구사항을 만족하는 모든 타입과 함께 작업할 수 있는 유연하고 재사용성 높은 함수나 타입을 작성하게 해준다. 코드의 중복을 피하고, 의도를 추상적이고 명확한 방법으로 표현할 수 있다.&lt;/p&gt;
&lt;p&gt;제네릭은 스위프트의 강력한 특징 중 하나이며, 많은 스위프트의 표준 라이브러리는 제네릭 코드로 빌드 되어있다. 사실 인지하지 못했어도 Language Guide 내내 제네릭 코드를 사용했다. 예를 들어, 스위프트의 &lt;code&gt;Array&lt;/code&gt;와 &lt;code&gt;Dictionary&lt;/code&gt; 타입은 둘 다 제네릭 컬렉션이다. &lt;code&gt;Int&lt;/code&gt; 값을 저장하는 배열, &lt;code&gt;String&lt;/code&gt; 값을 저장하는 배열, 혹은 스위프트에서 만들 수 있는 어떠한 다른 타입이라도 저장하는 배열 등을 생성할 수 있다. 비슷하게, 어떤 타입이든지 간에 특정한 타입을 저장하는 딕셔너리를 만들 수 있다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Generics (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-44-generics-2/</link><pubDate>Wed, 02 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-44-generics-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="generic-types"&gt;Generic Types&lt;/h3&gt;
&lt;p&gt;제네릭 함수와 다르게, 스위프트는 제네릭 타입도 정의할 수 있게 해준다. 이는 &lt;code&gt;Array&lt;/code&gt;나 &lt;code&gt;Dictionary&lt;/code&gt;처럼 모든 타입과 함께 작업할 수 있는 커스텀 클래스, 스트럭처, 열거형이다.&lt;/p&gt;
&lt;p&gt;이 섹션은 스택이라는 제네릭 컬렉션 타입을 작성하는 방법을 보여준다. 스택은 배열과 비슷하게 순서가 있는 값들의 집합이다, 하지만 스위프트의 &lt;code&gt;Array&lt;/code&gt; 타입보다 더 제한된 작업을 한다. 배열은 배열의 어느 위치에서나 값을 삽입하거나 제거할 수 있지만, 스택은 컬렉션의 끝 부분에서만 삽입(&lt;em&gt;pushing&lt;/em&gt;)이나 삭제(&lt;em&gt;popping&lt;/em&gt;)을 할 수 있다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Generics (3)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-45-generics-3/</link><pubDate>Fri, 04 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-45-generics-3/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="type-constraints"&gt;Type Constraints&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;swapTwoValues(_:_:)&lt;/code&gt; 함수와 &lt;code&gt;Stack&lt;/code&gt; 타입은 어떠한 타입으로도 작업할 수 있다. 하지만 특정한 &lt;em&gt;타입 제약조건(type constraints)&lt;/em&gt; 을 강제하여 제네릭 함수나 제네릭 타입이 사용할 수 있는 타입을 지정하는 것이 유용할 때가 있다. 타입 제약조건은 타입 파라미터가 특정한 클래스를 상속 받거나, 특정 프로토콜을 준수해야 하도록 지정한다.&lt;/p&gt;
&lt;p&gt;예를 들어, 스위프트의 &lt;code&gt;Dictionary&lt;/code&gt; 타입은 딕셔너리의 키로 사용할 수 있는 타입에 제한을 둔다. 딕셔너리의 키로 사용될 타입은 반드시 &lt;em&gt;hashable&lt;/em&gt; 해야한다. 즉, 스스로를 유니크하게 구분할 수 있어야 한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Generics (4)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-46-generics-4/</link><pubDate>Mon, 07 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-46-generics-4/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="associated-types"&gt;Associated Types&lt;/h3&gt;
&lt;p&gt;프로토콜을 정의할때, 프로토콜의 정의의 일부로 하나 혹은 그 이상의 &lt;em&gt;연관 타입(associated type)&lt;/em&gt; 을 선언하는 것이 유용할 때가 있다. 연관 타입은 프로토콜의 일부로 사용되는 타입에게 플레이스홀더 이름을 부여한다. 해당 연관 타입이 될 실제 타입은 프로토콜이 적용될 때 까지 지정되지 않는다. 연관 타입은 associatedtype 키워드로 특정할 수 있다.&lt;/p&gt;
&lt;h3 id="associated-types-in-action"&gt;Associated types in Action&lt;/h3&gt;
&lt;p&gt;다음은 연관 타입으로 &lt;code&gt;Item&lt;/code&gt;을 선언하는 프로토콜 &lt;code&gt;Container&lt;/code&gt;의 예시이다:&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;associatedtype&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;mutating&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kr"&gt;get&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;subscript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kr"&gt;get&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;Container&lt;/code&gt; 프로토콜은 모든 컨테이너가 반드시 제공해야할 세 개의 필수 요구사항을 정의한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Generics (5)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-47-generics-5/</link><pubDate>Tue, 08 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-47-generics-5/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="generic-where-clauses"&gt;Generic Where Clauses&lt;/h3&gt;
&lt;p&gt;타입 제약조건을 사용하면 제너럴 함수, 서브스크립트, 타입에 관련된 타입 파라미터의 요구사항을 정의할 수 있다.&lt;/p&gt;
&lt;p&gt;이는 연관 타입의 제약 조건을 정의하는데에도 유용하며, 제네릭 where 절을 정의하여 할 수 있다. 제네릭 &lt;code&gt;where&lt;/code&gt;절은 연관 타입이 반드시 특정 프로토콜을 준수하게 하거나, 특정한 타입 파라미터와 연관 타입이 반드시 같아야 함을 요구할 수 있다. 제네릭 &lt;code&gt;where&lt;/code&gt;절은 &lt;code&gt;where&lt;/code&gt; 키워드로 시작하고, 뒤에 연관 타입의 제약조건이나 타입 파라미터와 연관 타입의 동등 관계를 작성한다. 제네릭 where절은 타입이나 함수의 본문의 여는 중괄호 바로 앞에 작성한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Opaque Types (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-48-opaque-types-1/</link><pubDate>Sat, 12 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-48-opaque-types-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="opaque-and-boxed-types"&gt;Opaque and Boxed Types&lt;/h2&gt;
&lt;p&gt;스위프트는 값의 타입에 대한 상세한 정보를 숨기는 두가지 방법을 제공한다: 불투명한 타입과 박스형 프로토콜 타입이다. 타입의 정보를 숨기는 것은 모듈과 모듈을 호출하는 코드의 경계에서 유용하다, 리턴 값의 타입이 공개되지 않은 채로 유지될 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;함수나 메소드는 리턴 값의 타입 정보를 숨기기 위해 불투명한 타입을 리턴하다. 함수의 리턴 타입을 구체적으로 제공하는 대신에, 리턴 값은 값이 지원하는 프로토콜로 묘사된다. 불투명한 타입은 타입의 정체를 보존한다 — 컴파일러는 타입 정보에 대해 접근이 가능하지만, 모듈의 클라이언트들은 불가능하다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Opaque Types (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-49-opaque-types-2/</link><pubDate>Sun, 13 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-49-opaque-types-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="boxed-protocol-types"&gt;Boxed Protocol Types&lt;/h3&gt;
&lt;p&gt;박스드 프로토콜 타입은 “there exists a type T such that T conforms to the protocol” 라는 구절에서 따와 실존적 타입 &lt;em&gt;(existential type)&lt;/em&gt; 이라고도 한다. 박스드 프로토콜 타입을 만들기 위해, 프로토콜 이름 앞에 &lt;code&gt;any&lt;/code&gt;를 작성한다. 다음은 예시이다:&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;VerticalShapes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Shape&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;shapes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="n"&gt;Shape&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;shapes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="n"&gt;joined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;largeTriangle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Triangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;largeSquare&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;vertical&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VerticalShapes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shapes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;largeTriangle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;largeSquare&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="bp"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;위의 예시에서, &lt;code&gt;VerticalShapes&lt;/code&gt;는 도형들의 타입을 박스드 &lt;code&gt;Shape&lt;/code&gt; 원소들의 배열인 &lt;code&gt;[any Shape]&lt;/code&gt;로 선언한다. 배열의 각 원소들은 서로 다른 타입일 수 있지만, 반드시 &lt;code&gt;Shape&lt;/code&gt; 프로토콜을 준수해야 한다. 런타임에 유연성을 지원하기 위해, 스위프트는 필요할 때 박스(box)라는 간접적인 단계를 추가한다. 박스는 성능 코스트가 있다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Automatic Reference Counting (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-50-automatic-reference-counting-1/</link><pubDate>Tue, 15 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-50-automatic-reference-counting-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="automatic-reference-counting"&gt;Automatic Reference Counting&lt;/h2&gt;
&lt;p&gt;스위프트는 &lt;em&gt;자동 참조 카운팅&lt;/em&gt;(ARC)을 사용하여 앱의 메모리 사용량을 추적하고 관리한다. 대부분의 경우에, 스위프트에서 메모리 관리는 &amp;ldquo;그냥 작동&amp;quot;하고, 메모리 관리를 직접 할 생각을 하지 않아도 된다. ARC는 클래스 인스턴스가 더 이상 필요하지 않게 되었을 때, 자동적으로 해당 메모리를 비우게 된다.&lt;/p&gt;
&lt;p&gt;하지만, 경우에 따라 ARC는 메모리 관리를 위해 코드 내부에서의 관계**(주: 원문은 relationships between parts of your code, 코드 부분들 간의 관계)** 에 대한 정보를 요구할 때가 있다. 이 챕터는 이러한 상황들을 설명하고, 어떻게 ARC가 앱의 메모리를 관리하는지를 보여준다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Automatic Reference Counting (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-51-automatic-reference-counting-2/</link><pubDate>Wed, 16 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-51-automatic-reference-counting-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="resolving-strong-reference-cycles-between-class-instances"&gt;Resolving Strong Reference Cycles Between Class Instances&lt;/h3&gt;
&lt;p&gt;스위프트는 클래스 타입 프로퍼티로 작업할 때 강한 참조 사이클을 해결하는 두 가지 방법으로 약한 참조와 미소유 참조를 제공한다.&lt;/p&gt;
&lt;p&gt;약한 참조와 미소유 참조는 참조 사이클 내부의 한 인스턴스가 다른 인스턴스를 강하게 붙잡지 않고 참조할 수 있게 해준다. 그리고는 그 인스턴스들은 서로를 강한 참조 사이클 없이 참조할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;다른 인스턴스의 수명이 더 짧을 때 약한 참조를 사용한다. — 즉 다른 인스턴스가 먼저 할당 해제되는 경우이다. 이전의 &lt;code&gt;Apartment&lt;/code&gt; 예시에서, 아파트의 라이프 사이클 중간에 거주자가 없는 것은 충분히 가능한 일이므로 약한 참조는 이러한 경우의 참조 사이클을 깨뜨리는데 적합하다. 반대로, 미소유 참조를 사용하는 것은 다른 인스턴스가 같거나 더 긴 수명을 가지고 있을때 사용한다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Automatic Reference Counting (3)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-52-automatic-reference-counting-3/</link><pubDate>Mon, 21 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-52-automatic-reference-counting-3/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h3 id="strong-reference-cycles-for-closures"&gt;Strong Reference Cycles for Closures&lt;/h3&gt;
&lt;p&gt;두 클래스 인스턴스의 프로퍼티들이 서로를 강한 참조하면서 강한 참조 사이클이 만들어지는지를 이전에 보았고, 약한 참조와 미소유 참조가 이러한 강한 참조 사이클을 깨뜨리는 것도 보았다.&lt;/p&gt;
&lt;p&gt;강한 참조 사이클은 클래스의 인스턴스에 클로저를 할당하고, 해당 클로저의 본문에서 그 인스턴스를 캡처할때도 발생한다. 이러한 캡처는 &lt;code&gt;self.someProperty&lt;/code&gt;처럼 그 클로저가 해당 인스턴스의 프로퍼티에 접근하거나, &lt;code&gt;self.someMethod()&lt;/code&gt;처럼 해당 인스턴스의 메소드에 접근할 때 발생한다. 두 경우 모두, 이러한 접근으로 그 클로저가 &lt;code&gt;self&lt;/code&gt;를 &amp;ldquo;캡처&amp;quot;할때, 강한 참조 사이클을 생성하게 된다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Memory Safety (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-53-memory-safety-1/</link><pubDate>Sat, 26 Aug 2023 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-53-memory-safety-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="memory-safety"&gt;Memory Safety&lt;/h2&gt;
&lt;p&gt;스위프트는 기본적으로 코드에서 일어나는 안전하지 않은 행동들을 방지한다. 예를 들어, 스위프트는 변수를 사용하기 전에 초기화가 되어있는지 확인하고, 할당 해제된 메모리에는 접근하지 않으며, 배열의 인덱스 값들이 범위 안에 있는지 확인한다.&lt;/p&gt;
&lt;p&gt;또한 스위프트는 메모리를 수정하는 코드가 해당 메모리에 독점적으로 접근하도록 요구하여 같은 위치에 있는 메모리에 대한 동시 접근이 충돌하지 않게 한다. 스위프트가 메모리를 자동적으로 수정하기 때문에, 대부분의 경우에는 메모리 접근에 대해 생각하지 않아도 된다. 하지만, 충돌이 발생할 수 있는 잠재적인 위치를 이해하여, 메모리에 접근할 때 충돌하는 코드 작성을 회피하는 것도 중요하다. 만약 충돌을 일으키는 코드라면, 컴파일 에러 혹은 런타임 에러를 발생시키게 된다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Memory Safety (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-54-memory-safety-2/</link><pubDate>Mon, 27 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-54-memory-safety-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="conflicting-access-to-in-out-parameters"&gt;Conflicting Access to In-Out Parameters&lt;/h2&gt;
&lt;p&gt;함수는 모든 in-out 파라미터에 대한 장기 쓰기 접근 권한을 가지고 있다. in-out 파라미터에 대한 쓰기 권한은 모든 non-in-out 파라미터가 evaluate된 후에 시작되어 함수가 호출되는 전체 기간동안 유지된다. 여러개의 in-out 파라미터가 존재할 경우, 쓰기 접근 파라미터들이 보이는 순서대로 시작된다.&lt;/p&gt;
&lt;p&gt;이 장기 쓰기 접근 권한의 결과중 하나는 스코프 규칙(scoping rule)과 액세스 컨트롤이 허락하더라도, in-out으로 전달된 원본 변수에 접근할 수 없다는 것이다—원본에 접근하는 것은 충돌을 발생시킨다:&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Access Control (1)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-55-access-control-1/</link><pubDate>Tue, 28 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-55-access-control-1/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h1 id="access-control"&gt;Access Control&lt;/h1&gt;
&lt;p&gt;액세스 컨트롤은 다른 소스 파일이나 모듈에서 코드의 일부분에 접근하는 것을 제한한다. 이 기능은 코드의 구체적인 구현 사항을 숨기면서 해당 코드에 접근하고 사용할 선호하는 인터페이스를 지정할 수 있게 해준다.&lt;/p&gt;
&lt;p&gt;개별 타입들 (클래스, 스트럭처, 이뉴머레이션) 뿐만 아니라 해당 타입에 포함된 프로퍼티, 메소드, 이니셜라이저, 서브스크립트에도 접근 레벨을 지정할 수 있다. 프로토콜도 특정 컨텍스트로 제한할 수 있으며, 글로벌 상수, 변수, 함수도 가능하다.&lt;/p&gt;
&lt;p&gt;다양한 레벨의 액세스 컨트롤을 제공하는 것에 더해서, Swift는 일반적인 시나리오에 대해서 기본 액세스 레벨을 제공해서 명시적으로 액세스 컨트롤 레벨을 지정할 필요를 줄였다. 만약 싱글 타겟 앱을 작성하고 있다면, 아마도 명시적인 액세스 컨트롤을 지정할 필요가 없을 것이다.&lt;/p&gt;</description></item><item><title>The Swift Programming Language. Access Control (2)</title><link>https://kelly-chui.github.io/posts/the-swift-programming-language-56-access-control-2/</link><pubDate>Wed, 29 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/the-swift-programming-language-56-access-control-2/</guid><description>&lt;figure class="theme-image"&gt;
&lt;img
class="theme-image__image theme-image__image--light"
src="https://kelly-chui.github.io/images/assets/swift-logo-light.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;img
class="theme-image__image theme-image__image--dark"
src="https://kelly-chui.github.io/images/assets/swift-logo-dark.svg"
alt="Swift logo"
loading="lazy"
decoding="async"
&gt;
&lt;/figure&gt;
&lt;h2 id="custom-types"&gt;Custom Types&lt;/h2&gt;
&lt;p&gt;커스텀 타입에 대한 명시적인 액세스 레벨을 지정하고 싶으면, 타입을 정의할 때 하면 된다. 새로운 타입은 액세스 레벨이 허용하는 곳이라면 어디에서든 사용할 수 있다. 예를 들어, file-private한 클래스를 정의하면, 그 클래스가 정의된 소스 파일 내부에서 프로퍼티나, 함수의 파라미터 혹은 리턴 타입으로만 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;또한 타입의 액세스 컨트롤 레벨은 타입의 &lt;em&gt;멤버&lt;/em&gt;(프로퍼티, 메소드, 이니셜라이저, 서브스크립트)의 기본 액세스 레벨에도 영향을 끼친다. 만약 타입의 액세스 레벨을 private이나 file private으로 정의했다면, 멤버들의 기본 액세스 레벨도 private이나 file private이 된다. 만약 타입의 액세스 레벨을 internal 이나 public으로 정의했다면(혹은 액세스 레벨을 명시적으로 지정하지 않아 기본 액세스 레벨 internal을 사용한다면), 타입의 멤버의 기본 액세스 레벨은 internal이 된다.&lt;/p&gt;</description></item><item><title>StackDay. Date는 날짜가 아니다</title><link>https://kelly-chui.github.io/posts/stackday-dev-log-localday-time-boundary/</link><pubDate>Tue, 18 Aug 2026 17:52:46 +0900</pubDate><guid>https://kelly-chui.github.io/posts/stackday-dev-log-localday-time-boundary/</guid><description>&lt;p&gt;StackDay는 일정 주기의 습관 수행 여부를 기록하는 앱이고, MVP에서는 그 주기를 매일로 고정했다.&lt;/p&gt;
&lt;p&gt;지금까지 구현할 때, &amp;ldquo;어느 날에 수행했는가&amp;quot;와 &amp;ldquo;정확히 언제 기록했는가&amp;quot;를 모두 Foundation의 &lt;code&gt;Date&lt;/code&gt;로 표현했다.&lt;/p&gt;
&lt;p&gt;두 정보는 비슷해 보이지만 다르다. 이 차이를 타입으로 구분하지 않으면 날짜 검증, 중복 기록 검사, 통계 계산마다 같은 해석을 반복하게 된다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 그 문제가 실제 코드에서 어떻게 드러났고, &lt;code&gt;LocalDay&lt;/code&gt;·&lt;code&gt;Clock&lt;/code&gt;·TimeZone을 어떤 경계로 나눴는지 정리한다.&lt;/p&gt;
&lt;h2 id="date타입이-저장하는-것은-순간"&gt;Date타입이 저장하는 것은 순간&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Date&lt;/code&gt;는 연, 월, 일이나 자정 같은 달력 개념을 저장하지 않는다.&lt;/p&gt;</description></item><item><title>Swift Concurrency. Explore Structured Concurrency in Swift (2) - WWDC21</title><link>https://kelly-chui.github.io/posts/wwdc-swift-concurrency-explore-structured-concurrency-in-swift-2/</link><pubDate>Mon, 17 Aug 2026 19:50:17 +0900</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swift-concurrency-explore-structured-concurrency-in-swift-2/</guid><description>&lt;p&gt;&lt;a href="https://kelly-chui.github.io/posts/wwdc-swift-concurrency-explore-structured-concurrency-in-swift-1/"&gt;앞선 포스팅&lt;/a&gt;에서는 구조화된 동시성이 task 계층 구조를 활용해 제어 흐름을 일반 동기 코드처럼 만들고, 에러 전파와 cancellation을 단순하게 만드는 방식을 정리했다.&lt;/p&gt;
&lt;p&gt;이 포스팅에서는 계층 구조가 없는 task, 즉 unstructured task(구조화되지 않은 동시성)에 대해 정리한다.&lt;/p&gt;
&lt;h2 id="not-all-tasks-fit-a-structured-pattern"&gt;Not all tasks fit a structured pattern&lt;/h2&gt;
&lt;p&gt;동기 코드에서 처음으로 비동기 연산을 시작하는 경우처럼 parent task가 존재하지 않거나, task의 생명주기가 하나의 스코프에 들어맞지 않는 경우가 있을 수 있다.&lt;/p&gt;
&lt;p&gt;이런 경우는 특히 UIKit에서 delegate를 구현할 때 자주 발생한다.&lt;/p&gt;</description></item><item><title>StackDay. 비즈니스 규칙과 Entity Identity 분리하기</title><link>https://kelly-chui.github.io/posts/stackday-dev-log-completion-identity/</link><pubDate>Sun, 16 Aug 2026 15:23:36 +0900</pubDate><guid>https://kelly-chui.github.io/posts/stackday-dev-log-completion-identity/</guid><description>&lt;p&gt;현재 StackDay MVP에서는 하나의 &lt;code&gt;Habit&lt;/code&gt;에 대해 하루 한 번만 &lt;code&gt;Completion&lt;/code&gt;을 기록할 수 있다.&lt;/p&gt;
&lt;p&gt;그래서 처음에는 &lt;code&gt;(habitID, completedOn)&lt;/code&gt; 조합을 Completion의 identity처럼 사용해도 될 것 같았고, 실제로 그런 코드도 있었다.&lt;/p&gt;
&lt;p&gt;하지만 &lt;code&gt;Completion&lt;/code&gt; 삭제를 구현하면서 이 둘을 분리해야 한다는 점을 알게 되었다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Equatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Identifiable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;habitID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;completedOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;recordedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id="같은-날의-기록을-찾는-조건"&gt;같은 날의 기록을 찾는 조건&lt;/h2&gt;
&lt;p&gt;같은 &lt;code&gt;Habit&lt;/code&gt;의 같은 날 &lt;code&gt;Completion&lt;/code&gt;을 찾는 데에는 &lt;code&gt;habitID&lt;/code&gt;와 &lt;code&gt;completedOn&lt;/code&gt;이 필요하다. 현재 정책에서는 이 조건으로 중복 기록도 막을 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 이것은 현재 정책에서 필요한 조회 조건이다. 이후 시간 단위로 습관을 기록하거나, 하루에 여러 번 수행하는 &lt;code&gt;Habit&lt;/code&gt;이 생기면 같은 조합으로 여러 &lt;code&gt;Completion&lt;/code&gt;이 존재할 수 있다.&lt;/p&gt;</description></item><item><title>Swift Concurrency. Explore Structured Concurrency in Swift (1) - WWDC21</title><link>https://kelly-chui.github.io/posts/wwdc-swift-concurrency-explore-structured-concurrency-in-swift-1/</link><pubDate>Sat, 15 Aug 2026 15:54:01 +0900</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swift-concurrency-explore-structured-concurrency-in-swift-1/</guid><description>&lt;p&gt;&lt;a href="https://kelly-chui.github.io/posts/wwdc-swift-concurrency-meet-async-await-in-swift-1/"&gt;앞서 정리한 세션&lt;/a&gt;에서는 completion handler 기반의 비동기 코드를 async/await로 작성하면서, 비동기 코드에서도 return, throw, try-catch와 같은 일반적인 제어 흐름을 사용할 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;이번 포스팅에서는 WWDC21의 Explore Structured Concurrency in Swift 세션을 정리한다. async/await가 비동기 코드의 제어 흐름을 구조화했다면, structured concurrency는 여기서 더 나아가 동시에 실행되는 여러 task의 관계와 수명을 구조화한다.&lt;/p&gt;
&lt;h2 id="explore-structed-concurrency-in-swift"&gt;Explore Structed Concurrency in Swift&lt;/h2&gt;
&lt;p&gt;Swift Concurrency는 구조화된 프로그래밍(structured programming)의 아이디어에 기반한 structured concurrency라는 개념을 활용한다.&lt;/p&gt;
&lt;p&gt;옛날에는 프로그램에서 명령어를 나열하고, 컨트롤 플로우가 여기저기로 자유롭게 점프해서 코드를 읽기가 매우 어려웠다. (C의 &lt;code&gt;goto&lt;/code&gt;를 생각해보자)&lt;/p&gt;</description></item><item><title>StackDay. UseCase와 Entity는 각각 어디까지 책임져야 할까</title><link>https://kelly-chui.github.io/posts/stackday-dev-log-usecase-entity-responsibilities/</link><pubDate>Fri, 14 Aug 2026 16:31:52 +0900</pubDate><guid>https://kelly-chui.github.io/posts/stackday-dev-log-usecase-entity-responsibilities/</guid><description>&lt;p&gt;&lt;code&gt;CreateHabitUseCase&lt;/code&gt;를 시작으로 여러 유즈케이스를 구현하면서, 도메인 규칙을 어디에 두어야 할지 계속 고민하게 됐다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Habit&lt;/code&gt;을 생성할 때 이름의 유효성을 누가 검증해야 하는지, &lt;code&gt;Completion&lt;/code&gt;을 남길 때 해당 날짜에 기록할 수 있는지는 누가 판단해야 하는지처럼 비슷한 문제가 반복해서 나타났다.&lt;/p&gt;
&lt;p&gt;처음에는 유즈케이스가 사용자의 작업을 처리하니 이런 검증도 함께 담당하면 된다고 생각할 수 있었다. 하지만 구현을 진행할수록 엔티티가 스스로 보장해야 하는 규칙과 유즈케이스 작업의 흐름을 위해 판단해야 하는 규칙을 구분할 필요가 있었다.&lt;/p&gt;
&lt;p&gt;이 포스트에서는 &lt;code&gt;Habit&lt;/code&gt;의 생성과 &lt;code&gt;Completion&lt;/code&gt; 기록을 중심으로 어떤 고민을 했는지와 어떻게 해결했는지를 정리한다.&lt;/p&gt;</description></item><item><title>StackDay. Repository의 save를 insert와 update로 나누기</title><link>https://kelly-chui.github.io/posts/stackday-dev-log-repository-contracts/</link><pubDate>Fri, 14 Aug 2026 14:15:52 +0900</pubDate><guid>https://kelly-chui.github.io/posts/stackday-dev-log-repository-contracts/</guid><description>&lt;p&gt;유즈케이스를 구현하면서 &lt;code&gt;Habit&lt;/code&gt;과 &lt;code&gt;Completion&lt;/code&gt;을 저장할 Repository 계약을 좀 정리했다.&lt;/p&gt;
&lt;p&gt;처음에는 create와 update 둘 다 &lt;code&gt;save&lt;/code&gt; 하나로 저장하면 간단해 보였지만, 두 기능이 기대하는 실패 조건이 서로 다르다는 점에서 계약을 나눌 필요가 있었다.&lt;/p&gt;
&lt;h2 id="save는-어떤-동작인가"&gt;save는 어떤 동작인가&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;save&lt;/code&gt;를 upsert로 구현하면 호출하는 쪽은 편하다. ID가 없으면 삽입하고, 이미 있으면 갱신하면 된다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="nc"&gt;HabitRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Habit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="kr"&gt;throws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;문제는 이 동작이 실패해야 하는 상황까지 조용히 성공으로 바꾼다는 점이다.&lt;/p&gt;
&lt;p&gt;(물론 UUID라 현실적으로 겹칠 일은 없지만) &lt;code&gt;Habit&lt;/code&gt;을 생성하는 작업은 같은 ID가 이미 있으면 실패해야 하고, 보관처럼 기존 &lt;code&gt;Habit&lt;/code&gt;을 바꾸는 작업은 대상이 없으면 실패해야 한다.&lt;/p&gt;</description></item><item><title>StackDay. 책임 분리와 타입 분리</title><link>https://kelly-chui.github.io/posts/stackday-dev-log-separating-responsibility-and-types/</link><pubDate>Thu, 13 Aug 2026 18:32:51 +0900</pubDate><guid>https://kelly-chui.github.io/posts/stackday-dev-log-separating-responsibility-and-types/</guid><description>&lt;p&gt;이전 작업에서 &lt;code&gt;HabitStreak&lt;/code&gt;을 구현하면서 계산 결과와 계산 로직을 분리했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HabitStreak&lt;/code&gt;은 현재 스트릭과 최장 스트릭이라는 값만 표현하고, 계산은 &lt;code&gt;HabitStreakCalculator&lt;/code&gt;가 담당한다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;HabitStreak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Equatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;longest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;HabitStreakCalculator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Habit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Completion&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;referenceDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Calendar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HabitStreak&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;같은 원리로 &lt;code&gt;HabitStatistics&lt;/code&gt;도 파생 값과 계산 책임을 분리해서 구현했다.&lt;/p&gt;
&lt;h2 id="habitstatistics"&gt;HabitStatistics&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;HabitStatistics&lt;/code&gt;는 Habit의 수행 기록을 요약한 파생 값이다.&lt;/p&gt;
&lt;p&gt;MVP에서는 다음 값을 제공한다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;HabitStatistics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Equatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;totalCompletedDays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;eligibleTrackingDays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;completionRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Double&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;streak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HabitStreak&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;totalCompletedDays&lt;/code&gt;는 추적 기간 안에서 완료한 날짜의 수이고, &lt;code&gt;eligibleTrackingDays&lt;/code&gt;는 시작일부터 기준일까지 추적 대상이 된 날짜의 수다. 아카이브된 Habit은 &lt;code&gt;archivedOn&lt;/code&gt;을 마지막 추적일로 사용한다.&lt;/p&gt;</description></item><item><title>Data Structure. Segment Tree</title><link>https://kelly-chui.github.io/posts/cs-data-structure-segment-tree/</link><pubDate>Thu, 13 Aug 2026 14:18:26 +0900</pubDate><guid>https://kelly-chui.github.io/posts/cs-data-structure-segment-tree/</guid><description>&lt;h2 id="segment-tree"&gt;Segment Tree&lt;/h2&gt;
&lt;p&gt;배열의 특정 구간에 대한 연산을 빠르게 처리하기 위한 이진 트리 형태의 자료구조.&lt;/p&gt;
&lt;p&gt;구간 합, 최솟값, 최댓값처럼 배열의 일정 범위를 대상으로 반복해서 연산해야 하는 경우에 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;일반적인 배열에서 특정 구간의 합을 구하려면 해당 구간의 원소를 직접 순회해야 하므로 $O(n)$의 시간이 필요하다. 세그먼트 트리는 배열의 여러 구간에 대한 연산 결과를 트리 형태로 저장하여 구간 조회를 $O(\log n)$에 처리할 수 있도록 한다.&lt;/p&gt;
&lt;p&gt;특히 배열의 값이 변경되는 상황에서도 구간에 대한 연산을 반복해서 수행해야 할 때 사용할 수 있다.&lt;/p&gt;</description></item><item><title>DevOps. AI 코딩 에이전트 구조적으로 설계하기</title><link>https://kelly-chui.github.io/posts/devops-ai-coding-agent-configuration/</link><pubDate>Mon, 10 Aug 2026 13:15:39 +0900</pubDate><guid>https://kelly-chui.github.io/posts/devops-ai-coding-agent-configuration/</guid><description>&lt;h2 id="ai-에이전트에게-프로젝트를-설명하기"&gt;AI 에이전트에게 프로젝트를 설명하기&lt;/h2&gt;
&lt;p&gt;예전에는 LLM을 이용해 코드를 작성하려면 필요한 기능을 설명하고, 프로젝트의 구조나 현재 상황을 프롬프트로 설명해야 했다. 즉, 프로젝트와 LLM의 API가 사람이었다&amp;hellip;&lt;/p&gt;
&lt;pre class="mermaid" data-mermaid-source="flowchart&amp;#43;LR%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;B%5B%22%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8&amp;#43;%EC%9E%91%EC%84%B1%22%5D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;B&amp;#43;--&amp;#43;%EC%82%AC%EB%9E%8C&amp;#43;--%3E&amp;#43;C%5B%22LLM%EC%9D%B4&amp;#43;%EC%BD%94%EB%93%9C&amp;#43;%EC%83%9D%EC%84%B1%22%5D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;C&amp;#43;--&amp;#43;%EC%82%AC%EB%9E%8C&amp;#43;--%3E&amp;#43;D%5B%22%28copy&amp;#43;%26&amp;#43;paste&amp;#43;%ED%95%B4%EC%84%9C%29%5Cn%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90&amp;#43;%EC%A0%81%EC%9A%A9%22%5D"&gt;&lt;/pre&gt;&lt;p&gt;반면 Codex나 Claude Code 같은 코딩 에이전트는 프로젝트의 파일을 직접 탐색하고 기존 코드를 분석하면서 작업에 필요한 컨텍스트를 스스로 수집한다.&lt;/p&gt;
&lt;p&gt;하지만 코드만으로 프로젝트의 모든 것을 알 수 있는 것은 아니다. 프로젝트의 규모가 작거나 코드에 충분한 단서가 없다면 의도를 추론하기 어렵고, 반대로 규모가 커질수록 필요한 정보를 찾기 위해 더 많은 코드를 탐색하고 컨텍스트로 사용해야 한다.&lt;/p&gt;</description></item><item><title>DevOps. 로컬 LLM을 VSCode와 Xcode에서 코딩 에이전트로 사용하기</title><link>https://kelly-chui.github.io/posts/devops-llm-building-a-local-llm-2/</link><pubDate>Sun, 09 Aug 2026 11:08:32 +0900</pubDate><guid>https://kelly-chui.github.io/posts/devops-llm-building-a-local-llm-2/</guid><description>&lt;p&gt;&lt;a href="https://kelly-chui.github.io/posts/devops-llm-building-a-local-llm-1/"&gt;로컬 LLM 모델을 설치하고&lt;/a&gt; 이어서 로컬 LLM 모델을 Xcode와 VSCode에 연동해보자.&lt;/p&gt;
&lt;p&gt;우선 VSCode에 연결해보고 다음엔 Xcode의 &amp;lsquo;Chat&amp;rsquo; 기능, 최종적으론 OpenCode와 Xcode를 ACP로 연동해서 로컬 LLM을 코딩 에이전트로 이용하려 한다.&lt;/p&gt;
&lt;h2 id="vscode-연결"&gt;VSCode 연결&lt;/h2&gt;
&lt;h3 id="ollama-익스텐션-설치-및-모델-연동"&gt;Ollama 익스텐션 설치 및 모델 연동&lt;/h3&gt;
&lt;p&gt;Ollama에서 공식적으로 VSCode 익스텐션을 제공한다. 단순히 설치만 하면된다.&lt;/p&gt;
&lt;figure class="media-caption align-center"&gt;&lt;img src="https://kelly-chui.github.io/posts/devops-llm-building-a-local-llm-2/image-001-optimized-image.webp" alt="" loading="lazy" width="480"&gt;
&lt;/figure&gt;
&lt;p&gt;로컬 LLM을 VS Code에서 사용하려면, 빌트인 되어있는 Copilot Chat 기능을 이용해야 한다. Copilot Chat의 UI와 코드 컨텍스트 수집 기능을 사용하고, 실제 응답을 생성하는 언어 모델은 Ollama에서 실행 중인 로컬 모델을 사용할 것이다.&lt;/p&gt;</description></item><item><title>Swift Concurrency. Meet async/await in Swift (2) - WWDC21</title><link>https://kelly-chui.github.io/posts/wwdc-swift-concurrency-meet-async-await-in-swift-2/</link><pubDate>Mon, 03 Aug 2026 23:01:47 +0900</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swift-concurrency-meet-async-await-in-swift-2/</guid><description>&lt;p&gt;1편에서는 async await를 어떻게 사용했는지 알아봤다면, 여기서는 테스트와 실제 앱 코드에서 async/await를 사용하는 법을 알아본다. 또한 기존에 존재하는 completion handler 기반 API를 continuation을 사용하여, async/await를 사용하는 async alternative로 변환하는 방법을 알아본다.&lt;/p&gt;
&lt;h2 id="비동기-코드-테스트하기"&gt;비동기 코드 테스트하기&lt;/h2&gt;
&lt;p&gt;async/await를 쓰면 비동기 코드도 동기 코드만큼 쉽게 테스트할 수 있다. XCTest는 &lt;code&gt;async&lt;/code&gt; 테스트를 지원한다.&lt;/p&gt;
&lt;p&gt;아래는 completion handler 방식의 비동기 코드를 테스트하는 코드이다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MockViewModelSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;XCTestCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testFetchThumbnails&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kr"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;expectation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XCTestExpectation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;mock thumbnails completion&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mockViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchThumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mockID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;XCTAssertNil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;expectation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fulfill&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;expectation&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;aside class="callout" data-callout="note"&gt;
&lt;div class="callout__header"&gt;&lt;span class="callout__icon" aria-hidden="true"&gt;i&lt;/span&gt;&lt;span class="callout__title"&gt;fulfill&lt;/span&gt;
&lt;/div&gt;
&lt;div class="callout__body"&gt;&lt;code&gt;fulfill()&lt;/code&gt;은 expectation을 충족된 상태로 표시하는 메소드다. 일반적으로 비동기 작업이 완료되었을 때 호출한다.&lt;/div&gt;
&lt;/aside&gt;
&lt;p&gt;기존에는 &lt;code&gt;XCTestExpectation&lt;/code&gt;을 만들고, API를 호출한 뒤 expectation을 fulfill 하고, 지정한 시간 동안 기다리도록 작성해야 했다.&lt;/p&gt;</description></item><item><title>Swift Concurrency. Meet async/await in Swift (1) - WWDC21</title><link>https://kelly-chui.github.io/posts/wwdc-swift-concurrency-meet-async-await-in-swift-1/</link><pubDate>Mon, 03 Aug 2026 20:03:38 +0900</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swift-concurrency-meet-async-await-in-swift-1/</guid><description>&lt;p&gt;completion handler로 작성한 비동기 코드는 쉽게 장황해지고, 복잡해지고, 부정확해진다.&lt;/p&gt;
&lt;p&gt;Swift의 async/await는 비동기 코드를 일반 코드를 작성하는 것처럼 만들어 주고, 아이디어를 더 쉽게 반영하며, 더 안전하게 만든다.&lt;/p&gt;
&lt;p&gt;async/await는 단순한 비동기 표현 방식이 아니다. 이 세션에서는 async/await가 왜 기존 completion handler 방식보다 좋은지, 그리고 Swift의 컨셉에 왜 잘 맞는지를 계속해서 설명한다.&lt;/p&gt;
&lt;h2 id="meet-asyncawait-in-swift"&gt;Meet async/await in Swift&lt;/h2&gt;
&lt;p&gt;Foundation 같은 Apple SDK에는 &lt;code&gt;await&lt;/code&gt;할 수 있는 수백 개의 메소드가 존재한다. UIKit의 &lt;code&gt;UIImage&lt;/code&gt;는 섬네일을 만드는 API를 동기 방식과 비동기 방식으로 모두 제공한다.&lt;/p&gt;</description></item><item><title>SwiftUI. Essentials - WWDC24</title><link>https://kelly-chui.github.io/posts/wwdc-swiftui-essentials/</link><pubDate>Mon, 27 Jul 2026 22:18:58 +0900</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swiftui-essentials/</guid><description>&lt;p&gt;SwiftUI는 선언형 UI 프레임워크다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rich Feature Set, 다양한 기능과 기기 고유의 이점을 활용할 수 있는 풍부한 기능 집합&lt;/li&gt;
&lt;li&gt;Less Code, 더 적은 코드&lt;/li&gt;
&lt;li&gt;Incremental adoption, 필요한 순간에 적절하게 사용 가능. 전체 앱이 반드시 SwiftUI일 필요는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="swiftui-essentials"&gt;SwiftUI Essentials&lt;/h3&gt;
&lt;p&gt;SwiftUI는 선언형 UI 프레임워크이기 때문에 UIKit 처럼 &lt;code&gt;UIView&lt;/code&gt; 객체가 계속 메모리에 상주해있고, 그 객체를 직접 변화시키는게 아니라, &lt;code&gt;View&lt;/code&gt;를 보고 SwiftUI가 그리는 방식이다. 즉, &lt;code&gt;View&lt;/code&gt;는 그냥 UI 요소를 정의하는 설계도이다. (Demistify SwiftUI 세션에서도 한 말이다!)&lt;/p&gt;</description></item><item><title>SwiftUI. Discover Observation in SwiftUI - WWDC23</title><link>https://kelly-chui.github.io/posts/wwdc-swiftui-discover-observation-in-swiftui/</link><pubDate>Fri, 24 Jul 2026 17:41:15 +0900</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swiftui-discover-observation-in-swiftui/</guid><description>&lt;p&gt;SwiftUI의 Observation은 모델의 프로퍼티 변화를 추적하고, 그 변화에 맞춰 UI를 업데이트하는 기능이다. &lt;code&gt;@Observable&lt;/code&gt; 매크로를 사용하면 별도의 &lt;code&gt;ObservableObject&lt;/code&gt;, &lt;code&gt;@Published&lt;/code&gt;, &lt;code&gt;@ObservedObject&lt;/code&gt; 없이도 일반 Swift 타입에 가까운 형태로 관찰 가능한 모델을 만들 수 있다.&lt;/p&gt;
&lt;p&gt;핵심은 간단하다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;View가 읽은 프로퍼티가 바뀌면, 그 View가 다시 계산된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 글에서는 Observation의 동작 방식, &lt;code&gt;@State&lt;/code&gt;, &lt;code&gt;@Environment&lt;/code&gt;, &lt;code&gt;@Bindable&lt;/code&gt;을 언제 써야 하는지, 그리고 기존 &lt;code&gt;ObservableObject&lt;/code&gt; 기반 코드를 &lt;code&gt;@Observable&lt;/code&gt;로 옮기는 방법을 정리한다.&lt;/p&gt;
&lt;h2 id="observation"&gt;Observation&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;@Observable&lt;/code&gt;은 Swift 매크로다. 타입에 이 매크로를 붙이면 컴파일러가 해당 타입을 관찰할 수 있도록 코드를 확장한다.&lt;/p&gt;</description></item><item><title>SwiftUI. The Swiftui Cookbook for Navigation - WWDC22</title><link>https://kelly-chui.github.io/posts/wwdc-swiftui-cookbook-for-navigation/</link><pubDate>Thu, 23 Jul 2026 22:36:54 +0900</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swiftui-cookbook-for-navigation/</guid><description>&lt;h2 id="the-swiftui-cookbook-for-navigation"&gt;The SwiftUI cookbook for navigation&lt;/h2&gt;
&lt;p&gt;SwiftUI의 새로운 내비게이션 API를 발표한 WWDC 세션을 정리했다. 이 내비게이션 API는 각각의 &lt;code&gt;NavigationLink&lt;/code&gt;가 관리하는 대신, 스택 전체의 상태를 데이터로 표현하고 직접 조작할 수 있게 만든다.&lt;/p&gt;
&lt;p&gt;이 구조를 사용하면 버튼을 눌러 이동하는 기본적인 내비게이션뿐만 아니라, 딥 링크나 특정 화면으로 바로 이동하기, 루트 화면으로 돌아가기, 현재 내비게이션 상태 저장까지 같은 모델로 처리할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New naviation API: 새로운 Data-driven 내비게이션 API&lt;/li&gt;
&lt;li&gt;Recipes for navigation: 탐색을 프로그래밍 방식으로 완전히 제어하기&lt;/li&gt;
&lt;li&gt;Persistent state: 새로운 API를 이용하여 앱의 내비게이션 상태를 유지하기&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class="callout" data-callout="note"&gt;
&lt;div class="callout__header"&gt;&lt;span class="callout__icon" aria-hidden="true"&gt;i&lt;/span&gt;&lt;span class="callout__title"&gt;갑자기 프로그래밍 방식?&lt;/span&gt;
&lt;/div&gt;
&lt;div class="callout__body"&gt;&lt;p&gt;SwiftUI는 처음부터 코드로 UI를 작성하는 프레임워크였기 때문에, 기존 &lt;code&gt;NavigationView&lt;/code&gt;도 &lt;code&gt;isActive&lt;/code&gt;나 &lt;code&gt;selection&lt;/code&gt;을 이용해 화면을 프로그래밍 방식으로 전환할 수 있었다.&lt;/p&gt;</description></item><item><title>iOS. Demystify SwiftUI (3) - WWDC21</title><link>https://kelly-chui.github.io/posts/wwdc-swiftui-demystify-swiftui-3/</link><pubDate>Wed, 22 Jul 2026 23:13:56 +0900</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swiftui-demystify-swiftui-3/</guid><description>&lt;h2 id="dependencies-swiftui는-무엇이-바뀌었는지-어떻게-알까"&gt;Dependencies: SwiftUI는 무엇이 바뀌었는지 어떻게 알까?&lt;/h2&gt;
&lt;p&gt;앞선 글에서는 SwiftUI가 View를 구분하는 방식인 Identity와, Identity가 유지되는 기간인 Lifetime을 정리했다. 이번에는 SwiftUI가 View를 업데이트하는 기준인 Dependencies를 살펴본다.&lt;/p&gt;
&lt;p&gt;먼저 결론부터 말하면 SwiftUI의 업데이트 과정은 다음과 같다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Dependency 변경
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;관련된 View 무효화
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;body 재평가
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;새로운 View Value와 이전 Value 비교
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;실제로 변경된 화면만 업데이트&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Identity는 같은 View인지 판단하고, Lifetime은 상태를 유지하며, Dependency는 어떤 View를 다시 계산해야 하는지 결정한다. 이 세 가지를 함께 이해하면 단순히 API 사용법을 외우는 것보다 SwiftUI의 동작을 더 잘 예측할 수 있다.&lt;/p&gt;</description></item><item><title>SwiftUI. Demystify SwiftUI (2) - WWDC21</title><link>https://kelly-chui.github.io/posts/wwdc-swiftui-demystify-swiftui-2/</link><pubDate>Tue, 21 Jul 2026 21:42:49 +0900</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swiftui-demystify-swiftui-2/</guid><description>&lt;h2 id="lifetime"&gt;Lifetime&lt;/h2&gt;
&lt;p&gt;1편에서는 SwiftUI가 View를 구분하는 방식인 Identity를 정리했다. 이번에는 Identity가 유지되는 시간인 Lifetime과, 그 안에서 &lt;code&gt;State&lt;/code&gt;가 어떻게 보존되는지 살펴본다.&lt;/p&gt;
&lt;p&gt;View Value는 계속 새로 만들어질 수 있지만, Identity가 유지되는 동안에는 같은 View의 Lifetime 안에 있다고 볼 수 있다. 그리고 &lt;code&gt;State&lt;/code&gt;와 &lt;code&gt;StateObject&lt;/code&gt;는 이 Lifetime에 연결되어 있기 때문에 Identity가 바뀌면 함께 교체된다.&lt;/p&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/wwdc-swiftui-demystify-swiftui-2/image-001-optimized-image.webp"&gt;&lt;p&gt;고양이가 하루 동안 이곳저곳 움직이더라도 우리는 계속 같은 고양이라고 생각한다. 이처럼 Identity는 시간에 따라 달라지는 여러 값을 하나의 안정적인 존재로 연결한다. 시간의 흐름 속에서 연속성을 만들어 주는 셈이다.&lt;/p&gt;</description></item><item><title>DevOps. 로컬 LLM 모델 설치하고 UI 붙여보기</title><link>https://kelly-chui.github.io/posts/devops-llm-building-a-local-llm-1/</link><pubDate>Tue, 21 Jul 2026 10:34:39 +0900</pubDate><guid>https://kelly-chui.github.io/posts/devops-llm-building-a-local-llm-1/</guid><description>&lt;p&gt;Codex는 iOS 개발할 때만 사용하고, 다른 작업은 주로 무료 모델로 처리했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dev-data-server-light&lt;/code&gt;를 개발할 때는 Cursor를 사용했는데, 개발 도중 사용량이 모두 소진돼 Antigravity로 옮겨야 했다.&lt;/p&gt;
&lt;p&gt;그래서 로컬에서 직접 실행하는 LLM 환경을 찾았고, 생각보다는 쉽게 환경을 구축할 수 있었다., 맥미니에서 만족스럽게 사용하고 있다. 이번 글에서는 맥북 에어에도 같은 환경을 설치한 과정을 정리한다.&lt;/p&gt;
&lt;h2 id="맥북-에어-사양"&gt;맥북 에어 사양&lt;/h2&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/devops-llm-building-a-local-llm-1/image-001-optimized-image.webp#center" width="360"&gt;&lt;h2 id="설치"&gt;설치&lt;/h2&gt;
&lt;h3 id="ollama-설치하고-서버-켜기"&gt;ollama 설치하고 서버 켜기&lt;/h3&gt;
&lt;p&gt;맥에서는 Homebrew로 쉽게 설치할 수 있다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install ollama&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/devops-llm-building-a-local-llm-1/image-002-optimized-image.webp"&gt;&lt;p&gt;설치가 끝났으면 Ollama 서버를 켜야 한다. Ollama는 단순히 실행하는 앱이 아니라, 서버 형태로 동작한다.&lt;/p&gt;</description></item><item><title>SwiftUI. Demystify SwiftUI (1) - WWDC21</title><link>https://kelly-chui.github.io/posts/wwdc-swiftui-demystify-swiftui-1/</link><pubDate>Mon, 20 Jul 2026 21:51:21 +0900</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swiftui-demystify-swiftui-1/</guid><description>&lt;aside class="callout" data-callout="note"&gt;
&lt;div class="callout__header"&gt;&lt;span class="callout__icon" aria-hidden="true"&gt;i&lt;/span&gt;&lt;span class="callout__title"&gt;시작하기 전에&lt;/span&gt;
&lt;/div&gt;
&lt;div class="callout__body"&gt;&lt;p&gt;Identity는 화면에 띄워지는 UI 요소들이 같은 요소인지, 다른 요소인지 구분하는 방식이다.&lt;/p&gt;
&lt;p&gt;이 WWDC 세션에서 가장 중요한건, View 프로토콜을 채택하여 작성하는 View Structure가 실제 화면에 보이는 요소가 아니라 그냥 그 요소의 설계도일 뿐이라는 것이다. (UIKit과 많이 다르다)&lt;/p&gt;
&lt;p&gt;Swift에서 &amp;lsquo;Value Type은 Immutablity를 가진다.&amp;rsquo; 이라는 말이 가장 크게 체감되는 부분이기도 한데, SwiftUI에서 코드로 작성한 View도 Value Type이고 당연히 Immutable하다. 따라서 한 번 생성된 값은 변하지 않아야 한다. 만약 내부의 값이 변해야 하면 UIKit의 View Controller 처럼 은근슬쩍 내부 프로퍼티만 바꾸지 않고, Value 전체를 새로 만든다.&lt;/p&gt;</description></item><item><title>Archive. System Programming 정리 노트</title><link>https://kelly-chui.github.io/posts/archive-system-programming/</link><pubDate>Mon, 29 Jun 2026 16:38:00 +0900</pubDate><guid>https://kelly-chui.github.io/posts/archive-system-programming/</guid><description>&lt;p&gt;학부연구원 시절 FPGA랑 임베디드 살짝 하면서 적어놨던 노트를 최근 클라우드 정리할 때 발견해서, 간단하게 블로그에 포스팅함.&lt;/p&gt;
&lt;h2 id="rtos"&gt;RTOS&lt;/h2&gt;
&lt;p&gt;RTOS(Real-Time Operating System)는 정해진 시간 안에 작업을 수행하는 것을 보장하도록 설계된 운영체제이다.&lt;/p&gt;
&lt;p&gt;일반적인 운영체제와 다르게 RTOS는 작업이 정해진 시간 내에 반드시 수행되는 예측 가능성을 가장 중요하게 생각한다. 산업용 장비, 자동차, 드론, 의료기기와 같은 임베디드 시스템에서 주로 사용된다.&lt;/p&gt;
&lt;h2 id="uart"&gt;UART&lt;/h2&gt;
&lt;p&gt;UART(Universal Asynchronous Receiver/Transmitter)는 가장 널리 사용되는 직렬 통신 방식 중 하나이다.&lt;/p&gt;
&lt;p&gt;구조가 단순하고 구현이 쉬워 디버깅 콘솔, 센서, 마이크로컨트롤러 간 통신 등에 자주 사용된다. 다만 송신과 수신 장치가 동일한 통신 속도(Baud Rate)를 사용해야 한다.&lt;/p&gt;</description></item><item><title>미방문록. PoC 대표 색상 추출 Picker 만들기</title><link>https://kelly-chui.github.io/posts/mibangmurok-dev-log-colorpicker/</link><pubDate>Mon, 09 Feb 2026 17:36:10 +0900</pubDate><guid>https://kelly-chui.github.io/posts/mibangmurok-dev-log-colorpicker/</guid><description>&lt;img src="https://kelly-chui.github.io/posts/mibangmurok-dev-log-colorpicker/image-001-optimized-image.webp" alt="" loading="lazy"&gt;
&lt;p&gt;음식 사진에서 색상을 뽑아서 카드의 배경색에 적용하는 기능을 구현해야 했다.&lt;/p&gt;
&lt;h2 id="처음-생각한-방향"&gt;처음 생각한 방향&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;원본 이미지를 그대로 다루면 비용이 크다.&lt;/li&gt;
&lt;li&gt;색이 너무 많으면 후보가 지저분해진다.&lt;/li&gt;
&lt;li&gt;원하는 색이 반드시 많이 나오는 색은 아니다.&lt;/li&gt;
&lt;li&gt;결과를 화면에 어떻게 보여줄지도 같이 고민해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="직접-구현해보자"&gt;직접 구현해보자.&lt;/h2&gt;
&lt;p&gt;외부 라이브러리를 쓰기보다는, 가능한 경우에는 항상 먼저 구현해보고 그 이후에 외부 라이브러리를 쓰는 방향을 선호하기 때문에, 직접 구현해보기로 했다.&lt;/p&gt;
&lt;p&gt;학부 시절에 이미지 프로세싱을 하기도 했고 나름대로 흥미롭게 접근했다.&lt;/p&gt;
&lt;h3 id="coregraphics에서-픽셀-읽기"&gt;CoreGraphics에서 픽셀 읽기&lt;/h3&gt;
&lt;p&gt;색상을 추출하려면 결국 이미지의 픽셀 값에 접근해야 했다.&lt;/p&gt;</description></item><item><title>Archive. NVIDIA Physical AI 생태계 정리 노트</title><link>https://kelly-chui.github.io/posts/archive-physical-ai/</link><pubDate>Fri, 06 Feb 2026 01:39:00 +0900</pubDate><guid>https://kelly-chui.github.io/posts/archive-physical-ai/</guid><description>&lt;p&gt;최근 NVIDIA의 피지컬 AI 생태계를 접할 기회가 있었는데, Omniverse, Isaac Sim, Cosmos, GR00T처럼 처음 보는 용어가 한꺼번에 등장해서 각각이 어떤 역할을 하는지 궁금해졌다.&lt;/p&gt;
&lt;p&gt;조금 찾아보니 개별 기술을 각각 봐서 이해하긴 어렵고, 전체 구조를 보면서 어떤 역할을 하는지 이해하는 것이 중요하다는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;그래서 나중에 다시 봐도 흐름을 빠르게 떠올릴 수 있도록 핵심 개념을 간단히 정리했다.&lt;/p&gt;
&lt;h2 id="피지컬-ai"&gt;피지컬 AI&lt;/h2&gt;
&lt;p&gt;현실 세계를 인식 → 판단 → 물리적으로 행동하는 AI 시스템 전체를 의미한다. 디지털 AI와 달리 센서 불확실성, 실시간 제어, 물리 상호작용이 핵심 난이도다. (현직자도 단순히 소프트웨어로 처리하는 것 만으로는 충분하지 않다고 했다.)&lt;/p&gt;</description></item><item><title>Algorithm. Kadane's Algorithm</title><link>https://kelly-chui.github.io/posts/cs-algorithm-kadanes-algorithm/</link><pubDate>Tue, 25 Nov 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-algorithm-kadanes-algorithm/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;1차원 배열이 주어졌을 때, 연속된 구간의 최대 합을 구하는 알고리즘을 카데인 알고리즘이라고 한다.&lt;/p&gt;
&lt;p&gt;예를 들어 다음 배열이 있다면,&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[-2, 1, -3, 4, -1, 2, 1, -5, 4]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;연속된 구간 &lt;code&gt;[4, -1, 2, 1]&lt;/code&gt;의 합인 &lt;code&gt;6&lt;/code&gt;이 가장 크다.&lt;/p&gt;
&lt;p&gt;카데인 알고리즘은 앞에서 계산한 결과를 재활용하여 배열을 한 번만 순회한다. 따라서 시간 복잡도 &lt;code&gt;O(n)&lt;/code&gt;, 공간 복잡도 &lt;code&gt;O(1)&lt;/code&gt;로 문제를 해결할 수 있다.&lt;/p&gt;
&lt;h2 id="아이디어"&gt;아이디어&lt;/h2&gt;
&lt;p&gt;현재 원소에서 끝나는 연속 구간의 최대 합을 유지하고 다음 원소를 만났을 때 기존 구간을 이어가는 것과 현재 원소부터 새로운 구간을 시작하는 것 중 더 큰 값을 선택한다.&lt;/p&gt;</description></item><item><title>Docker. Dockerfile 작성하기</title><link>https://kelly-chui.github.io/posts/devops-docker-04-writing-dockerfile/</link><pubDate>Thu, 09 Oct 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devops-docker-04-writing-dockerfile/</guid><description>&lt;p&gt;컨테이너를 실행하려면 먼저 이미지가 필요하고, 이미지는 &lt;code&gt;Dockerfile&lt;/code&gt;이라는 파일을 통해 생성한다.&lt;/p&gt;
&lt;p&gt;예제 Dockerfile을 기준으로 각 명령어가 어떤 역할을 하는지 살펴본다.&lt;/p&gt;
&lt;h2 id="dockerfile-구조-살펴보기"&gt;Dockerfile 구조 살펴보기&lt;/h2&gt;
&lt;p&gt;먼저 예제 Dockerfile을 보자.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-docker" data-lang="docker"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.13&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/usr/local/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Install the application dependencies&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; requirements.txt ./&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; pip install --no-cache-dir -r requirements.txt&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Copy in the source code&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; src ./src&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;8080&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Setup an app user so the container doesn&amp;#39;t run as the root user&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; useradd app&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CMD&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;uvicorn&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;app.main:app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--host&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.0.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--port&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;8080&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이제 위 Dockerfile을 한 줄씩 살펴보자.&lt;/p&gt;</description></item><item><title>Docker. 내 애플리케이션 Dockerizing 하기</title><link>https://kelly-chui.github.io/posts/devops-docker-06-dockerizing/</link><pubDate>Thu, 09 Oct 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devops-docker-06-dockerizing/</guid><description>&lt;h2 id="0-기반-파일"&gt;0. 기반 파일&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/nice-card/express-example"&gt;https://github.com/nice-card/express-example&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="1-프로젝트-위치-확인"&gt;1. 프로젝트 위치 확인&lt;/h2&gt;
&lt;p&gt;현재 경로를 프로젝트 경로로 설정하고 확인하기&lt;/p&gt;
&lt;img alt="image.png" loading="lazy" src="https://kelly-chui.github.io/posts/devops-docker-06-dockerizing/image-001-optimized-image.webp"&gt;&lt;h2 id="2--dockerfile-dockerignore-추가"&gt;2. Dockerfile, .dockerignore 추가&lt;/h2&gt;
&lt;p&gt;프로젝트 루트에 Dockerfile을 새로 만들기.&lt;/p&gt;
&lt;h3 id="dockerfile"&gt;Dockerfile&lt;/h3&gt;
&lt;p&gt;프로젝트 루트에 만들기.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-docker" data-lang="docker"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# === build stage ===&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; package*.json ./&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; npm ci &lt;span class="c1"&gt;# dev 포함 (tsc 사용)&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; . .&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; npm run build &lt;span class="c1"&gt;# dist/ 생성&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# === runtime stage ===&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; package*.json ./&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; npm ci --omit&lt;span class="o"&gt;=&lt;/span&gt;dev &lt;span class="c1"&gt;# 런타임에 필요한 것만&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 빌드 결과만 가져오기&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --from&lt;span class="o"&gt;=&lt;/span&gt;build /app/dist ./dist&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3000&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CMD&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;npm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;start&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# package.json의 &amp;#34;start&amp;#34;: &amp;#34;node dist/index.js&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="dockerignore"&gt;.dockerignore&lt;/h3&gt;
&lt;p&gt;불필요한 파일이 이미지에 안 들어가게 막는 용도&lt;/p&gt;</description></item><item><title>Docker. 이미지 Build하고 Push하기</title><link>https://kelly-chui.github.io/posts/devops-docker-05-build-and-push/</link><pubDate>Thu, 09 Oct 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devops-docker-05-build-and-push/</guid><description>&lt;h2 id="사전준비하기--docker-hub"&gt;사전준비하기 &amp;amp; Docker Hub&lt;/h2&gt;
&lt;h3 id="docker-hub"&gt;Docker Hub&lt;/h3&gt;
&lt;p&gt;Docker 이미지를 공유하기 위해서는 이미지를 저장할 공간이 필요하고, 이 때 Docker Hub가 이미지 저장소로 사용된다. 다른 사람들이 올린 이미지를 검색하여 실행하거나 자신의 이미지를 기반으로 사용할 수 있는 공간을 제공한다.&lt;/p&gt;
&lt;h3 id="컨테이너-이미지"&gt;컨테이너 이미지&lt;/h3&gt;
&lt;p&gt;컨테이너 이미지는 애플리케이션을 실행하기 위한 파일, config, 종속성을 모두 포함한 표준화된 패키지이다. 이미지를 배포하면 다른 사람과 공유할 수 있다.&lt;/p&gt;
&lt;h2 id="docker-hub에-이미지-푸시하기"&gt;Docker Hub에 이미지 푸시하기&lt;/h2&gt;
&lt;p&gt;GitHub가 소스 코드를 저장하는 것처럼 Docker Hub는 컨테이너 이미지를 저장한다.&lt;/p&gt;</description></item><item><title>Docker. Docker Desktop 실습해보기</title><link>https://kelly-chui.github.io/posts/devops-docker-03-docker-desktop-lab/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devops-docker-03-docker-desktop-lab/</guid><description>&lt;h2 id="설치하기"&gt;설치하기&lt;/h2&gt;
&lt;p&gt;공식 문서에 설치 가이드 설명 잘 되어있으니 알아서&amp;hellip;&lt;/p&gt;
&lt;h2 id="첫-번째-컨테이너-실행하기"&gt;첫 번째 컨테이너 실행하기&lt;/h2&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d -p 8080:80 docker/welcome-to-docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt; 에서 이 컨테이너의 프론트엔드 방문 가능.&lt;/p&gt;
&lt;img alt="image.png" loading="lazy" src="https://kelly-chui.github.io/posts/devops-docker-03-docker-desktop-lab/image-001-optimized-image.webp"&gt;&lt;h2 id="docker-desktop-사용해보기"&gt;Docker Desktop 사용해보기&lt;/h2&gt;
&lt;img alt="image.png" loading="lazy" src="https://kelly-chui.github.io/posts/devops-docker-03-docker-desktop-lab/image-002-optimized-image.webp"&gt;&lt;h3 id="컨테이너-탭"&gt;컨테이너 탭&lt;/h3&gt;
&lt;img alt="image.png" loading="lazy" src="https://kelly-chui.github.io/posts/devops-docker-03-docker-desktop-lab/image-006-optimized-image.webp"&gt;&lt;ul&gt;
&lt;li&gt;Logs — &lt;code&gt;docker logs &amp;lt;container-name&amp;gt;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;컨테이너 내부에서 실행 중인 프로세스의 표준 출력을 그대로 보여줌.&lt;/li&gt;
&lt;li&gt;디버깅할 때 많이 사용하는 부분&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Inspect — &lt;code&gt;docker inspect &amp;lt;container-name&amp;gt;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;컨테이너의 환경 변수, 포트 바인딩, 실행 명령, 마운트 경로, 네트워크, 상태 등 메타데이터를 JSON으로 보여줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Bind mounts
&lt;ul&gt;
&lt;li&gt;로컬 호스트와 컨테이너 사이의 폴더 연결 정보를 보여줌
&lt;ul&gt;
&lt;li&gt;ex) &lt;code&gt;-v ~/project:/app&lt;/code&gt; → 내 컴퓨터의 &lt;code&gt;~/project&lt;/code&gt; 폴더가 컨테이너 내부 &lt;code&gt;/app&lt;/code&gt;에 연결됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;로컬 파일 수정 시 컨테이너에 즉시 반영되기 때문에 개발할 때 자주 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Exec — &lt;code&gt;docker exec -it &amp;lt;container-name&amp;gt;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;실행 중인 컨테이너 내부에 직접 명령어를 입력할 수 있는 쉘 세션을 엶.&lt;/li&gt;
&lt;li&gt;컨테이너 내부 구조 탐색, 빠른 테스트, 수동 디버깅에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Files — &lt;code&gt;docker cp&lt;/code&gt;, &lt;code&gt;docker exec ls&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;컨테이너 내부의 파일 시스템 탐색&lt;/li&gt;
&lt;li&gt;호스트 ↔ 컨테이너 간 파일을 복사할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stats: &lt;code&gt;docker stats &amp;lt;container-name&amp;gt;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;컨테이너의 실시간 리소스 사용량(CPU, 메모리, 네트워크, I/O) 등을 모니터링&lt;/li&gt;
&lt;li&gt;성능 문제나 리소스 병목을 추적할 때 사용&lt;/li&gt;
&lt;li&gt;여러 컨테이너 동시 모니터링 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="직접-컨테이너를-사용해서-개발해보기"&gt;직접 컨테이너를 사용해서 개발해보기&lt;/h2&gt;
&lt;h3 id="0-프로젝트-클론"&gt;0. 프로젝트 클론&lt;/h3&gt;
&lt;p&gt;우선 Docker에서 제공하는 예제 앱 가져오기&lt;/p&gt;</description></item><item><title>Docker. Docker 시작하기</title><link>https://kelly-chui.github.io/posts/devops-docker-01-getting-started/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devops-docker-01-getting-started/</guid><description>&lt;h2 id="docker"&gt;Docker&lt;/h2&gt;
&lt;p&gt;애플리케이션 개발, 배포 및 실행을 위한 개방형 플랫폼&lt;/p&gt;
&lt;h2 id="특징"&gt;특징&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Note.&lt;/p&gt;
&lt;p&gt;컨테이너는 개발 환경 자체가 아니라 애플리케이션를 실행하는 격리된 프로세스다. 일반적으로 개발은 호스트 환경에서 이루어지고, 컨테이너는 실행 및 테스트 환경으로 사용된다.&lt;/p&gt;
&lt;p&gt;-&amp;gt; 그런데 vscode에서 쓰는 dev container는&amp;hellip;?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="컨테이너를-사용한-앱-개발"&gt;컨테이너를 사용한 앱 개발&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Docker 컨테이너 안에서 앱을 띄우기 때문에 “환경이 달라서 동작하지 않는다”와 같은 문제 해결&lt;/li&gt;
&lt;li&gt;ex. Node, Python, MySQL같은 것을 도커로 묶어서 개발&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="컨테이너는-완전한-실행-단위"&gt;컨테이너는 완전한 실행 단위&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;컨테이너 하나는 완전한 실행 단위이기 때문에 QA, 테스트, CI/CD 환경에서 그대로 사용&lt;/li&gt;
&lt;li&gt;같은 이미지 → 같은 코드, 같은 환경 → 어디서든 일관된 실행이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="애플리케이션을-컨테이너-형태로-배포"&gt;애플리케이션을 컨테이너 형태로 배포&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;클라우드나 로컬 서버로 그대로 옮겨서 실행하면 된다.&lt;/li&gt;
&lt;li&gt;수명 주기 관리
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;build&lt;/code&gt;, &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;stop&lt;/code&gt;, &lt;code&gt;rm&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt; 모든 것을 Docker가 명령어 단위로 관리해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="반응형-배포-및-확장"&gt;반응형 배포 및 확장&lt;/h3&gt;
&lt;p&gt;Docker는 어디서나 똑같이 돌고, 원하면 즉시 늘리거나 줄일 수 있다.&lt;/p&gt;</description></item><item><title>Docker. 컨테이너와 이미지</title><link>https://kelly-chui.github.io/posts/devops-docker-02-containers-and-images/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devops-docker-02-containers-and-images/</guid><description>&lt;h2 id="컨테이너"&gt;컨테이너&lt;/h2&gt;
&lt;p&gt;애플리케이션의 구성 요소(프론트엔드, API, DB 등)을 각각 격리된 프로세스로 실행하는 환경.&lt;/p&gt;
&lt;h3 id="특징"&gt;특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;호스트의 어떤 환경에도 의존하지 않고, 필요한 라이브러리와 설정을 모두 포함한 독립된 환경으로 존재한다.&lt;/li&gt;
&lt;li&gt;서로 다른 컨테이너는 격리되어 있어서 하나가 망가져도 다른 컨테이너에 영향을 주지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="컨테이너-vs-vm"&gt;컨테이너 vs. VM&lt;/h3&gt;
&lt;p&gt;VM은 자체 커널, 드라이버, 프로그램을 갖춘 완전한 운영체제이기 때문에, 단일 애플리케이션을 분리하기 위해 VM을 사용하는 것은 오버헤드가 크다.&lt;/p&gt;
&lt;p&gt;컨테이너는 실행에 필요한 모든 파일을 갖춘 고립된 프로세스이다. 여러 컨테이너를 실행하더라도 모두 동일한 커널을 공유하므로, 더 적은 인프라에서 많은 애플리케이션을 실행할 수 있다.&lt;/p&gt;</description></item><item><title>Node. TypeScript 프로젝트 초기화하기</title><link>https://kelly-chui.github.io/posts/node-typescript-project-initialization/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/node-typescript-project-initialization/</guid><description>&lt;h2 id="새로운-노드-프로젝트-생성하기"&gt;새로운 노드 프로젝트 생성하기&lt;/h2&gt;
&lt;h3 id="npm-초기화부터"&gt;npm 초기화부터&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm init -y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Node 프로젝트를 초기화한다. 이 명령을 실행하면 현재 디렉터리에 &lt;code&gt;package.json&lt;/code&gt; 파일이 생성되고, Node와 npm은 이를 기준으로 프로젝트를 관리한다.&lt;/p&gt;
&lt;h3 id="npm-install"&gt;npm install&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install -D typescript tsx @types/node @types/express&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm install&lt;/code&gt;: 필요한 패키지를 설치하는 명령이다. 뒤에 패키지 이름을 지정하면 해당 패키지를 설치하고, &lt;code&gt;package.json&lt;/code&gt;에도 자동으로 기록한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-D&lt;/code&gt;: &lt;code&gt;--save-dev&lt;/code&gt;의 약자이다. 개발 과정에서만 사용하는 패키지를 &lt;code&gt;devDependencies&lt;/code&gt;에 추가한다. TypeScript처럼 빌드 과정에서만 필요한 도구들이 여기에 해당한다.&lt;/li&gt;
&lt;li&gt;패키지
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;typescript&lt;/code&gt;: TS 컴파일러 (tsc 명령어 제공)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tsx&lt;/code&gt;: TS 파일을 빌드 없이 바로 실행하게 해주는 도구 (dev 서버용)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@types/node&lt;/code&gt;: Node.js의 타입 정의&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@types/express&lt;/code&gt;: Express 라이브러리의 타입 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Note.&lt;/p&gt;</description></item><item><title>JavaScript. 모듈 시스템</title><link>https://kelly-chui.github.io/posts/javascript-module-system/</link><pubDate>Tue, 07 Oct 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/javascript-module-system/</guid><description>&lt;p&gt;프로젝트가 커질수록 모든 코드를 하나의 파일에 작성하기는 어렵다. 그래서 JavaScript는 기능별로 코드를 여러 파일로 나누어 관리하며, 각각의 파일을 모듈(Module)이라고 한다.&lt;/p&gt;
&lt;p&gt;모듈 시스템은 이러한 모듈을 다른 파일에서 가져오고 내보내는 방법을 정의한다.&lt;/p&gt;
&lt;h2 id="주요-모듈-시스템"&gt;주요 모듈 시스템&lt;/h2&gt;
&lt;h3 id="commonjs-cjs"&gt;CommonJS (CJS)&lt;/h3&gt;
&lt;p&gt;CommonJS는 Node.js 초창기부터 사용된 모듈 시스템이다. &lt;code&gt;require&lt;/code&gt;로 다른 모듈을 가져오고, &lt;code&gt;module.exports&lt;/code&gt;로 외부에 공개할 값을 내보낸다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// math.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsx" data-lang="jsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// main.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;math&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./math&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;위 예시에서 &lt;code&gt;module.exports&lt;/code&gt;는 &lt;code&gt;add&lt;/code&gt; 함수를 다른 파일에서도 사용할 수 있도록 내보낸다. &lt;code&gt;main.js&lt;/code&gt;에서는 &lt;code&gt;require&lt;/code&gt;를 통해 해당 모듈을 가져와 &lt;code&gt;math.add()&lt;/code&gt;를 호출한다.&lt;/p&gt;</description></item><item><title>Node. Node.js 소개</title><link>https://kelly-chui.github.io/posts/node-nodejs-introduction/</link><pubDate>Sun, 05 Oct 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/node-nodejs-introduction/</guid><description>&lt;p&gt;Node.js는 브라우저 밖에서도 JavaScript를 실행할 수 있게 해주는 런타임이다.&lt;/p&gt;
&lt;h2 id="구조"&gt;구조&lt;/h2&gt;
&lt;p&gt;Node.js를 설치하면 JavaScript 런타임 하나만 설치되는 것이 아닌, 프로젝트 관리, 외부 라이브러리, 개발 도구를 실행하기 위한 여러 구성 요소가 함께 설치된다.&lt;/p&gt;
&lt;h3 id="node"&gt;Node&lt;/h3&gt;
&lt;p&gt;JavaScript 실행 환경이다. 브라우저 외부에서 JS를 실행할 수 있게 해준다.&lt;/p&gt;
&lt;p&gt;간단한 파일은 다음처럼 직접 실행할 수도 있다. (다른 언어들과 비슷하다.)&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node &amp;lt;FILE-NAME&amp;gt;.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="npmnode-package-manager"&gt;npm(Node Package Manager)&lt;/h3&gt;
&lt;p&gt;Node 프로젝트의 패키지 관리자이다. 외부 라이브러리를 설치하고, 제거하고, 프로젝트의 의존성을 관리한다.&lt;/p&gt;
&lt;p&gt;외부 라이브러리를 설치할 때는 다음 명령을 사용한다.&lt;/p&gt;</description></item><item><title>SwiftUI 상태 관리</title><link>https://kelly-chui.github.io/posts/swiftui-swiftui-property-wrapper/</link><pubDate>Thu, 25 Sep 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/swiftui-swiftui-property-wrapper/</guid><description>&lt;h2 id="상태를-누가-소유하고-어떻게-전달해야-하는가"&gt;상태를 누가 소유하고 어떻게 전달해야 하는가&lt;/h2&gt;
&lt;p&gt;SwiftUI에서는 상태를 저장하거나 다른 뷰에 전달할 때 프로퍼티 래퍼를 사용한다.&lt;/p&gt;
&lt;p&gt;이 상태를 현재 뷰가 직접 가지고 있는지, 부모 뷰에서 전달받는지, 아니면 별도의 객체가 관리하는지에 따라 사용하는 래퍼가 달라진다.&lt;/p&gt;
&lt;h3 id="상위-뷰--하위-뷰"&gt;상위 뷰 → 하위 뷰&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@State → @Binding&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;상위 뷰가 상태를 소유하고, 하위 뷰는 해당 상태를 수정할 수 있도록 &lt;code&gt;@Binding&lt;/code&gt;으로 전달받는다.&lt;/p&gt;
&lt;h3 id="뷰-모델--뷰--하위-뷰"&gt;뷰 모델 → 뷰 → 하위 뷰&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ObservableObject&lt;/code&gt; 프로토콜을 채택한 뷰 모델에서는, 변경을 알려야 하는 값을 &lt;code&gt;@Published&lt;/code&gt; 프로퍼티로 선언한다.(이 뷰 모델을 뷰에서 주입받아 사용할 때는 보통 &lt;code&gt;@ObservedObject&lt;/code&gt;로 관찰함)&lt;/p&gt;</description></item><item><title>Swift Concurrency. Behind the Scenes - WWDC21</title><link>https://kelly-chui.github.io/posts/wwdc-swift-concurrency-behind-the-scenes/</link><pubDate>Mon, 25 Aug 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-swift-concurrency-behind-the-scenes/</guid><description>&lt;h2 id="threading-model"&gt;Threading model&lt;/h2&gt;
&lt;p&gt;뉴스 피드 리더 앱을 예로 들어보자. 앱의 상위 컴포넌트는 다음과 같이 나눌 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자 인터페이스를 담당하는 메인 스레드&lt;/li&gt;
&lt;li&gt;사용자가 구독한 뉴스 피드를 추적하는 데이터베이스&lt;/li&gt;
&lt;li&gt;최신 콘텐츠를 가져오는 네트워킹 서브시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;사용자가 최신 뉴스를 요청하면 다음과 같은 흐름으로 처리할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;메인 스레드에서 이벤트 제스처 처리&lt;/li&gt;
&lt;li&gt;데이터베이스 작업을 처리하는 직렬 큐로 요청을 비동기적으로 디스패치
&lt;ul&gt;
&lt;li&gt;작업을 다른 큐로 보내면 메인 스레드는 데이터베이스 작업을 기다리지 않고 사용자 입력에 계속 반응할 수 있다.&lt;/li&gt;
&lt;li&gt;직렬 큐가 상호 배제를 보장하므로 데이터베이스 접근을 순서대로 처리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터베이스 큐에선 사용자가 구독한 피드에 URLSession을 통해 콘텐츠를 다운로드하는 네트워크 요청을 스케줄링&lt;/li&gt;
&lt;li&gt;결과가 도착하면 URLSession의 콜백이 Delegate Queue 위에서 호출되어서 각 결과의 Completion Handler가 동기적으로 데이터베이스를 업데이트한다.&lt;/li&gt;
&lt;li&gt;MainThread를 깨워서 UI를 리프레시한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;보기에는 합리적인 방법이다. 메인 스레드를 블록하지 않으면서 네트워크 요청을 동시에 처리할 수 있기 때문이다.&lt;/p&gt;</description></item><item><title>Swift Concurrency. 01. 도입 배경</title><link>https://kelly-chui.github.io/posts/swift-concurrency-01-introduction/</link><pubDate>Sun, 03 Aug 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/swift-concurrency-01-introduction/</guid><description>&lt;p&gt;Swift Concurrency는 Swift 5.5에서 도입된 기능이다. &lt;code&gt;Foundation&lt;/code&gt;을 import 해야 쓸 수 있는 GCD와 다르게, Swift 언어 자체에 내장된 동시성 모델이다.&lt;/p&gt;
&lt;p&gt;Swift Concurrency는 크게 두 가지 부분으로 나뉘어진다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;async&lt;/code&gt;, &lt;code&gt;await&lt;/code&gt; 로 작성하는 새로운 동시성 모델&lt;/li&gt;
&lt;li&gt;애플리케이션을 여러 동시성 태스크로 분할하는 Actor&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 포스트에서는 Swift Concurrency의 도입 배경을 알아본다.&lt;/p&gt;
&lt;h2 id="gcd의-한계"&gt;GCD의 한계&lt;/h2&gt;
&lt;p&gt;GCD는 Objective-C에서부터 사용해온 애플이 만든 비동기 API다. Queue 기반으로 작업을 스케줄링 하고, 스레드 위에서 추상화 되어있기 때문에 스레드를 직접 생성하거나 관리할 필요가 없다.&lt;/p&gt;</description></item><item><title>iOS. 파일 시스템과 샌드박스</title><link>https://kelly-chui.github.io/posts/ios-file-system-sandbox/</link><pubDate>Mon, 21 Jul 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/ios-file-system-sandbox/</guid><description>&lt;p&gt;앱은 종료된 뒤에도 데이터를 유지하기 위해 파일 시스템을 사용한다. iOS, macOS 같은 애플 플랫폼에서는 APFS(Apple File System)라는 파일 시스템을 쓴다. 애플은 사용자 파일과 앱 내부 파일을 분리해서 관리하는 것을 목표로 한다.&lt;/p&gt;
&lt;p&gt;개발자 문서에서는 파일 시스템의 목표를 다음과 같이 제시한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자 파일은 쉽게 찾을 수 있도록 한다.&lt;/li&gt;
&lt;li&gt;앱 내부적으로 사용하는 파일은 사용자 눈에 띄지 않도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;많은 파일 시스템처럼 APFS도 디렉토리 구조를 통해 계층적으로 파일을 관리한다. 모든 디스크가 &amp;lsquo;단일 파일 컬렉션&amp;rsquo;을 구성한다는 점이 특징이다. 즉, 여러 디스크를 연결해도 사용자에게는 하나의 계층에 있는 것처럼 보인다.&lt;/p&gt;</description></item><item><title>Algorithm. Bit masking</title><link>https://kelly-chui.github.io/posts/cs-algorithm-bit-masking/</link><pubDate>Thu, 12 Jun 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-algorithm-bit-masking/</guid><description>&lt;h2 id="기본-소개"&gt;기본 소개&lt;/h2&gt;
&lt;p&gt;비트 마스킹은 비트 연산을 활용해서 집합을 구현하는 방법이다. 각 자리수의 비트는 하나의 원소를 나타내며, 이 원소들이 모여서 집합을 구성하게 된다.&lt;/p&gt;
&lt;p&gt;예를 들어, 원소 A, B, C, D를 각각 다음과 같이 표현할 수 있다:&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mb"&gt;0b0001&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mb"&gt;0b0010&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mb"&gt;0b0100&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mb"&gt;0b1000&lt;/span&gt; &lt;span class="c1"&gt;// 8&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;혹은 쉬프트 연산을 활용해서 표현할 수도 있다. 이 방법이 조금 더 직관적이고 단순하다:&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이렇게 원소들을 정의하게 되면, 각 원소들이 각각의 자릿수를 점유하고 있기 때문에 겹칠 일이 없다. 따라서 2진수로 쉽게 집합을 표현할 수 있다.&lt;/p&gt;</description></item><item><title>Call by reference와 Call by pointer의 차이</title><link>https://kelly-chui.github.io/posts/cs-programming-language-call-by-reference-call-by-pointer/</link><pubDate>Sat, 07 Jun 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-programming-language-call-by-reference-call-by-pointer/</guid><description>&lt;p&gt;Call by reference와 call by pointer는 모두 호출자가 가진 객체를 함수 안에서 변경할 수 있어 비슷해 보인다. 하지만 C++ 언어에서 파라미터가 가지는 의미와 사용할 수 있는 연산은 다르다.&lt;/p&gt;
&lt;h2 id="pointer를-값으로-전달하기"&gt;Pointer를 값으로 전달하기&lt;/h2&gt;
&lt;p&gt;포인터 파라미터는 주소를 저장한 포인터 객체를 값으로 전달받는다. 함수는 복사된 포인터를 역참조해 호출자의 객체를 변경할 수 있다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateByPointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;updateByPointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;포인터 자체는 복사본이므로 함수 안에서 다른 주소를 대입해도 호출자가 가진 포인터 변수는 바뀌지 않는다.&lt;/p&gt;</description></item><item><title>SniffMEET. Xcode 프로젝트 파일 정리하기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-xcode-project-cleanup/</link><pubDate>Thu, 05 Jun 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-xcode-project-cleanup/</guid><description>&lt;h2 id="작업-내역"&gt;작업 내역&lt;/h2&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/sniffmeet-dev-log-xcode-project-cleanup/image-001-optimized-image.webp"&gt;&lt;p&gt;Xcode에서 프로젝트 파일 관련 경고가 많이 떠서 정리했다.&lt;/p&gt;
&lt;h2 id="작업-내역-1"&gt;작업 내역&lt;/h2&gt;
&lt;h3 id="null-파일-제거"&gt;null 파일 제거&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;320043702CDC9A0D00D08B6D /* (null) in Sources */ = {isa = PBXBuildFile; };
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;320043722CDC9E5F00D08B6D /* (null) in Sources */ = {isa = PBXBuildFile; };
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;320043762CDCA18E00D08B6D /* (null) in Sources */ = {isa = PBXBuildFile; };
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;320043782CDCA49100D08B6D /* (null) in Sources */ = {isa = PBXBuildFile; };
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3200437C2CDCA6F300D08B6D /* (null) in Sources */ = {isa = PBXBuildFile; };&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이전에 있었던 구조 변경이나, git 충돌/머지 등 다양한 이유로 프로젝트 파일 안에 &lt;code&gt;(null)&lt;/code&gt; 파일을 참조하는 라인이 많이 있었다.&lt;/p&gt;</description></item><item><title>Swift. C부터 시작하는 메소드 디스패치</title><link>https://kelly-chui.github.io/posts/swift-method-dispatch/</link><pubDate>Sat, 31 May 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/swift-method-dispatch/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;메소드 호출이란 과연 무엇일까? 우리가 평소에 사용하는 메소드 호출은 내부적으로 어떻게 동작하는지 궁금해진 적이 있다. C++로 PS를 풀 때 가끔 함수 포인터를 써야 하는 경우가 생기는데, Swift에서 클로저를 이용하는 것과 비슷하지만 주변 값을 캡처할 수 없다는 특징이 있다. 함수 포인터는 C에도 존재하는 개념이므로, 다음과 같이 생각이 뻗어나갔다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;함수 포인터와 C 스타일 스트럭처를 사용하면 C언어에서도 OOP 스타일의 메소드가 구현 가능할 것 같다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;생각이 계속 확장되다 보니 실제 OOP 언어의 클래스 구현도 이런 방식으로 되어있지 않을까? 라는 생각에 도달했고, 메소드 디스패치를 포함해서 여러 개념들을 이어서 생각해볼 수 있었다.&lt;/p&gt;</description></item><item><title>OS. Operating System 오버뷰</title><link>https://kelly-chui.github.io/posts/cs-os-overview/</link><pubDate>Sat, 17 May 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-os-overview/</guid><description>&lt;h2 id="운영체제os"&gt;운영체제(OS)&lt;/h2&gt;
&lt;p&gt;컴퓨터 하드웨어와 사용자 사이에서 중간 역할을 수행하며, 하드웨어 자원을 효율적이고 안전하게 관리하고, 사용자 프로그램이 원활하게 실행되도록 지원하는 시스템 소프트웨어&lt;/p&gt;
&lt;h3 id="커널"&gt;커널&lt;/h3&gt;
&lt;p&gt;운영체제의 핵심 부분, 하드웨어와 직접 상호작용 하여, 자원 관리와 시스템 콜 처리를 담당하는 코드 영역, 일반적으로 운영체제는 커널을 지칭한다.&lt;/p&gt;
&lt;h2 id="역할"&gt;역할&lt;/h2&gt;
&lt;p&gt;운영체제는 CPU, 메모리 등의 하드웨어를 효율적으로 사용할 수 있도록 관리하고, 애플리케이션이 자원을 효율적으로 할당받아 문제 없이 실행될 수 있도록 한다.&lt;/p&gt;
&lt;h3 id="cpu-관리cpu-스케줄링"&gt;CPU 관리(CPU 스케줄링)&lt;/h3&gt;
&lt;p&gt;CPU가 실행 중인 프로그램을 모두 동시에 실행할 수 있는 것은 아니므로, 프로그램들이 효율적으로 CPU를 할당받도록 할당 순서와 사용 시간을 결정하는 역할&lt;/p&gt;</description></item><item><title>DB. Database 오버뷰.</title><link>https://kelly-chui.github.io/posts/cs-db-overview/</link><pubDate>Sat, 03 May 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-db-overview/</guid><description>&lt;h2 id="데이터베이스database-db"&gt;데이터베이스(Database, DB)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;여러 사람이 공동으로 사용하기 위해 통합, 저장하여 운영하는 데이터들의 집합&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;특정 조직(서비스)의 업무를 수행하는데 필요한 상호 관련된 데이터들의 모임이다.&lt;/p&gt;
&lt;h3 id="dbmsdatabase-management-system"&gt;DBMS(Database Management System)&lt;/h3&gt;
&lt;p&gt;데이터베이스를 관리하기 위한 프로그램, 대표적으로 SQL을 사용하는 관계형 데이터베이스(RDBMS)가 있고, SQL을 사용하지 않는 NoSQL DBMS도 있다.&lt;/p&gt;
&lt;h2 id="관계형-데이터베이스relational-db-rdb"&gt;관계형 데이터베이스(Relational DB, RDB)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;2차원 표를 이용해서 데이터간의 상호 관계를 정의하는 데이터베이스&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="스키마schema"&gt;스키마(Schema)&lt;/h3&gt;
&lt;p&gt;데이터베이스의 구조와 제약 조건에 대한 전반적인 명세. 엔티티, 어트리뷰트, 릴레이션, 제약 조건 등을 전반적으로 정의한 DB의 구조적 정의&lt;/p&gt;</description></item><item><title>Algorithm. LIS(Subsequence)</title><link>https://kelly-chui.github.io/posts/cs-algorithm-lis-subsequence/</link><pubDate>Thu, 03 Apr 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-algorithm-lis-subsequence/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;수열의 원소를 골라내서 만든 부분 수열 중, 각 원소가 이전 원소보다 크면서, 가장 긴 길이를 가지는 부분 수열을 찾는 알고리즘&lt;/p&gt;
&lt;p&gt;DP 방식과 Binary Search를 쓰는 Greedy 방식 두 가지가 있으며, 별개의 방식이 아닌 두 방식이 밀접하게 연관되어 있다.&lt;/p&gt;
&lt;h2 id="아이디어"&gt;아이디어&lt;/h2&gt;
&lt;p&gt;LIS는 현재까지 구한 부분 수열의 결과를 이용해 더 긴 부분 수열을 만들어 나가는 문제이다.&lt;/p&gt;
&lt;p&gt;DP는 이전 계산 결과를 이용해 현재 상태를 구하며, Binary Search를 이용한 방식은 같은 아이디어를 유지하면서 탐색 과정을 최적화한 것이다.&lt;/p&gt;</description></item><item><title>Software Engineering. DI와 DIP</title><link>https://kelly-chui.github.io/posts/se-dependency-injection/</link><pubDate>Thu, 27 Mar 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/se-dependency-injection/</guid><description>&lt;h2 id="의존성-주입dependency-injection"&gt;의존성 주입(Dependency Injection)&lt;/h2&gt;
&lt;p&gt;의존성 주입은 객체가 필요한 의존성을 내부에서 직접 생성하지 않고 외부에서 전달받는 방식이다. 별도의 DI 프레임워크나 주입 전용 객체가 반드시 필요한 것은 아니다. 생성자를 통해 객체를 전달하는 것만으로도 의존성 주입이 된다.&lt;/p&gt;
&lt;h2 id="의존의-의미"&gt;의존의 의미&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;A → B&lt;/code&gt;: &lt;code&gt;A&lt;/code&gt;가 &lt;code&gt;B&lt;/code&gt;에 의존한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;A&lt;/code&gt;가 동작하기 위해 &lt;code&gt;B&lt;/code&gt;의 타입이나 기능을 사용한다면 &lt;code&gt;A&lt;/code&gt;는 &lt;code&gt;B&lt;/code&gt;에 의존한다. &lt;code&gt;B&lt;/code&gt;의 공개 인터페이스가 바뀌면 &lt;code&gt;A&lt;/code&gt;도 영향을 받을 수 있다.&lt;/p&gt;
&lt;p&gt;다음 코드에서 &lt;code&gt;Greeter&lt;/code&gt;는 구체 타입인 &lt;code&gt;EnglishGreetingProvider&lt;/code&gt;를 직접 생성한다.&lt;/p&gt;
&lt;img alt="Screenshot 2024-09-29 at 4.21.47\u202fPM.png" loading="lazy" src="https://kelly-chui.github.io/posts/se-dependency-injection/image-001-optimized-image.webp"&gt;&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EnglishGreetingProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Greeter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EnglishGreetingProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이 구조에서는 구현을 교체하거나 테스트 대역을 사용하려면 &lt;code&gt;Greeter&lt;/code&gt;를 직접 수정해야 한다.&lt;/p&gt;</description></item><item><title>Network. VPN(Virtual Private Network)</title><link>https://kelly-chui.github.io/posts/cs-network-vpn/</link><pubDate>Tue, 04 Mar 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-vpn/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;VPN은 공용 네트워크를 이용하여 사설 네트워크처럼 안전하게 통신할 수 있도록 하는 기술이다.&lt;/p&gt;
&lt;p&gt;공용 네트워크를 통해 사설 네트워크처럼 안전하게 데이터를 주고받는 기술, 데이터를 암호화하여 외부로부터의 도청, 해킹, 감시로부터 보호하고, 원격으로 사설 네트워크에 안전하게 접속할 수 있도록 한다.&lt;/p&gt;
&lt;h2 id="동작-원리"&gt;동작 원리&lt;/h2&gt;
&lt;h3 id="암호화"&gt;암호화&lt;/h3&gt;
&lt;p&gt;사용자의 데이터를 암호화 하여 전송하므로, ISP나 외부에서 데이터를 읽을 수 없도록 한다.&lt;/p&gt;
&lt;h3 id="터널링"&gt;터널링&lt;/h3&gt;
&lt;p&gt;VPN 터널이라는 보안 통로를 통해 전송됨, 데이터 패킷을 감싸 보호하는 역할을 한다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대표적인 VPN 프로토콜: OpenVPN, IPsec, WireGuard&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ip-주소-변경"&gt;IP 주소 변경&lt;/h3&gt;
&lt;p&gt;VPN 서버를 통해 인터넷에 접속하면 목적지 서버에는 사용자의 공인 IP 대신 VPN 서버의 IP가 보인다. 하지만 VPN 사업자는 사용자의 원래 IP와 트래픽 정보를 볼 수 있고, 쿠키나 계정 같은 다른 식별 수단도 남으므로 VPN이 익명성을 보장하는 것은 아니다.&lt;/p&gt;</description></item><item><title>SniffMEET. 닉네임 검증 개선 고민</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-nickname-validation-combine/</link><pubDate>Thu, 13 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-nickname-validation-combine/</guid><description>&lt;h2 id="작업-내역"&gt;작업 내역&lt;/h2&gt;
&lt;p&gt;닉네임 TextField의 검증 방식을 다시 정리했다. 현재는 &lt;code&gt;UITextFieldDelegate&lt;/code&gt;로 길이 검증과 버튼 활성화를 처리하고 있었는데, 중복 체크까지 같은 흐름에 넣을 경우 네트워크 요청이 너무 자주 발생할 수 있어 보였다.&lt;/p&gt;
&lt;h3 id="현재-구현"&gt;현재 구현&lt;/h3&gt;
&lt;p&gt;현재는 Delegate 기반으로 다음 두 가지를 처리하고 있었다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;ProfileCreateViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UITextFieldDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;textFieldDidChangeSelection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UITextField&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;textCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="bp"&gt;count&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;submitButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;textCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;textCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UITextField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;shouldChangeCharactersIn&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NSRange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;replacementString&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;newLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;inputTextValid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newLength&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inputTextValid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;길이 검증 자체는 단순했고, 현재 사용성도 나쁘지 않았다.&lt;/p&gt;
&lt;p&gt;문제는 중복 체크처럼 네트워크 요청이 들어가는 검증까지 같은 방식으로 처리하기에는 부담이 커진다는 점이었다.&lt;/p&gt;</description></item><item><title>SniffMEET. AuthManager와 SessionManager의 책임 분리하기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-auth-session-responsibilities/</link><pubDate>Mon, 10 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-auth-session-responsibilities/</guid><description>&lt;h2 id="작업-배경"&gt;작업 배경&lt;/h2&gt;
&lt;p&gt;AuthManager와 SessionManager는 서로 매우 밀접하게 연관된 작업을 한다. AuthManager는 사용자의 계정 인증을 담당하고 세션을 받아오며, SessionManager는 그 세션을 관리한다.&lt;/p&gt;
&lt;p&gt;하지만 기존 코드에서는 세션 관리를 포함한 대부분의 작업을 &lt;code&gt;AuthManager&lt;/code&gt;가 처리하고, &lt;code&gt;SessionManager&lt;/code&gt;는 세션을 소유하는 역할만 하고 있었다.&lt;/p&gt;
&lt;h3 id="역할-분리"&gt;역할 분리&lt;/h3&gt;
&lt;p&gt;기존에는 &lt;code&gt;AuthManager&lt;/code&gt;에서 세션 갱신, 복원, 저장, 로드 작업을 처리하고 있었고, &lt;code&gt;SessionManager&lt;/code&gt;는 세션의 만료 여부 확인과 세션 객체를 소유하는 역할만 맡고 있었다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// AuthManager 인터페이스 프로토콜&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="nc"&gt;AuthManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AuthManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kr"&gt;get&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;authStateSubject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PassthroughSubject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AuthState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Never&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kr"&gt;get&lt;/span&gt; &lt;span class="kr"&gt;set&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;signInAnonymously&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="kr"&gt;throws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;restoreSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="kr"&gt;throws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;refreshSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="kr"&gt;throws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;loadTokens&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kr"&gt;throws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// SessionManager 클래스&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;shared&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SessionManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SupabaseSession&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isExpired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 세션 만료를 파악할 때는 30초의 여유시간을 줍니다.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeIntervalSince1970&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TimeInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;그래서 세션 관리는 &lt;code&gt;SessionManager&lt;/code&gt;, 인증은 &lt;code&gt;AuthManager&lt;/code&gt;가 맡도록 기능을 다시 정의했다.&lt;/p&gt;</description></item><item><title>SniffMEET. Interactor가 인프라를 직접 알아도 될까</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-interactor-infrastructure-dependency/</link><pubDate>Mon, 10 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-interactor-infrastructure-dependency/</guid><description>&lt;h2 id="interactor에서-직접-매니저-호출하지-않기"&gt;Interactor에서 직접 매니저 호출하지 않기&lt;/h2&gt;
&lt;p&gt;매니저가 변하면 인터랙터를 직접 변경해야한다. 매니저와 인터랙터 간의 의존성을 줄이고, 인터랙터는 “비즈니스 로직”만, 그리고 비즈니스 로직의 구현체인 유즈케이스에서 네트워크 요청이나 데이터 관리와 같은 인프라 로직을 담당하는 것이 좋다고 생각한다.&lt;/p&gt;
&lt;p&gt;또한 싱글톤 객체여도 현재 주입하는 방식으로 사용중인데, 인터랙터에서 사용하면 직접 매니저의 싱글톤 객체를 호출해서 사용해야 한다:&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;sendWalkRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longtitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;myInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;loadUserInfoUseCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;SessionManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;requestWalkUseCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;walkNoti&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;walkNoti&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;presenter&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;didSendWalkRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;위 코드에서 &lt;code&gt;id&lt;/code&gt;를 얻기 위해 SessionManager의 싱글톤 객체를 호출해야 한다. 싱글톤 객체 호출 자체는 크게 문제가 되지 않을 수 있지만, 다음과 같이 생각해볼 주제가 있다.&lt;/p&gt;</description></item><item><title>SniffMEET. 세션 갱신과 정보 접근을 SessionManager에 캡슐화하기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-session-manager-encapsulation/</link><pubDate>Mon, 10 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-session-manager-encapsulation/</guid><description>&lt;h2 id="sessionmanager-사용성-개선"&gt;SessionManager 사용성 개선&lt;/h2&gt;
&lt;h3 id="세션을-갱신하는-방법"&gt;세션을 갱신하는 방법&lt;/h3&gt;
&lt;p&gt;Supabase 서버와의 통신이 필요할 때, 우선 세션의 유효성을 검증해야 한다.&lt;/p&gt;
&lt;p&gt;기존 방식에선 직접 AuthManager의 싱글톤 객체에서 세션을 갱신하는 작업을 했지만&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AuthManager와 SessionManager의 역할 명확하게 분리&lt;/li&gt;
&lt;li&gt;AuthManager의 싱글톤 해제&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;와 같은 변화점이 있었고, 기존 AuthManager가 담당하던 세션 갱신, 복원 작업이 SessionManager로 옮겨졌다. SessionManager의 싱글톤 객체에서 세션의 유효성을 체크하는 컴퓨티드 프로퍼티의 부울리언 값을 체크하여 세션을 갱신하는 메소드를 호출하는 방식을 사용했다:&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;SessionManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isExpired&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;SessionManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refreshSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이 방식은 SessionManager의 싱글톤 객체 호출이 두 번 일어나고, &lt;code&gt;refreshSession&lt;/code&gt;이라는 메소드를 직접 노출해서, SessionManager가 세션을 갱신하는 방법을 직접 노출하는 문제가 있다.&lt;/p&gt;</description></item><item><title>SniffMEET. 세션 저장 메소드를 공개해도 괜찮을까?</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-session-save-api/</link><pubDate>Mon, 10 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-session-save-api/</guid><description>&lt;h2 id="세션-저장하기"&gt;세션 저장하기&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;AuthManager&lt;/code&gt;와 &lt;code&gt;SessionManager&lt;/code&gt;의 역할을 분리하고 나니, 세션을 받아오는 쪽과 저장하는 쪽이 서로 달라지는 문제가 발생했다.&lt;/p&gt;
&lt;p&gt;(AuthManager가 세션도 관리하던)기존 구조에서는 &lt;code&gt;saveSession(for:)&lt;/code&gt;가 외부에 노출되어 있지 않았는데, 역할이 옮겨진 뒤에는 AuthManager에서 받아온 세션을 SessionManager로 전달해야 했다.&lt;/p&gt;
&lt;h3 id="고민한-점"&gt;고민한 점&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;saveSession(for:)&lt;/code&gt;는 사실상 &lt;code&gt;AuthManager&lt;/code&gt;가 세션을 받아온 상황에서만 필요한 메소드다.&lt;/p&gt;
&lt;p&gt;그런데 이 메소드를 공개하면 캡슐화가 약해진다는 생각을 했고, 다시 AuthManager로 돌리면 SessionManager가 맡아야 할 책임을 되돌리는 것이 되었다.&lt;/p&gt;
&lt;p&gt;따라서 몇 가지 방법을 떠올려 봤다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(최종 선택) &lt;code&gt;saveSession(for:)&lt;/code&gt;를 공개한다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;saveSession(for:)&lt;/code&gt;를 &lt;code&gt;AuthManager&lt;/code&gt;로 다시 옮긴다&lt;/li&gt;
&lt;li&gt;같은 로직을 &lt;code&gt;AuthManager&lt;/code&gt;와 &lt;code&gt;SessionManager&lt;/code&gt;에 각각 작성한다&lt;/li&gt;
&lt;li&gt;세션이 갱신됐다는 이벤트를 따로 전달할 수 있는 통신 레이어를 만든다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;처음에 가장 괜찮아 보였던 건 세션 갱신 이벤트를 따로 전달하는 방식이었다.&lt;/p&gt;</description></item><item><title>Swift. 프로토콜 요구사항과 메소드 어트리뷰트</title><link>https://kelly-chui.github.io/posts/swift-discardable-result-protocol-requirement/</link><pubDate>Mon, 10 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/swift-discardable-result-protocol-requirement/</guid><description>&lt;p&gt;&lt;code&gt;RemoteDBRequestBuildable&lt;/code&gt; 프로토콜을 추가한 뒤, 이전에는 나타나지 않던 unused-result 경고가 발생했다.&lt;/p&gt;
&lt;img alt="프로토콜을 통한 호출에서 발생한 unused-result 경고" loading="lazy" src="https://kelly-chui.github.io/posts/swift-discardable-result-protocol-requirement/image-001-optimized-image.webp"&gt;&lt;h2 id="구체-타입에서는-사라졌던-경고"&gt;구체 타입에서는 사라졌던 경고&lt;/h2&gt;
&lt;p&gt;기존 구현의 &lt;code&gt;request()&lt;/code&gt;에는 &lt;code&gt;@discardableResult&lt;/code&gt;가 붙어 있었다. 반환값을 사용하지 않아도 경고가 발생하지 않았다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SupabaseDBRequestBuilder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;discardableResult&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="kr"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;concrete&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SupabaseDBRequestBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;concrete&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이후 Builder를 프로토콜로 추상화했지만 프로토콜 요구사항에는 해당 어트리뷰트를 붙이지 않았다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="nc"&gt;RemoteDBRequestBuildable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="kr"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;SupabaseDBRequestBuilder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RemoteDBRequestBuildable&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="n"&gt;RemoteDBRequestBuildable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SupabaseDBRequestBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;구체 구현에는 여전히 &lt;code&gt;@discardableResult&lt;/code&gt;가 있는데도, 프로토콜 타입으로 호출하면 경고가 다시 나타났다.&lt;/p&gt;
&lt;h2 id="호출을-검사하는-선언과-실행할-구현"&gt;호출을 검사하는 선언과 실행할 구현&lt;/h2&gt;
&lt;p&gt;컴파일러는 호출 지점의 정적 타입을 기준으로 사용할 메서드 선언을 해석한다.&lt;/p&gt;</description></item><item><title>SniffMEET. ISP로 Supabase 요청 빌더 역할 나누기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-request-interface-segregation/</link><pubDate>Thu, 06 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-request-interface-segregation/</guid><description>&lt;h2 id="들어가며"&gt;들어가며&lt;/h2&gt;
&lt;p&gt;Supabase REST API를 직접 감싸서 사용하다 보니, 유즈케이스 레이어에서 &lt;code&gt;eq.&lt;/code&gt;, &lt;code&gt;in.&lt;/code&gt;, &lt;code&gt;gte.&lt;/code&gt; 같은 쿼리 문자열을 직접 조립하는 코드가 늘어났다.&lt;/p&gt;
&lt;p&gt;처음에는 단순했지만, 요청이 많아질수록 오타 가능성이 커지고 Supabase의 세부 문법이 상위 레이어로 새어 나오는 문제가 있었다. 이번 글은 이 쿼리 구성 책임을 Supabase DB Layer 안으로 옮기기 위해 요청 빌더와 쿼리 파라미터 래퍼를 도입한 과정을 정리한다.&lt;/p&gt;
&lt;h2 id="기본-구조"&gt;기본 구조&lt;/h2&gt;
&lt;p&gt;SNMNetwork의 동작을 간단하게 설명하면, &lt;code&gt;SNMRequestConvertible&lt;/code&gt;을 채택하는 객체의 정보로 네트워크 요청을 생성하고, 통신한 다음, 응답을 반환하는 방식으로 동작한다.&lt;/p&gt;</description></item><item><title>Network. NAT(Network Address Translation)</title><link>https://kelly-chui.github.io/posts/cs-network-nat/</link><pubDate>Wed, 05 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-nat/</guid><description>&lt;h2 id="nat"&gt;NAT&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;NAT: Network Address Translation&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;패킷이 라우팅 장치를 통해 전송되는 동안 패킷의 IP 주소 정보를 수정하여 IP 주소를 다른 주소로 매핑하는 방법, 공인 IP와 사설 IP로 나눠서 처리한다.&lt;/p&gt;
&lt;h2 id="동작-원리"&gt;동작 원리&lt;/h2&gt;
&lt;img alt="Untitled.png" loading="lazy" src="https://kelly-chui.github.io/posts/cs-network-nat/image-001-optimized-image.webp"&gt;&lt;p&gt;NAT는 라우터에서 동작하며, 내부 네트워크의 사설 IP 주소를 외부에서 사용할 수 있는 공인 IP 주소로 변환한다. 이를 통해 여러 장치가 하나의 공인 IP 주소를 공유하면서 인터넷에 접속할 수 있다.&lt;/p&gt;
&lt;h3 id="동작-방식"&gt;동작 방식&lt;/h3&gt;
&lt;h4 id="출발지-주소-변환-snat-source-nat"&gt;출발지 주소 변환 (SNAT: Source NAT)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;내부 네트워크의 컴퓨터가 외부에 패킷을 전송할 때 사설 IP주소를 공인 IP주소로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="목적지-주소-변환-dnat-destination-nat"&gt;목적지 주소 변환 (DNAT: Destination NAT)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;외부에서 내부로 네트워크로 들어오는 패킷의 목적지 IP를 공인 IP에서 내부 네트워크의 사설 IP로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="포트-주소-변환-pat-port-address-translation"&gt;포트 주소 변환 (PAT: Port Address Translation)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;여러 내부 IP들이 하나의 공인 IP를 공유하기 때문에, 포트 번호로 이들을 구별한다.&lt;/li&gt;
&lt;li&gt;하나의 공인 IP 주소를 여러 장치가 함께 사용할 수 있도록 포트 번호를 함께 변환하여 구분한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어 다음과 같이 서로 다른 내부 장치가 하나의 공인 IP를 사용할 수 있다.&lt;/p&gt;</description></item><item><title>Network. 쿠키, 세션</title><link>https://kelly-chui.github.io/posts/cs-network-cookie-session/</link><pubDate>Sun, 02 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-cookie-session/</guid><description>&lt;h2 id="개요"&gt;개요&lt;/h2&gt;
&lt;p&gt;웹 애플리케이션에서 사용자 정보를 저장하고 관리하는 방식, HTTP는 statelessness이기 때문에, 이전 요청에 대한 정보를 기억하지 않는다. 따라서 이 문제를 해결하기 위해 쿠키와 세션이 필요하다.&lt;/p&gt;
&lt;h2 id="쿠키"&gt;쿠키&lt;/h2&gt;
&lt;p&gt;클라이언트에 저장되는 작은 데이터 파일, 서버가 클라이언트에게 데이터를 저장하도록 지시하면, 해당 데이터를 전송하게 됨&lt;/p&gt;
&lt;h3 id="용도"&gt;용도&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;사용자 인증 정보 저장 (로그인 상태 유지)&lt;/li&gt;
&lt;li&gt;웹사이트 설정 저장&lt;/li&gt;
&lt;li&gt;사용자 행동 추적&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="특징"&gt;특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;주기적 전송: 사용자가 특정 사이트에 다시 방문하면 브라우저가 자동으로 쿠키를 서버로 전송함&lt;/li&gt;
&lt;li&gt;수명: 유효 기간을 설정할 수 있고, 만료되면 삭제됨, 유효기간이 없으면 브라우저 세션이 종료될 때 삭제&lt;/li&gt;
&lt;li&gt;용량: 한 도메인당 쿠키는 4KB 정도로 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="보안"&gt;보안&lt;/h3&gt;
&lt;p&gt;사용자의 브라우저에 저장되기 때문에 클라이언트에서 조작할 수 있다. 따라서 민감한 데이터는 쿠키에 저장하면 안됨&lt;/p&gt;</description></item><item><title>OS. Stack, Heap 메모리 관리</title><link>https://kelly-chui.github.io/posts/cs-os-stack-and-heap/</link><pubDate>Sun, 02 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-os-stack-and-heap/</guid><description>&lt;p&gt;Date: February 2, 2025
Multi-select: Memory&lt;/p&gt;
&lt;h2 id="스택-메모리"&gt;스택 메모리&lt;/h2&gt;
&lt;p&gt;LIFO 구조로, 함수 호출 시 생성되는 스택 프레임(로컬 변수, 파라미터, 반환 주소 등)을 저장한다. 함수가 호출 될 때 마다 새로운 스택 프레임이 쌓이고, 함수가 종료되면 스택 프레임 제거된다.&lt;/p&gt;
&lt;h3 id="특징"&gt;특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;빠른 메모리 할당/해제: 스택은 메모리가 자동으로 관리되어서 함수가 끝나면 알아서 메모리가 반환됨&lt;/li&gt;
&lt;li&gt;고정 크기: 스택 메모리는 크기가 제한적이라서 너무 많은 데이터를 저장하면 스택 오버플로우 위험이 있음&lt;/li&gt;
&lt;li&gt;호출 상태 저장: 함수의 매개변수와 지역 값 일부가 스택 프레임에 저장될 수 있음. 실제 저장 위치는 ABI와 컴파일러 최적화에 따라 레지스터나 다른 영역이 될 수도 있음&lt;/li&gt;
&lt;li&gt;스레드별 스택: 각 스레드는 별도의 호출 스택을 가지지만, 이것만으로 함수나 데이터 전체가 thread-safe해지는 것은 아님. 스택에 있는 참조가 공유 객체를 가리킬 수도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="스택-오버플로우"&gt;스택 오버플로우&lt;/h3&gt;
&lt;p&gt;프로그램이 스택 메모리를 초과할 때 발생하는 오류, 함수 호출 시마다 생성되는 스택 프레임이 쌓이면서 스택 공간을 사용하게 되고, 이 제한된 스택 공간을 초과해서 스택 프레임이 쌓일 경우 발생한다.&lt;/p&gt;</description></item><item><title>Network. ARP(Address Resolution Protocol)</title><link>https://kelly-chui.github.io/posts/cs-network-arp/</link><pubDate>Sat, 01 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-arp/</guid><description>&lt;h2 id="arp"&gt;ARP&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;ARP: Address Resolution Protocol&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;네트워크에서 IP 주소와 MAC 주소를 변환하는 프로토콜&lt;/p&gt;
&lt;h2 id="동작-방식"&gt;동작 방식&lt;/h2&gt;
&lt;p&gt;데이터 전송 시, 송신자는 목적지의 MAC 주소를 알아야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;송신 장치가 ARP Request를 브로드캐스트한다.
&lt;ul&gt;
&lt;li&gt;여기서 네트워크의 범위는 LAN, 즉 서브넷 내부&lt;/li&gt;
&lt;li&gt;서브넷 마스크를 이용해서 공유기를 거쳐야 하는지 판단함.&lt;/li&gt;
&lt;li&gt;이때 기본 게이트웨이는 이미 알고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;해당 IP 주소를 가진 장치가 ARP Reply를 유니캐스트로 응답한다.&lt;/li&gt;
&lt;li&gt;이 정보를 ARP 캐시에 저장하여 다음 통신에서 요청하지 않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Note.&lt;/p&gt;
&lt;p&gt;ARP 캐시: IP 주소와 MAC 주소의 대응 관계를 임시로 저장한 캐시 메모리&lt;/p&gt;</description></item><item><title>Network. DHCP(Dynamic Host Configuration Protocol)</title><link>https://kelly-chui.github.io/posts/cs-network-dhcp/</link><pubDate>Sat, 01 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-dhcp/</guid><description>&lt;h2 id="개요"&gt;개요&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;DHCP: Dynamic Host Configuration Protocol&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;디바이스가 네트워크에 접속하려면 고유한 IP주소가 필요하지만, 수동으로 설정하면 불편하고 충돌이 발생할 가능성이 있음, DHCP 서버는 자동으로 IP 주소와 서브넷 마스크, 게이트웨이와 같은 파라미터를 배정한다.&lt;/p&gt;
&lt;h2 id="동작-방식"&gt;동작 방식&lt;/h2&gt;
&lt;h3 id="dora"&gt;DORA&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Discovery
&lt;ul&gt;
&lt;li&gt;클라이언트가 네트워크에 연결되면, IP 주소가 필요하다고 브로드 캐스트 요청을 보냄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Offer
&lt;ul&gt;
&lt;li&gt;DHCP 서버가 사용 가능한 IP 주소를 찾아서 클라이언트에게 제안&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Request
&lt;ul&gt;
&lt;li&gt;클라이언트가 서버에게 응답함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Acknowledge
&lt;ul&gt;
&lt;li&gt;DHCP 서버가 설정을 최종적으로 승인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="임대"&gt;임대&lt;/h3&gt;
&lt;p&gt;DHCP는 IP 주소를 영구적으로 할당하지 않고 일정 시간 동안 임대(Lease)하는 방식으로 관리한다.&lt;/p&gt;</description></item><item><title>Network. DNS(Domain Name System)</title><link>https://kelly-chui.github.io/posts/cs-network-dns/</link><pubDate>Sat, 01 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-dns/</guid><description>&lt;h2 id="dns"&gt;DNS&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;DNS: Domain Name System&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;도메인 이름을 IP 주소로 변환하는 시스템, 웹 사이트 주소를 IP주소로 변환해주는 역할&lt;/p&gt;
&lt;h2 id="동작-방식"&gt;동작 방식&lt;/h2&gt;
&lt;p&gt;DNS는 계층 구조를 가지며, 요청을 처리하는 과정에서 필요한 DNS 서버를 순차적으로 탐색한다. 한 번 조회한 결과는 캐시에 저장되어 이후에는 같은 과정을 반복하지 않을 수 있다.&lt;/p&gt;
&lt;p&gt;사용자가 google.com을 입력했다고 하면, 맥이 자동으로 로컬 DNS 서버에 요청을 보냄 → 만약 로컬 DNS 서버가 정보를 가지고 있지 않다면, 상위 DNS 서버로 요청을 보냄&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. 맥 → 로컬 캐시 확인
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 로컬 DNS 서버(ISP DNS 서버) → 로컬 캐시 확인
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 로컬 DNS 서버 → 재귀 DNS 서버에 요청
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. 재귀 DNS 서버 → 루트 DNS 서버 → TLD DNS 서버 → 권한 있는 네임서버
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5. 권한 있는 네임서버 → IP 주소 반환
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;6. 재귀 DNS 서버 → 로컬 DNS 서버 → IP 주소 전달
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;7. 로컬 DNS 서버 → 맥에 최종 IP 주소 전달
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;8. 맥 → 구글 접속&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;재귀 DNS 서버(Recursive DNS Server)는 클라이언트를 대신하여 필요한 DNS 서버를 차례대로 조회하고 최종 결과를 반환하는 역할을 한다.&lt;/p&gt;</description></item><item><title>Network. Load Balancing</title><link>https://kelly-chui.github.io/posts/cs-network-load-balancing/</link><pubDate>Sat, 01 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-load-balancing/</guid><description>&lt;h2 id="로드-밸런싱"&gt;로드 밸런싱&lt;/h2&gt;
&lt;p&gt;가용성, 성능, 확장성을 높이기 위해 여러 서버에 네트워크 트래픽을 분산하는 방법이다.&lt;/p&gt;
&lt;h2 id="특징"&gt;특징&lt;/h2&gt;
&lt;p&gt;서버와 클라이언트 간의 트래픽을 지시하고 제어하여 가용성, 확장성, 보안 및 성능이 향상됨&lt;/p&gt;
&lt;h3 id="가용성"&gt;가용성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;여러 서버에 트래픽을 분배하여 한 서버가 다운되었을 때, 사용 가능한 서버로 트래픽을 리다이렉션한다.&lt;/li&gt;
&lt;li&gt;애플리케이션 가동 중지 없이 서버 유지 관리 및 업그레이드 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="성능"&gt;성능&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;여러 서버에 요청을 분배하여, 네트워크 지연 시간을 줄임&lt;/li&gt;
&lt;li&gt;물리적으로 더 가까운 서버로 리다이렉션 하여 지연 시간 단축&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="보안"&gt;보안&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;트래픽 모니터링&lt;/li&gt;
&lt;li&gt;공격 트래픽을 여러 서버로 리다이렉션 하여 영향 최소화&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="확장성"&gt;확장성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;서버 수를 동적으로 추가하거나 제거하여 트래픽 변화에 유연하게 대응 가능&lt;/li&gt;
&lt;li&gt;수평 확장(서버 추가), 수직 확장(성능 향상) 둘 다 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="동작-원리"&gt;동작 원리&lt;/h2&gt;
&lt;h3 id="l4-로드-밸런싱"&gt;L4 로드 밸런싱&lt;/h3&gt;
&lt;p&gt;전송 계층에서 수행하는 로드 밸런싱, 패킷 헤더에 포함된 정보를 기반으로 TCP나 UDP 패킷을 기준으로 트래픽을 분배한다.&lt;/p&gt;</description></item><item><title>Network. REST(Representational State Transfer)</title><link>https://kelly-chui.github.io/posts/cs-network-restful-api/</link><pubDate>Sat, 01 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-restful-api/</guid><description>&lt;h2 id="rest"&gt;REST&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;REST: Representational State Transfer&lt;/li&gt;
&lt;li&gt;RESTful API: REST를 기반으로 한 API&lt;/li&gt;
&lt;li&gt;REST 아키텍처 스타일의 설계 원칙을 준수하는 API&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Note.&lt;/p&gt;
&lt;p&gt;REST는 HTTP와 동일한 개념이 아니다. REST는 웹 API를 설계하기 위한 아키텍처 스타일이며, HTTP는 REST를 구현할 때 가장 많이 사용하는 프로토콜이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="설계-원칙"&gt;설계 원칙&lt;/h2&gt;
&lt;h3 id="uniform-interface"&gt;Uniform interface&lt;/h3&gt;
&lt;p&gt;동일한 리소스에 대한 모든 API 요청은 동일하게 표시되어야 한다(리소스는 단일 URL을 통해 식별할 수 있도록 고유해야 한다). 서버는 표준 형식으로 정보를 전송하고, 형식이 지정된 리소스를 REST에서는 표현(Represent)라고 한다.&lt;/p&gt;</description></item><item><title>Network. 프록시</title><link>https://kelly-chui.github.io/posts/cs-network-proxy/</link><pubDate>Sat, 01 Feb 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-proxy/</guid><description>&lt;h2 id="프록시"&gt;프록시&lt;/h2&gt;
&lt;img alt="proxy.png" loading="lazy" src="https://kelly-chui.github.io/posts/cs-network-proxy/image-001-optimized-image.webp"&gt;&lt;p&gt;클라이언트와 서버 사이에 중간 역할을 하는 시스템, 클라이언트의 요청을 대신 처리하고 서버로부터 받은 응답을 클라이언트에 전달하는 방식으로 작동&lt;/p&gt;
&lt;p&gt;프록시는 클라이언트와 서버 사이에서 요청과 응답을 대신 전달하는 중개 서버이다.&lt;/p&gt;
&lt;h2 id="특징"&gt;특징&lt;/h2&gt;
&lt;h3 id="익명성-제공"&gt;익명성 제공&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;클라이언트의 실제 IP 주소를 숨기고, 프록시 서버의 IP 주소 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="캐싱"&gt;캐싱&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;자주 요청되는 데이터를 미리 저장해두고, 클라이언트의 요청이 있을 떄 빠르게 응답&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="보안"&gt;보안&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;프록시 서버는 클라이언트와 서버 사이에 존재, 보안 및 필터링 기능 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="트래픽-제어"&gt;트래픽 제어&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;리소스 보호를 위해 트래픽 필터링&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="종류"&gt;종류&lt;/h2&gt;
&lt;h3 id="포워드-프록시"&gt;포워드 프록시&lt;/h3&gt;
&lt;p&gt;클라이언트가 외부 서버에 접근할 때 중개 역할을 하는 프록시&lt;/p&gt;</description></item><item><title>Network. TCP Flow &amp; Congestion Control</title><link>https://kelly-chui.github.io/posts/cs-network-tcp-flow-congestion-control/</link><pubDate>Fri, 31 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-tcp-flow-congestion-control/</guid><description>&lt;h2 id="개요"&gt;개요&lt;/h2&gt;
&lt;p&gt;TCP는 신뢰성 있는 데이터 전송을 위해 흐름 제어(Flow Control) 와 혼잡 제어(Congestion Control) 기능을 제공함.&lt;/p&gt;
&lt;p&gt;흐름 제어는 송신자와 수신자 사이의 데이터 전송량을 조절하고, 혼잡 제어는 네트워크 전체의 혼잡을 방지하기 위한 메커니즘이다.&lt;/p&gt;
&lt;h2 id="흐름-제어-flow-control"&gt;흐름 제어 (Flow Control)&lt;/h2&gt;
&lt;p&gt;송신자와 수신자의 처리 속도 차이로 인해 데이터가 손실되지 않도록 전송량을 조절하는 기능이다.&lt;/p&gt;
&lt;h3 id="정지-대기-stop-and-wait"&gt;정지-대기 (Stop-and-Wait)&lt;/h3&gt;
&lt;p&gt;수신 측의 ACK를 받은 후에 다음 패킷을 전송하는 방식, 한 번에 하나의 패킷만을 전송할 수 있음&lt;/p&gt;
&lt;h3 id="슬라이딩-윈도우-sliding-window"&gt;슬라이딩 윈도우 (Sliding Window)&lt;/h3&gt;
&lt;p&gt;ACK를 이용해서 송신 데이터의 양을 조절하는 방식, ACK를 받지 않더라도 미리 정해진 패킷의 수 만큼 연속적으로 전송하는 방식&lt;/p&gt;</description></item><item><title>Network. TCP vs. UDP</title><link>https://kelly-chui.github.io/posts/cs-network-tcp-vs-udp/</link><pubDate>Fri, 31 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-tcp-vs-udp/</guid><description>&lt;h2 id="tcp-vs-udp"&gt;TCP vs. UDP&lt;/h2&gt;
&lt;p&gt;TCP와 UDP는 모두 전송 계층에서 동작하는 프로토콜이지만, 연결 방식과 데이터 전송 방식이 다르다.&lt;/p&gt;
&lt;p&gt;TCP는 신뢰성을 우선으로 하고, UDP는 속도와 실시간성을 우선으로 설계되었다.&lt;/p&gt;
&lt;h2 id="비교"&gt;비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;TCP&lt;/th&gt;
&lt;th&gt;UDP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;연결 방식&lt;/td&gt;
&lt;td&gt;연결 지향 (Connection-oriented)&lt;/td&gt;
&lt;td&gt;비연결형 (Connectionless)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;신뢰성&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;순서 보장&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 전송 방식&lt;/td&gt;
&lt;td&gt;스트림(Stream)&lt;/td&gt;
&lt;td&gt;데이터그램(Datagram)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;흐름 제어&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;혼잡 제어&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;헤더 크기&lt;/td&gt;
&lt;td&gt;20~60 Byte&lt;/td&gt;
&lt;td&gt;8 Byte&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;속도&lt;/td&gt;
&lt;td&gt;상대적으로 느림&lt;/td&gt;
&lt;td&gt;상대적으로 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="언제-사용할까"&gt;언제 사용할까?&lt;/h2&gt;
&lt;h3 id="tcp"&gt;TCP&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;웹 서비스 (HTTP, HTTPS)&lt;/li&gt;
&lt;li&gt;이메일 (SMTP)&lt;/li&gt;
&lt;li&gt;파일 전송 (FTP)&lt;/li&gt;
&lt;li&gt;데이터의 정확성이 중요한 서비스&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="udp"&gt;UDP&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;DNS&lt;/li&gt;
&lt;li&gt;DHCP&lt;/li&gt;
&lt;li&gt;실시간 스트리밍&lt;/li&gt;
&lt;li&gt;VoIP&lt;/li&gt;
&lt;li&gt;온라인 게임&lt;/li&gt;
&lt;li&gt;빠른 응답이 중요한 서비스&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="선택-기준"&gt;선택 기준&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;데이터의 정확성과 순서가 중요하다면 TCP를 사용한다.&lt;/li&gt;
&lt;li&gt;일부 패킷 손실이 발생하더라도 실시간성이 더 중요하다면 UDP를 사용한다.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Network. TCP(Transmission Control Protocol)</title><link>https://kelly-chui.github.io/posts/cs-network-tcp/</link><pubDate>Fri, 31 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-tcp/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;TCP(Transmission Control Protocol)는 전송 계층(L4)에서 동작하는 연결 지향(Connection-oriented) 프로토콜이다.&lt;/p&gt;
&lt;p&gt;신뢰성 있는 데이터 전송을 위해 연결 설정, 순서 제어, 오류 제어, 흐름 제어, 혼잡 제어 등의 기능을 제공한다.&lt;/p&gt;
&lt;h2 id="특징"&gt;특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;신뢰성 있는 연결형 서비스 제공&lt;/li&gt;
&lt;li&gt;포트 번호를 이용한 다중화, 순서 제어, 오류 제어, 흐름 제어, 혼잡 제어 기능 제공&lt;/li&gt;
&lt;li&gt;애플리케이션에는 메시지 경계를 보존하지 않는 연속된 바이트 스트림을 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tcp-헤더"&gt;TCP 헤더&lt;/h3&gt;
&lt;p&gt;TCP 세그먼트 앞부분에 위치한 데이터 전송을 위한 제어 정보를 포함하는 구조, 20바이트로 구성되며 최대 60바이트로 확장 가능&lt;/p&gt;</description></item><item><title>Network. UDP(User Datagram Protocol)</title><link>https://kelly-chui.github.io/posts/cs-network-udp/</link><pubDate>Fri, 31 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-udp/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;UDP(User Datagram Protocol)는 전송 계층(L4)에서 동작하는 비연결형(Connectionless) 프로토콜이다.&lt;/p&gt;
&lt;p&gt;연결 설정 없이 데이터를 빠르게 전송하며, 신뢰성보다 실시간성이 중요한 환경에서 주로 사용된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UDP: User Datagram Protocol&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OSI 7계층에서 전송 계층(L4)에 해당&lt;/p&gt;
&lt;h2 id="특징"&gt;특징&lt;/h2&gt;
&lt;h3 id="장점"&gt;장점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;데이터 전송 전에 연결을 설정하지 않는 비연결형 서비스
&lt;ul&gt;
&lt;li&gt;지연 시간이 낮음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단순한 프로토콜 구조
&lt;ul&gt;
&lt;li&gt;헤더 크기가 작고, 오버헤드가 적음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;IP 계층의 유니캐스트, 브로드캐스트, 멀티캐스트 전송에 사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단점"&gt;단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;신뢰성 부족
&lt;ul&gt;
&lt;li&gt;패킷 손실, 순서 오류, 중복이 발생해도 복구할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션 수준의 보완 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="데이터그램-패킷-통신"&gt;데이터그램 패킷 통신&lt;/h3&gt;
&lt;p&gt;각 데이터그램은 다른 데이터그램과 독립적으로 전달된다. UDP 자체에는 순서 번호, 재전송, 중복 제거 기능이 없으므로 도착 순서나 전달 성공을 보장하지 않는다.&lt;/p&gt;</description></item><item><title>Algorithm. Counter Clock Wise</title><link>https://kelly-chui.github.io/posts/cs-algorithm-counter-clock-wise/</link><pubDate>Thu, 30 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-algorithm-counter-clock-wise/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;2차원 평면 위에서 두 선분이 주어졌을 때 두 선분의 교차 여부를 판단하는 알고리즘&lt;/p&gt;
&lt;h2 id="아이디어"&gt;아이디어&lt;/h2&gt;
&lt;p&gt;2차원 평면 상에 3개의 점이 있을 때, 이들을 이루는 방향을 계산해서 두 선분의 교차 여부를 판단한다.&lt;/p&gt;
&lt;p&gt;여기서 말하는 3개의 점은 기준이 되는 선분의 양 끝점($P_1$, $P_2$라 하면)과, 나머지 선분의 한 끝점($Q_1$ 또는 $Q_2$)을 말한다.&lt;/p&gt;
&lt;p&gt;총 4개의 점인데 왜 3개의 점이냐고 하냐면, 기준이 되는 선분의 양 끝점과 나머지 선분의 한 끝점을 이용해 각각 CCW를 계산하기 때문이다.&lt;/p&gt;</description></item><item><title>Swift. Associated Type</title><link>https://kelly-chui.github.io/posts/swift-associated-type/</link><pubDate>Sun, 26 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/swift-associated-type/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;Swift의 프로토콜은 구현이 아니라 요구사항만 정의한다. 그런데 어떤 프로토콜은 사용할 타입을 미리 알 수 없는 경우가 있다.&lt;/p&gt;
&lt;p&gt;예를 들어, 아래와 같은 &lt;code&gt;Container&lt;/code&gt; 프로토콜을 만든다고 생각해 보자.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;???)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;append&lt;/code&gt;가 받을 타입은 무엇이어야 할까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Int&lt;/code&gt;일 수도 있고, &lt;code&gt;String&lt;/code&gt;일 수도 있으며, 사용자가 직접 만든 타입일 수도 있다. 하지만 프로토콜이 특정 타입에 종속되면 재사용성이 크게 떨어진다.&lt;/p&gt;
&lt;p&gt;이럴 때 사용하는 것이 Associated Type이다. 처음에는 Generic으로도 해결할 수 있을 것 같다는 생각이 들었는데, 프로토콜은 Generic을 사용할 수 없다.&lt;/p&gt;</description></item><item><title>SniffMEET. Diffable Data Source에서 이미지가 갱신되지 않은 이유</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-diffable-data-source-image-update/</link><pubDate>Mon, 20 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-diffable-data-source-image-update/</guid><description>&lt;h2 id="들어가며"&gt;들어가며&lt;/h2&gt;
&lt;p&gt;SniffMeet의 메이트 리스트 화면은 &lt;code&gt;UITableViewDataSource&lt;/code&gt;를 직접 구현해서 셀을 구성하고 있었다. 이후 테이블 뷰와 컬렉션 뷰에서 Diffable DataSource를 사용해보기로 했고, 먼저 메이트 리스트 화면에 적용해보려고 했다.&lt;/p&gt;
&lt;p&gt;처음에는 어렵지 않을 거라고 생각했다. 섹션과 아이템 타입을 만들고, snapshot을 적용하면 기존 &lt;code&gt;reloadData()&lt;/code&gt;보다 깔끔하게 업데이트할 수 있을 것 같았다. 하지만 실제로는 이미지 갱신이 제대로 동작하지 않았다. 더 정확히는 프로필 이미지 데이터는 받아오는데, Diffable DataSource가 셀을 다시 구성할 때는 이미지가 계속 &lt;code&gt;nil&lt;/code&gt;로 남아 있었다.&lt;/p&gt;
&lt;p&gt;결국 이 작업은 성공적으로 마무리하지 못했다. 그래도 실패한 이유를 다시 정리해보니, 문제는 Diffable DataSource API 자체가 아니라 item의 identity와 표시 상태를 제대로 구분하지 못한 데 있었다.&lt;/p&gt;</description></item><item><title>OS. IPC(Inter-Process Communication)</title><link>https://kelly-chui.github.io/posts/cs-os-ipc/</link><pubDate>Sun, 19 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-os-ipc/</guid><description>&lt;p&gt;Date: January 19, 2025
Multi-select: Process&lt;/p&gt;
&lt;h2 id="ipc"&gt;IPC&lt;/h2&gt;
&lt;p&gt;여러 개의 독립적인 프로세스들이 서로 데이터를 주고받을 때 사용하는 메커니즘을 IPC(Inter Process Communication)라고 한다.&lt;/p&gt;
&lt;p&gt;각각의 프로세스는 별개의 메모리 공간을 가지기 때문에, 서로 직접 메모리에 접근할 수 없기 때문에 IPC는 프로세스 간에 데이터를 교환하는 방법을 제공한다.&lt;/p&gt;
&lt;h2 id="메세지-패싱"&gt;메세지 패싱&lt;/h2&gt;
&lt;p&gt;직접 메모리를 공유하지 않고 메세지를 통해 데이터를 주고받는 방식, 격리성을 보장할 수 있는 장점이 있지만 속도가 느리고 메모리 소비가 증가한다는 단점이 있음&lt;/p&gt;
&lt;h3 id="주요방식"&gt;주요방식&lt;/h3&gt;
&lt;h4 id="simple"&gt;Simple&lt;/h4&gt;
&lt;p&gt;간단히 메세지를 보내고 받는 방식&lt;/p&gt;</description></item><item><title>SniffMEET. 프로필 이미지 다운샘플링과 썸네일 분리하기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-profile-image-downsampling-thumbnails/</link><pubDate>Fri, 17 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-profile-image-downsampling-thumbnails/</guid><description>&lt;h2 id="개요"&gt;개요&lt;/h2&gt;
&lt;p&gt;사용자가 고해상도 사진을 그대로 업로드하면, 앱에서는 훨씬 작은 크기로만 이미지를 표시하는데도 원본 이미지를 계속 전송하고 저장하게 된다.&lt;/p&gt;
&lt;p&gt;SniffMeet에서도 프로필 이미지는 홈 화면의 프로필 카드나 메이트 리스트의 작은 썸네일로 표시되는 경우가 대부분이었다. 그런데 원본 이미지를 그대로 업로드하고, 목록에서도 같은 이미지를 다시 내려받고 있었다.&lt;/p&gt;
&lt;p&gt;불필요한 네트워크 사용량과 메모리 사용을 줄이기 위해 두 가지를 적용했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;업로드 전에 프로필 이미지를 앱에서 필요한 크기로 다운샘플링한다.&lt;/li&gt;
&lt;li&gt;메이트 리스트에서는 원본 프로필 이미지가 아니라 별도의 썸네일 이미지를 내려받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="다운샘플링-기준-정하기"&gt;다운샘플링 기준 정하기&lt;/h2&gt;
&lt;p&gt;이미지를 어느 크기까지 줄일지 먼저 정해야 했다. 기준은 앱에서 프로필 이미지가 가장 크게 표시되는 홈 화면의 프로필 카드로 잡았다.&lt;/p&gt;</description></item><item><title>SniffMEET. NI·MPC 프로필 드랍 흐름 소유권 고민하기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-ni-mpc-profile-drop-ownership/</link><pubDate>Tue, 14 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-ni-mpc-profile-drop-ownership/</guid><description>&lt;h2 id="들어가며"&gt;들어가며&lt;/h2&gt;
&lt;p&gt;SniffMeet의 프로필 드랍 기능은 주변 사용자를 찾고, 기기 간 연결을 만들고, 거리와 방향 조건을 만족했을 때 프로필을 전달하는 흐름이다.&lt;/p&gt;
&lt;p&gt;겉으로 보면 &amp;ldquo;주변 사람 찾기&amp;quot;와 &amp;ldquo;프로필 보내기&amp;quot;를 나누면 될 것 같았다. 하지만 실제 흐름을 펼쳐보니 MPC, NI, 유즈케이스, 인터랙터가 서로 맞물려 있었고, 단순히 기능 이름으로 유즈케이스를 나누기 어려웠다.&lt;/p&gt;
&lt;p&gt;이 글은 프로필 드랍 흐름을 유즈케이스로 나누면서 고민했던 지점을 정리한 기록이다.&lt;/p&gt;
&lt;h2 id="기존에-생각한-흐름"&gt;기존에 생각한 흐름&lt;/h2&gt;
&lt;p&gt;당시 프로필 드랍은 대략 다음 순서로 동작한다고 봤다.&lt;/p&gt;</description></item><item><title>SniffMEET. OSSignposter로 앱 내부 작업 구간 측정하기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-os-signposter-intervals/</link><pubDate>Tue, 14 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-os-signposter-intervals/</guid><description>&lt;h2 id="들어가며"&gt;들어가며&lt;/h2&gt;
&lt;p&gt;성능 개선을 하려면 먼저 어떤 작업이 얼마나 걸리는지 볼 수 있어야 한다. CPU 사용량만 보면 어느 시점에 부하가 생겼는지는 알 수 있지만, 앱 내부의 어떤 작업이 그 구간에 실행됐는지는 바로 드러나지 않는다.&lt;/p&gt;
&lt;p&gt;그래서 Instruments에서 앱 내부 작업 구간을 이름으로 확인할 수 있도록 &lt;code&gt;OSSignposter&lt;/code&gt;를 래핑해보기로 했다.&lt;/p&gt;
&lt;h2 id="어디에-붙일까"&gt;어디에 붙일까?&lt;/h2&gt;
&lt;p&gt;처음에는 측정용 타입을 따로 만들지, 기존 &lt;code&gt;SNMLogger&lt;/code&gt;에 붙일지 고민했다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;subsystem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;SniffMeet&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;SNMLogger&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;별도의 &lt;code&gt;SNMBenchMarker&lt;/code&gt; 같은 타입을 만들 수도 있었지만, 이미 프로젝트에 로그 시스템이 있었기 때문에 기존 &lt;code&gt;SNMLogger&lt;/code&gt;에 통합하는 쪽을 선택했다.&lt;/p&gt;</description></item><item><title>Network. URI(Uniform Resource Identifier)</title><link>https://kelly-chui.github.io/posts/cs-network-uri-urn-url/</link><pubDate>Sun, 12 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-uri-urn-url/</guid><description>&lt;h2 id="uri-uniform-resource-identifier"&gt;URI (Uniform Resource Identifier)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;자원(파일, 웹 페이지, 이메일 주소)등을 식별하는 문자열&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;URI는 URL과 URN을 포함하는 상위 개념이다.&lt;/p&gt;
&lt;h2 id="url-uniform-resource-locator"&gt;URL (Uniform Resource Locator)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;자원의 위치(Location)와 접근 방법을 나타내는 URI&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;HTTP, HTTPS, FTP 등의 프로토콜을 포함할 수 있다.&lt;/li&gt;
&lt;li&gt;프로토콜, 호스트, 경로, 쿼리 등을 통해 자원에 접근하는 방법을 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="urn-uniform-resource-name"&gt;URN (Uniform Resource Name)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;자원의 이름(Name)으로 식별하는 URI&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;자원의 위치와 관계없이 고유한 이름으로 식별한다.&lt;/li&gt;
&lt;li&gt;대표적으로 ISBN, ISSN 등이 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="관계"&gt;관계&lt;/h2&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;URI
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── URL
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── URN&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</description></item><item><title>OS. Context Switching</title><link>https://kelly-chui.github.io/posts/cs-os-context-switching/</link><pubDate>Sun, 12 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-os-context-switching/</guid><description>&lt;h2 id="컨텍스트-스위칭"&gt;컨텍스트 스위칭&lt;/h2&gt;
&lt;p&gt;CPU가 현재 실행 중인 프로세스나 스레드를 중단하고, 다른 프로세스나 스레드를 실행하는 과정. 멀티 태스킹 환경에서 하나의 CPU가 여러 작업을 번갈아 가며 처리할 수 있게 만들어 준다.&lt;/p&gt;
&lt;h3 id="언제-발생하나"&gt;언제 발생하나?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CPU 스케줄러가 다른 작업을 선택했을 때(그러면 다른 프로세스를 써야하니까)&lt;/li&gt;
&lt;li&gt;실행 중인 작업이 블로킹 되었을때 (I/ O같은게 들어왔다거나 할 때)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="과정"&gt;과정&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;현재 작업(프로세스, 스레드)의 컨텍스트를 저장한다.&lt;/li&gt;
&lt;li&gt;다음에 실행할 작업의 컨텍스트를 복원한다.&lt;/li&gt;
&lt;li&gt;CPU가 새로운 작업을 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="코스트"&gt;코스트&lt;/h3&gt;
&lt;p&gt;컨텍스트 스위칭을 할 때, 상태를 저장하고 로드하는 과정에서 메모리 접근, 레지스터 저장, 복원과 같은 작업 때문에 시간과 자원을 사용하게 된다.&lt;/p&gt;</description></item><item><title>OS. Deadlock</title><link>https://kelly-chui.github.io/posts/cs-os-deadlock/</link><pubDate>Sun, 12 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-os-deadlock/</guid><description>&lt;h2 id="데드락"&gt;데드락&lt;/h2&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/cs-os-deadlock/image-001.svg"&gt;&lt;p&gt;두 개 이상의 프로세스나 스레드가 서로 상대방이 점유하고 있는 자원을 기다리며, 서로 무한히 기다리는 상태에 빠지는 현상&lt;/p&gt;
&lt;h2 id="데드락의-발생-조건"&gt;데드락의 발생 조건&lt;/h2&gt;
&lt;p&gt;다음 네 가지 Coffman 조건이 동시에 성립할 때 데드락이 발생할 수 있다. 각 조건은 데드락의 필요 조건이지만, 네 조건이 성립한다고 해서 모든 실행에서 반드시 데드락이 발생하는 것은 아니다.&lt;/p&gt;
&lt;h3 id="상호-배제mutual-exclusion"&gt;상호 배제(Mutual Exclusion)&lt;/h3&gt;
&lt;p&gt;한 번에 하나의 작업만 사용할 수 있는 자원이 존재하는 상태다.&lt;/p&gt;
&lt;h3 id="점유-대기hold-and-wait"&gt;점유 대기(Hold and Wait)&lt;/h3&gt;
&lt;p&gt;작업이 하나 이상의 자원을 이미 보유한 상태에서 다른 작업이 가진 추가 자원을 기다리는 상태다.&lt;/p&gt;</description></item><item><title>프로세스와 스레드</title><link>https://kelly-chui.github.io/posts/cs-os-process-thread/</link><pubDate>Sun, 12 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-os-process-thread/</guid><description>&lt;h2 id="프로세스"&gt;프로세스&lt;/h2&gt;
&lt;p&gt;컴퓨터에서 실행 중인 프로그램의 인스턴스, 프로그램이 메모리에 올라가면 프로세스가 된다.&lt;/p&gt;
&lt;h3 id="프로세스의-상태"&gt;프로세스의 상태&lt;/h3&gt;
&lt;p&gt;운영체제마다 세부 상태는 다르지만, 기본적인 5상태 모델은 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;생성(New): 프로세스가 만들어지는 중인 상태&lt;/li&gt;
&lt;li&gt;준비(Ready): 실행에 필요한 자원을 갖추고 CPU 할당을 기다리는 상태&lt;/li&gt;
&lt;li&gt;실행(Running): CPU에서 명령을 실행하고 있는 상태&lt;/li&gt;
&lt;li&gt;대기(Waiting, Blocked): I/O 완료나 이벤트 발생을 기다리는 상태&lt;/li&gt;
&lt;li&gt;종료(Terminated): 실행이 끝난 상태&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CPU 스케줄러가 준비 상태의 프로세스를 선택하면 실행 상태로 이동한다. 할당 시간이 끝나거나 선점되면 다시 준비 상태가 되고, I/O를 요청하면 대기 상태로 이동한다. 기다리던 작업이 완료되면 다시 준비 상태로 돌아간다.&lt;/p&gt;</description></item><item><title>Data Structure. Hash Table</title><link>https://kelly-chui.github.io/posts/cs-data-structure-hash-table/</link><pubDate>Sat, 11 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-data-structure-hash-table/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;키, 값 쌍으로 데이터를 저장하는 자료구조. 데이터 삽입, 검색, 삭제가 빠르다. (스위프트에서 사용하는 딕셔너리도 해시 테이블이다.)&lt;/p&gt;
&lt;p&gt;해시 테이블은 키를 배열의 인덱스로 직접 사용하는 대신, 해시 함수를 통해 키를 일정한 범위의 해시 값으로 변환한다. 이 덕분에 배열 전체를 순회하지 않고도 데이터가 저장된 위치에 빠르게 접근할 수 있다.&lt;/p&gt;
&lt;h2 id="구조"&gt;구조&lt;/h2&gt;
&lt;p&gt;해시 테이블은 배열과 해시 함수로 구성된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;배열: 데이터를 저장할 배열, 배열의 인덱스는 해시 값을 뜻함&lt;/li&gt;
&lt;li&gt;해시 함수: 키를 해시 값으로 변환하는 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;키를 해시 함수에 넣어서 계산된 해시 값을 기반으로, 배열의 인덱스에 값을 저장한다.&lt;/p&gt;</description></item><item><title>SniffMEET. DTO와 Entity의 경계 점검하기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-dto-entity-boundaries/</link><pubDate>Sat, 11 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-dto-entity-boundaries/</guid><description>&lt;h2 id="들어가며"&gt;들어가며&lt;/h2&gt;
&lt;p&gt;SniffMeet에 기능이 붙으면서 DTO와 Entity가 빠르게 늘어났다. 처음에는 필요한 타입을 그때그때 만들면 됐지만, 어느 순간부터 어떤 타입이 어느 레이어에서 쓰이는지 헷갈리기 시작했다.&lt;/p&gt;
&lt;p&gt;특히 &lt;code&gt;UserInfoDTO&lt;/code&gt;, &lt;code&gt;UserInfo&lt;/code&gt;, &lt;code&gt;DogInfo&lt;/code&gt;, &lt;code&gt;Mate&lt;/code&gt;처럼 비슷한 정보를 담는 타입들이 여러 곳에서 서로 다른 이름으로 사용되고 있었다. 그래서 바로 코드를 고치기보다, 먼저 현재 타입들이 어디에서 쓰이고 있는지 정리해보기로 했다.&lt;/p&gt;
&lt;h2 id="먼저-한-일-사용처-조사"&gt;먼저 한 일: 사용처 조사&lt;/h2&gt;
&lt;p&gt;가장 먼저 DTO, Entity, Supabase 관련 Request/Response 타입을 나눠서 사용처를 훑어봤다.&lt;/p&gt;
&lt;p&gt;전체 목록을 보면 타입 수가 많은 것도 문제였지만, 더 큰 문제는 몇몇 타입의 사용처가 너무 넓다는 점이었다.&lt;/p&gt;</description></item><item><title>SniffMEET. 비동기 태스크와 액터 점검하기 (1)</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-task-mainactor-boundaries-part-1/</link><pubDate>Wed, 08 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-task-mainactor-boundaries-part-1/</guid><description>&lt;h2 id="작업-내역"&gt;작업 내역&lt;/h2&gt;
&lt;p&gt;이번 작업에서는 &lt;code&gt;Task&lt;/code&gt;, &lt;code&gt;async/await&lt;/code&gt;, &lt;code&gt;@MainActor&lt;/code&gt;가 섞여 있던 부분을 화면별로 점검했다.&lt;/p&gt;
&lt;p&gt;앱 시작 흐름부터 프로필 등록, 메이트 목록, 산책 응답까지 비동기 경계를 다시 살펴보고, 각 화면에서 책임이 어디에 있어야 하는지 정리했다.&lt;/p&gt;
&lt;h3 id="앱-시작-플로우"&gt;앱 시작 플로우&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;UIWindow&lt;/code&gt;의 루트 뷰 컨트롤러를 결정하는 흐름은 &lt;code&gt;@MainActor&lt;/code&gt;로 두고, 그 안에서 세션 복원이 끝나기를 기다렸다. &lt;code&gt;await&lt;/code&gt;로 대기하는 동안에는 메인 액터를 점유하지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;displayInitialScreen&lt;/code&gt;이 &lt;code&gt;MainActor&lt;/code&gt; 안에서 실행되는지 breakpoint로 확인했고, &lt;code&gt;displayOnBoardingView&lt;/code&gt;는 그대로 &lt;code&gt;MainActor&lt;/code&gt;에 남겨 두었다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// AppRouter.swift&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;displayInitialScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;MainActor&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;SupabaseAuthManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;restoreSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;displayTabBar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;displayOnBoardingView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="프로필-입력-및-등록"&gt;프로필 입력 및 등록&lt;/h3&gt;
&lt;p&gt;프로필 입력 부분은 전달만 하는 역할이라 비동기 처리가 필요하지 않았다.&lt;/p&gt;</description></item><item><title>SniffMEET. 비동기 태스크와 액터 점검하기 (2)</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-task-mainactor-boundaries-part-2/</link><pubDate>Wed, 08 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-task-mainactor-boundaries-part-2/</guid><description>&lt;h3 id="메이트-요청"&gt;메이트 요청&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;didTabAcceptButton&lt;/code&gt;과 &lt;code&gt;saveMateInfo&lt;/code&gt;를 따라가 보니 유스케이스 실행을 위해 &lt;code&gt;Task&lt;/code&gt;가 존재했고, Combine의 &lt;code&gt;sink&lt;/code&gt; 클로저 안에서도 &lt;code&gt;Task&lt;/code&gt;를 생성하고 있었다. Interactor에서 태스크를 묶으면 저장 완료를 알리는 메서드가 따로 필요해 보였다.&lt;/p&gt;
&lt;p&gt;Presenter 메서드는 &lt;code&gt;async&lt;/code&gt;로 바꾸고, &lt;code&gt;View&lt;/code&gt;의 &lt;code&gt;sink&lt;/code&gt;에서는 &lt;code&gt;Task&lt;/code&gt;를 통해 호출하도록 두었다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;acceptButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;touchUpInside&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;weak&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;presenter&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;didTapAcceptButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;DogProfileDTO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;presenter&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;closeTheView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cancellables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// RequestMatePresenter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;closeTheView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;view&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;dismissView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;didTapAcceptButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;SNMLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;id: &lt;/span&gt;&lt;span class="si"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;interactor&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;saveMateInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="산책-요청-및-응답"&gt;산책 요청 및 응답&lt;/h3&gt;
&lt;h4 id="respondwalkpresenter"&gt;&lt;code&gt;RespondWalkPresenter&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;산책 요청은 &lt;code&gt;Task&lt;/code&gt;로 Interactor에 존재했고, 위치 변환은 Presenter에서 &lt;code&gt;await&lt;/code&gt;로 처리했다.&lt;/p&gt;</description></item><item><title>SniffMEET. Instruments로 성능 개선 후보 찾기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-instruments-performance-candidates/</link><pubDate>Tue, 07 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-instruments-performance-candidates/</guid><description>&lt;h2 id="들어가며"&gt;들어가며&lt;/h2&gt;
&lt;p&gt;성능 개선이 필요해 보이는 지점을 감으로 고르지 않기 위해, 먼저 SniffMeet의 주요 흐름에서 CPU와 네트워크 사용량을 훑어봤다.&lt;/p&gt;
&lt;p&gt;이 측정만으로 병목 원인을 확정할 수는 없다. 다만 어떤 화면과 기능을 먼저 의심해야 하는지, 이후 리팩토링 후보를 정하는 기준으로는 충분했다.&lt;/p&gt;
&lt;h2 id="cpu-사용량-측정"&gt;CPU 사용량 측정&lt;/h2&gt;
&lt;p&gt;Instruments의 Time Profiler로 주요 사용자 흐름을 따라가며 CPU 사용량이 튀는 구간을 확인했다.&lt;/p&gt;
&lt;img alt="Time Profiler에서 CPU 사용량이 튀는 구간" loading="lazy" src="https://kelly-chui.github.io/posts/sniffmeet-dev-log-instruments-performance-candidates/image-001-optimized-image.webp"&gt;&lt;p&gt;측정 중 비교적 사용량이 높았던 지점은 다음과 같았다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;시점&lt;/th&gt;
&lt;th&gt;동작&lt;/th&gt;
&lt;th&gt;관찰&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0:26&lt;/td&gt;
&lt;td&gt;프로필 입력 뷰 로드&lt;/td&gt;
&lt;td&gt;CPU 70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0:53&lt;/td&gt;
&lt;td&gt;텍스트 필드 입력&lt;/td&gt;
&lt;td&gt;CPU 70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:44&lt;/td&gt;
&lt;td&gt;사진, 닉네임 입력 뷰 로드&lt;/td&gt;
&lt;td&gt;CPU 70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:08&lt;/td&gt;
&lt;td&gt;포토피커&lt;/td&gt;
&lt;td&gt;CPU 90%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:00&lt;/td&gt;
&lt;td&gt;등록 완료 버튼 터치&lt;/td&gt;
&lt;td&gt;CPU 100%, 약 0.7초 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:28&lt;/td&gt;
&lt;td&gt;메이트 리스트 뷰 로드&lt;/td&gt;
&lt;td&gt;CPU 50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:41&lt;/td&gt;
&lt;td&gt;산책 요청 보내기 터치&lt;/td&gt;
&lt;td&gt;CPU 80%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:10&lt;/td&gt;
&lt;td&gt;지도 로드&lt;/td&gt;
&lt;td&gt;CPU 95%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:05&lt;/td&gt;
&lt;td&gt;요청 보내기&lt;/td&gt;
&lt;td&gt;CPU 100%, 약 0.2초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:36&lt;/td&gt;
&lt;td&gt;MPC 연결&lt;/td&gt;
&lt;td&gt;CPU 100%, 약 1.2초&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;특히 회원가입 완료 버튼을 눌렀을 때와 MPC 연결 시점이 눈에 띄었다. 회원가입 완료 시점에는 메인 스레드에서만 작업이 몰리는 것처럼 보였고, MPC 연결 시점에는 MultipeerConnectivity 관련 스레드가 지속적으로 동작했다.&lt;/p&gt;</description></item><item><title>Algorithm. LCS(Subsequence)</title><link>https://kelly-chui.github.io/posts/cs-algorithm-lcs-subsequence/</link><pubDate>Thu, 02 Jan 2025 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-algorithm-lcs-subsequence/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;두 문자열이 있을 때, 가장 긴 공통 부분 문자열을 찾아내는 알고리즘, 부분 문자열이란 원소들이 원래 순서를 유지하면서 일부 원소를 생략할 수 있는 형태를 말함&lt;/p&gt;
&lt;p&gt;예시:&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;문자열 A: A**B**CB**DAB**
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;문자열 B: **BD**C**AB**B&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;A와 B의 LCS는 BDAB라고 할 수 있다.&lt;/p&gt;
&lt;h2 id="아이디어"&gt;아이디어&lt;/h2&gt;
&lt;p&gt;2차원 배열을 통해 중간 결과를 메모이제이션 한다. 이 배열은 두 문자열의 길이에 따라 결정된다.&lt;/p&gt;
&lt;h2 id="알고리즘"&gt;알고리즘&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;편의상 첫 번째 열과 행은 0으로 초기화 한다. 빈 문자열과 비교했을 때의 경우를 처리할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;D&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ol&gt;
&lt;li&gt;두 번째 문자열의 문자를 차례대로 가져와 비교한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;D&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;테이블의 각 원소는 그 시점의 substring간의 LCS를 의미한다. 즉 위 표에서 1인 원소는 substring “AB”와 “B”의 LCS의 길이가 1임을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;앞선 결과를 기반으로 LCS의 크기를 계산한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;D&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;B와 C는 같지 않더라도, “ABC”와 “B”를 비교하는 것과 같으므로, 이전에 “AB”와 “B”를 비교한 값인 1이 유지된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;만약 두 문자가 다른 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;D&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;B와 D는 다르지만, “AB”와 “BD”의 LCS의 길이는 1이다. 3번의 경우와 합쳐서 두 원소가 다르지 않을 때, DP 테이블을 채우는 점화식을 세울 수 있다.&lt;/li&gt;
&lt;li&gt;dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;두 문자가 같은 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;D&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;두 문자가 같은 경우, 즉 위 표의 상황에서는 “ABCBD”와 “BD”의 LCS의 크기를 구하는 경우가 된다. 이 경우는 “ABCB”와 “B”의 LCS의 크기에서 원소 “D”가 더해진 것과 같으므로, 대각선 위에 있는 값에서 1을 더한 값이 된다.&lt;/li&gt;
&lt;li&gt;dp[i][j] = dp[i - 1][j - 1] + 1&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="동작-원리"&gt;동작 원리&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DP 테이블을 채우면서 두 문자가 다른 경우
&lt;ul&gt;
&lt;li&gt;이전에 계산한 결과를 그대로 유지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;두 문자가 같은 경우
&lt;ul&gt;
&lt;li&gt;그 문자가 없는 경우의 LCS의 크기에서 1을 더한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;점화식&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id="기타"&gt;기타&lt;/h2&gt;
&lt;h3 id="길이와-실제-문자열"&gt;길이와 실제 문자열&lt;/h3&gt;
&lt;p&gt;DP 테이블의 마지막 값인 &lt;code&gt;dp[m][n]&lt;/code&gt;은 LCS의 &lt;strong&gt;길이&lt;/strong&gt;만 나타낸다.&lt;/p&gt;</description></item><item><title>Algorithm. Binary Search off-by-one 에러 없이 구현하기</title><link>https://kelly-chui.github.io/posts/cs-algorithm-binary-search/</link><pubDate>Tue, 31 Dec 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-algorithm-binary-search/</guid><description>&lt;h2 id="off-by-one-error"&gt;off by one error&lt;/h2&gt;
&lt;p&gt;바이너리 서치를 할 때, 경계나 중간 값을 처리하면서 인덱스가 하나 어긋나는 에러&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;주요 원인은 바이너리 서치에 대한 이해 부족&lt;/li&gt;
&lt;li&gt;인덱스 레인지, high / low 갱신 방식, 중간값 계산 방식, 값 리턴 방식을 다양하게 생각해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="binary-search-테크닉"&gt;Binary Search 테크닉&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;else&lt;/code&gt;보다는 &lt;code&gt;else if&lt;/code&gt;를 사용하여 조건을 명확하게 표현하기&lt;/li&gt;
&lt;li&gt;오버플로우 방지를 위해서는 &lt;code&gt;mid&lt;/code&gt;를 계산할 때, &lt;code&gt;(low + high) / 2&lt;/code&gt;보다 &lt;code&gt;low + (high - low) / 2&lt;/code&gt;를 사용하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;바이너리 서치는 단순히 정렬된 배열에서 값을 찾는 알고리즘이 아니다. 바이너리 서치를 이용하여, 정확한 값을 찾는 경우보다 조건을 만족하는 첫 번째 또는 마지막 위치를 찾는 경우가 더 많다.&lt;/p&gt;</description></item><item><title>SniffMEET. anon-key와 익명 사용자의 접근 권한 구분</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-anon-access-control/</link><pubDate>Thu, 28 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-anon-access-control/</guid><description>&lt;h2 id="anon-role과-anon-user의-접근-권한-차이"&gt;anon-role과 anon-user의 접근 권한 차이&lt;/h2&gt;
&lt;p&gt;Public Scheme에 있는 테이블을 &lt;code&gt;anon-key&lt;/code&gt;로는 접근할 수 있었지만, &lt;code&gt;anon-user&lt;/code&gt;의 JWT 토큰으로는 접근하지 못하는 문제가 있었다.&lt;br&gt;
처음에는 둘이 비슷한 범주라고 생각했지만, 실제로는 권한 체계와 접근 방식이 달라서 같은 문제로 볼 수 없었다.&lt;/p&gt;
&lt;h3 id="문제를-확인하기-전-생각한-내용"&gt;문제를 확인하기 전 생각한 내용&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;anon-key&lt;/code&gt;, &lt;code&gt;anon-role&lt;/code&gt;, &lt;code&gt;anon-user&lt;/code&gt;의 차이부터 다시 정리했다.&lt;br&gt;
&lt;code&gt;anon-key&lt;/code&gt;를 이용한 접근은 &lt;code&gt;anon-role&lt;/code&gt;로 보고, &lt;code&gt;anon-user&lt;/code&gt;는 이메일 주소나 전화번호를 등록하지 않은 유저를 의미한다. 이 경우 role 자체는 &lt;code&gt;authenticated-role&lt;/code&gt;이었다.&lt;/p&gt;
&lt;p&gt;익명 유저가 접근했을 때 &lt;code&gt;200 OK&lt;/code&gt;가 뜨지만 실제 값을 가져오지 못하는 경우도 있었다.&lt;br&gt;
그래서 접근 자체는 허용되지만 &lt;code&gt;RLS&lt;/code&gt;에서 막히고 있다고 추정할 수 있었다.&lt;/p&gt;</description></item><item><title>SniffMEET. Storage 버킷과 접근 제어</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-storage-access-control/</link><pubDate>Sun, 24 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-storage-access-control/</guid><description>&lt;p&gt;SniffMEET의 프로필 이미지는 사용자 정보와 성격이 다르다. 사용자 이름이나 소개처럼 구조화된 데이터는 Database에 저장하고, 이미지 파일은 Storage에 저장하는 편이 적합하다.&lt;/p&gt;
&lt;p&gt;Storage의 Postgres 테이블에는 파일 자체가 아니라 버킷과 객체의 메타데이터가 기록된다.&lt;/p&gt;
&lt;h2 id="버킷과-객체-경로"&gt;버킷과 객체 경로&lt;/h2&gt;
&lt;p&gt;버킷은 파일을 묶는 최상위 컨테이너다. 사용자마다 버킷을 만드는 것보다 용도별 버킷을 만들고 객체 경로로 사용자를 구분하는 편이 관리하기 쉽다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;profile-images/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── {user-id}/profile.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── {user-id}/thumbnail.jpg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이 구조에서는 &lt;code&gt;profile-images&lt;/code&gt; 버킷이 공통된 공개 범위와 파일 제한을 담당하고, 사용자 ID가 포함된 경로를 RLS 정책에서 검사할 수 있다.&lt;/p&gt;</description></item><item><title>SniffMEET. Supabase 세션 갱신 요청이 실패한 이유</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-refresh-token-request/</link><pubDate>Thu, 21 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-refresh-token-request/</guid><description>&lt;h2 id="문제"&gt;문제&lt;/h2&gt;
&lt;p&gt;Supabase 세션 갱신 요청이 제대로 동작하지 않았다. 세션 로직이나 네트워크 레이어 문제처럼 보였지만, 실제 원인은 요청 형식이었다. 당시 메모는 이랬다.&lt;/p&gt;
&lt;h2 id="수정"&gt;수정&lt;/h2&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;&amp;#34;grant_type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;refreshToken&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;grant_type&lt;/code&gt; 값을 Swift식 camelCase로 보내고 있었다. Supabase가 기대하는 값은 &lt;code&gt;refresh_token&lt;/code&gt;이었다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;&amp;#34;grant_type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;refresh_token&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;body도 마찬가지였다. Supabase가 기대하는 key는 &lt;code&gt;refreshToken&lt;/code&gt;이 아니라 &lt;code&gt;refresh_token&lt;/code&gt;이었다. refresh 요청에서는 기존 access token의 &lt;code&gt;Authorization&lt;/code&gt; 헤더도 제거했다. (토큰을 갱신받기 위한 요청인데, 만료된 토큰을 보내는거니까 아무 의미 없다!)&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{ &lt;/span&gt;&lt;span class="se"&gt;\&amp;#34;&lt;/span&gt;&lt;span class="s"&gt;refresh_token&lt;/span&gt;&lt;span class="se"&gt;\&amp;#34;&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;refreshToken&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\&amp;#34;&lt;/span&gt;&lt;span class="s"&gt; }&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Authorization&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;refresh 요청은 기존 access token으로 리소스에 접근하는 요청이 아니라, refresh token으로 새 세션을 발급받는 요청이기 때문이다.&lt;/p&gt;</description></item><item><title>SniffMEET. JWT와 Supabase RLS 정책에 연결하기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-jwt-rls/</link><pubDate>Wed, 20 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-jwt-rls/</guid><description>&lt;p&gt;Supabase Auth로 로그인하면 클라이언트는 Access Token으로 JWT를 받는다. Supabase는 요청에 포함된 JWT를 검증하고, 그 안의 사용자 정보와 역할을 RLS 정책에 전달한다.&lt;/p&gt;
&lt;pre class="mermaid" data-mermaid-source="flowchart&amp;#43;LR%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;Client%5B%22%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8%22%5D&amp;#43;--%3E%7C%22JWT%EB%A5%BC&amp;#43;%ED%8F%AC%ED%95%A8%ED%95%9C&amp;#43;%EC%9A%94%EC%B2%AD%22%7C&amp;#43;Supabase%5B%22Supabase&amp;#43;API%22%5D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;Supabase&amp;#43;--%3E%7C%22JWT&amp;#43;%EA%B2%80%EC%A6%9D%22%7C&amp;#43;Claims%5B%22sub%2C&amp;#43;role&amp;#43;%EB%93%B1%EC%9D%98&amp;#43;%ED%81%B4%EB%A0%88%EC%9E%84%22%5D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;Claims&amp;#43;--%3E&amp;#43;RLS%5B%22PostgreSQL&amp;#43;RLS&amp;#43;%EC%A0%95%EC%B1%85%22%5D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;RLS&amp;#43;--%3E&amp;#43;Database%5B%22%ED%97%88%EC%9A%A9%EB%90%9C&amp;#43;%ED%96%89%EB%A7%8C&amp;#43;%EC%A1%B0%ED%9A%8C&amp;#43;%EB%98%90%EB%8A%94&amp;#43;%EB%B3%80%EA%B2%BD%22%5D"&gt;&lt;/pre&gt;&lt;h2 id="jwt의-클레임"&gt;JWT의 클레임&lt;/h2&gt;
&lt;p&gt;JWT는 헤더, 페이로드, 서명으로 구성된다. 각 부분은 Base64URL로 인코딩되지만 암호화되지는 않으므로 페이로드에 비밀 정보를 넣으면 안 된다. 서명은 헤더와 페이로드가 변조되지 않았는지 검증하는 데 사용한다.&lt;/p&gt;
&lt;p&gt;Supabase의 JWT에는 다음과 같은 클레임이 포함된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sub&lt;/code&gt;: 사용자의 고유 ID&lt;/li&gt;
&lt;li&gt;&lt;code&gt;role&lt;/code&gt;: RLS를 적용할 PostgreSQL 역할&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iat&lt;/code&gt;: 토큰이 발급된 시각&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exp&lt;/code&gt;: 토큰이 만료되는 시각&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="supabase에서-jwt-사용하기"&gt;Supabase에서 JWT 사용하기&lt;/h2&gt;
&lt;p&gt;RLS는 PostgreSQL이 행 단위로 접근을 제한하는 기능이다. Supabase에서는 &lt;code&gt;auth.uid()&lt;/code&gt;로 현재 JWT의 &lt;code&gt;sub&lt;/code&gt; 값을 가져와 행의 사용자 ID와 비교할 수 있다.&lt;/p&gt;</description></item><item><title>SniffMEET. RLS와 테이블 권한</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-rls-empty-response/</link><pubDate>Wed, 20 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-rls-empty-response/</guid><description>&lt;h2 id="문제"&gt;문제&lt;/h2&gt;
&lt;p&gt;Supabase에서 anon key로 Public 테이블에 접근하면 데이터가 보이는데, 익명 로그인 후 받은 JWT access token으로 접근하면 &lt;code&gt;200 OK&lt;/code&gt;만 오고 데이터가 비어 있었다.&lt;/p&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-rls-empty-response/image-003-optimized-image.webp"&gt;&lt;p&gt;처음에는 authenticated role이 anon role보다 상위 권한이라고 생각해서 더 헷갈렸다. 하지만 Supabase에서는 role의 상하관계보다 RLS 정책을 어떻게 작성했는지가 더 중요했다.&lt;/p&gt;
&lt;h2 id="확인"&gt;확인&lt;/h2&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-rls-empty-response/image-002-optimized-image.webp"&gt;&lt;p&gt;JWT가 만료된 경우에는 &lt;code&gt;401 Unauthorized&lt;/code&gt;가 발생했다. 반면 RLS 조건에 맞지 않는 경우에는 요청 자체는 성공해서 &lt;code&gt;200 OK&lt;/code&gt;가 오지만, row가 필터링되어 데이터가 내려오지 않았다.&lt;/p&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-rls-empty-response/image-004-optimized-image.webp"&gt;&lt;p&gt;authenticated 유저가 읽을 수 있도록 다음 policy를 추가하자 데이터를 읽을 수 있었다.&lt;/p&gt;</description></item><item><title>Swift. Xcode 디버깅과 LLDB 기본 명령어</title><link>https://kelly-chui.github.io/posts/swift-xcode-lldb-debugging/</link><pubDate>Wed, 20 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/swift-xcode-lldb-debugging/</guid><description>&lt;h2 id="lldb가-뭔가요"&gt;LLDB가 뭔가요&lt;/h2&gt;
&lt;p&gt;LLVM + Debugger&lt;/p&gt;
&lt;p&gt;LLVM 위에서 동작하는 디버거&lt;/p&gt;
&lt;h3 id="llvmlow-level-virtual-machine"&gt;LLVM(Low Level Virtual Machine)&lt;/h3&gt;
&lt;img alt="image.png" loading="lazy" src="https://kelly-chui.github.io/posts/swift-xcode-lldb-debugging/image-001-optimized-image.webp"&gt;&lt;p&gt;프로그래밍 언어를 CPU 아키텍처가 이해할 수 있도록 컴파일 하는 과정을 생각해보면 (프로그래밍 언어의 종류 * CPU 아키텍처의 종류) 만큼의 경우의 수가 생긴다.&lt;/p&gt;
&lt;p&gt;LLVM은 LLVM-IR이라는 중간 언어를 만들어서 프로그래밍 언어를 LLVM-IR로 변역하고 이 LLVM-IR을 CPU 아키텍처에 맞는 기계어로 번역하게 해주는 역할을 함 어려운건 여기까지!&lt;/p&gt;
&lt;h2 id="xcode-debugging"&gt;XCode Debugging&lt;/h2&gt;
&lt;h3 id="breakpoint"&gt;BreakPoint&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;브레이크 포인트가 걸린 라인 직전 까지 실행 됨&lt;/li&gt;
&lt;li&gt;활성화/비활성화 할 수 있음&lt;/li&gt;
&lt;li&gt;다양한 옵션 지정 가능 (이름, LLDB 커맨드, 로그 작성 등등)…&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="debugging-bar"&gt;Debugging Bar&lt;/h3&gt;
&lt;img alt="image.png" loading="lazy" src="https://kelly-chui.github.io/posts/swift-xcode-lldb-debugging/image-002-optimized-image.webp"&gt;&lt;ul&gt;
&lt;li&gt;Disable/enable breakpoints: 브레이크 포인트들을 활성화/비활성화&lt;/li&gt;
&lt;li&gt;Pause/continue execution: 다음 브레이크 포인트가 나올 때 까지 진행&lt;/li&gt;
&lt;li&gt;Step over: 다음 라인으로 넘어가기&lt;/li&gt;
&lt;li&gt;Step into: 현재 브레이크 포인트가 걸려있는 메소드로 들어가기&lt;/li&gt;
&lt;li&gt;Step out: 현재 메소드에서 벗어나기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="lldb-직접-사용해보기"&gt;LLDB 직접 사용해보기&lt;/h2&gt;
&lt;h3 id="print-p"&gt;Print: &lt;code&gt;p&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;현재 메모리 상태에 있는 것을 출력한다.&lt;/p&gt;</description></item><item><title>SniffMEET. 익명 로그인 세션의 생성·복원·갱신 흐름</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-anonymous-session-lifecycle/</link><pubDate>Thu, 14 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-supabase-anonymous-session-lifecycle/</guid><description>&lt;h2 id="익명-로그인-생성-복원-갱신-로직-구현"&gt;익명 로그인 생성, 복원, 갱신 로직 구현&lt;/h2&gt;
&lt;p&gt;JWT, Refresh Token, 세션의 개념이 아직 명확하게 정리되지 않은 상태에서 익명 계정을 먼저 만들게 됐다. 개념이 명확하지 않아서, 세션 관리에 꽤 애를 먹었는데, 앱 재실행 이후에도 익명 계정을 어떻게 이어서 사용할지부터 정리할 필요가 있었다.&lt;/p&gt;
&lt;p&gt;생성, 복원, 갱신 로직은 서로 밀접하게 연결되어 있어서, 처음 앱을 실행했을 때와 다시 실행했을 때의 흐름을 먼저 구분해야 했다.&lt;/p&gt;
&lt;h3 id="실제-문제-해결-과정"&gt;실제 문제 해결 과정&lt;/h3&gt;
&lt;p&gt;먼저 시점별로 세션 상태를 표로 정리했다.&lt;/p&gt;</description></item><item><title>SniffMEET. 여러 VIPER 구현에서 Router 구조 비교하기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-viper-router-comparison/</link><pubDate>Tue, 12 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-viper-router-comparison/</guid><description>&lt;p&gt;VIPER에서 Router가 화면 전환을 담당한다는 원칙은 분명하다. 하지만 현재 화면을 어떻게 참조하는지, 다음 모듈은 누가 만드는지, AppRouter가 반드시 필요한지는 구현마다 달랐다.&lt;/p&gt;
&lt;p&gt;SniffMEET의 화면 구조를 정하기 전에 여러 VIPER 프로젝트를 비교하며 Router의 책임 범위를 확인했다.&lt;/p&gt;
&lt;h2 id="무엇을-확인했나"&gt;무엇을 확인했나&lt;/h2&gt;
&lt;p&gt;분석 기준은 다음 세 가지였다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Router가 현재 ViewController를 어떻게 참조하는가&lt;/li&gt;
&lt;li&gt;화면 전환 시 다음 VIPER 모듈을 누가 조립하는가&lt;/li&gt;
&lt;li&gt;여러 모듈에 걸친 전환을 AppRouter가 담당하는가&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="구현-비교"&gt;구현 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구현&lt;/th&gt;
&lt;th&gt;모듈 조립&lt;/th&gt;
&lt;th&gt;화면 전환&lt;/th&gt;
&lt;th&gt;AppRouter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/amitshekhariitbhu/iOS-Viper-Architecture"&gt;iOS-Viper-Architecture&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;WireFrame의 정적 메서드&lt;/td&gt;
&lt;td&gt;출발 View를 인자로 전달&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/tailec/ios-architecture"&gt;ios-architecture&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;정적 팩토리 메서드&lt;/td&gt;
&lt;td&gt;전환 사례가 적음&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/ferranabello/Viperit"&gt;Viperit&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;프레임워크가 모듈을 관리&lt;/td&gt;
&lt;td&gt;모듈이 직접 자신을 표시&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AppModules&lt;/code&gt;로 모듈 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/giftbott/iOS-Architecture-Sample"&gt;iOS-Architecture-Sample&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;모듈별 생성&lt;/td&gt;
&lt;td&gt;앱 단위 Router가 화면 계층 관리&lt;/td&gt;
&lt;td&gt;싱글톤 AppRouter&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;구현은 달랐지만 공통점도 있었다. View나 Presenter가 직접 다음 화면을 생성하지 않았고, 화면 조립과 전환을 UI 바깥의 객체로 분리하고 있었다.&lt;/p&gt;</description></item><item><title>SniffMEET. UITabBarController VIPER 컨테이너로 다루기</title><link>https://kelly-chui.github.io/posts/sniffmeet-dev-log-viper-tab-bar-container/</link><pubDate>Mon, 11 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/sniffmeet-dev-log-viper-tab-bar-container/</guid><description>&lt;p&gt;VIPER 모듈을 사용하더라도, 직접 탭 바를 구현하지 않는 이상 &lt;code&gt;UITabBarController&lt;/code&gt;는 사용해야 한다. 일반적인 VIPER 화면처럼 View, Presenter, Interactor, Router를 모두 갖춘 독립 모듈로 보기엔 애매한 것 같다.&lt;/p&gt;
&lt;p&gt;특히 Interactor는 여기서 아무 의미가 없어보이고, Router는 &lt;code&gt;UITabBarController&lt;/code&gt;에 내장되어 있으니까&amp;hellip;&lt;/p&gt;
&lt;h2 id="모듈-빌더"&gt;모듈 빌더&lt;/h2&gt;
&lt;p&gt;router, interactor를 생성하고 이를 가지고 있는 presenter를 생성해서 뷰에 집어넣는 방식&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HomeModuleBuilder&lt;/code&gt;는 Home 화면에 필요한 VIPER 구성 요소를 한 곳에서 조립한다. ViewController를 만들고, Router와 Interactor를 생성한 뒤 Presenter에 주입한다. 마지막으로 NavigationFactory를 통해 화면을 &lt;code&gt;UINavigationController&lt;/code&gt;로 감싸서 반환한다.&lt;/p&gt;</description></item><item><title>Software Engineering. VIPER 아키텍처</title><link>https://kelly-chui.github.io/posts/se-viper/</link><pubDate>Mon, 11 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/se-viper/</guid><description>&lt;h2 id="viper란"&gt;VIPER란&lt;/h2&gt;
&lt;img alt="VIPER 구성 요소와 데이터 흐름" loading="lazy" src="https://kelly-chui.github.io/posts/se-viper/image-001-optimized-image.webp"&gt;&lt;p&gt;VIPER는 화면을 View, Interactor, Presenter, Entity, Router로 나누는 아키텍처 패턴이다. 각 객체의 역할과 의존 방향을 제한해 UI, 비즈니스 로직, 화면 전환을 분리한다.&lt;/p&gt;
&lt;h2 id="구성-요소"&gt;구성 요소&lt;/h2&gt;
&lt;h3 id="view"&gt;View&lt;/h3&gt;
&lt;p&gt;화면을 그리고 사용자 입력을 Presenter에 전달한다. Presenter가 전달한 값을 UI에 반영하며, 비즈니스 로직은 직접 처리하지 않는다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="nc"&gt;ExampleViewProtocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;AnyObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;displayMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;displayError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UIViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;presenter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="n"&gt;ExamplePresenterProtocol&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kc"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;presenter&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;@IBAction&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;buttonTapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UIButton&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;presenter&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;handleButtonTapped&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;ExampleViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ExampleViewProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;displayMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;displayError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;errorLabel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;pre class="mermaid" data-mermaid-source="flowchart&amp;#43;LR%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;View%5B%22View%22%5D&amp;#43;--%3E%7C%22%EC%82%AC%EC%9A%A9%EC%9E%90&amp;#43;%EC%9D%B4%EB%B2%A4%ED%8A%B8%22%7C&amp;#43;Presenter%5B%22Presenter%22%5D%0A&amp;#43;&amp;#43;&amp;#43;&amp;#43;Presenter&amp;#43;-.-%3E%7C%22weak&amp;#43;%C2%B7&amp;#43;%ED%99%94%EB%A9%B4&amp;#43;%EA%B0%B1%EC%8B%A0%22%7C&amp;#43;View"&gt;&lt;/pre&gt;&lt;h3 id="presenter"&gt;Presenter&lt;/h3&gt;
&lt;p&gt;View의 입력을 해석해 Interactor나 Router에 작업을 요청한다. Interactor가 반환한 Entity는 화면에 표시할 값으로 가공해 View에 전달한다.&lt;/p&gt;</description></item><item><title>DevOps. GitHub Actions 워크플로우</title><link>https://kelly-chui.github.io/posts/devops-github-actions-workflow/</link><pubDate>Tue, 05 Nov 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devops-github-actions-workflow/</guid><description>&lt;h2 id="들어가며"&gt;들어가며&lt;/h2&gt;
&lt;p&gt;GitHub Actions는 저장소에서 발생한 이벤트를 기준으로 빌드, 테스트, 배포 같은 작업을 자동화한다. 자동화 과정은 &lt;code&gt;.github/workflows&lt;/code&gt; 디렉토리의 YAML 파일로 정의한다.&lt;/p&gt;
&lt;p&gt;처음 workflow를 작성할 때는 &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;on&lt;/code&gt;, &lt;code&gt;jobs&lt;/code&gt;, &lt;code&gt;steps&lt;/code&gt;가 각각 무엇을 나타내는지부터 이해할 필요가 있다.&lt;/p&gt;
&lt;h2 id="전체-구조"&gt;전체 구조&lt;/h2&gt;
&lt;p&gt;GitHub Actions의 실행 흐름은 다음과 같다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Event -&amp;gt; Workflow -&amp;gt; Job -&amp;gt; Runner -&amp;gt; Step&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이 흐름을 YAML로 표현하면 다음과 같다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build and Test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;run-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build triggered by @${{ github.actor }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;macos-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;echo &amp;#34;Build&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;needs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;macos-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;echo &amp;#34;Test&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id="name과-run-name"&gt;&lt;code&gt;name&lt;/code&gt;과 &lt;code&gt;run-name&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;name&lt;/code&gt;은 workflow 자체의 이름이다. GitHub 저장소의 Actions 탭에서 workflow를 구분할 때 사용한다.&lt;/p&gt;</description></item><item><title>UIKit. TableViewCell 스와이프 동작 넣기</title><link>https://kelly-chui.github.io/posts/ios-uikit-tableviewcell-swipe-actions-uitableviewdelegate/</link><pubDate>Fri, 20 Sep 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/ios-uikit-tableviewcell-swipe-actions-uitableviewdelegate/</guid><description>&lt;h2 id="기본-메소드들"&gt;기본 메소드들&lt;/h2&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 오른쪽에서 왼쪽으로 스와이프&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;trailingSwipeActionsConfigurationForRowAt&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 왼쪽에서 오른쪽으로 스와이프&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;leadingSwipeActionsConfigurationForRowAt&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id="구현-순서"&gt;구현 순서&lt;/h2&gt;
&lt;h3 id="1-uicontextualaction을-사용해서-액션-구현"&gt;1. &lt;code&gt;UIContextualAction&lt;/code&gt;을 사용해서 액션 구현&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;deleteAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UIContextualAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destructive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;삭제&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completionHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 삭제 로직&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;completionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="2-uiswipeactionsconfiguration에-액션-등록"&gt;2. &lt;code&gt;UISwipeActionsConfiguration&lt;/code&gt;에 액션 등록&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 액션 여러개 등록 가능&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UISwipeActionsConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;deleteAction&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="3-전체-코드"&gt;3. 전체 코드&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UITableView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trailingSwipeActionsConfigurationForRowAt&lt;/span&gt; &lt;span class="n"&gt;indexPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IndexPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UISwipeActionsConfiguration&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;deleteAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UIContextualAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destructive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;삭제&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completionHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 삭제 로직&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;completionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UISwipeActionsConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;deleteAction&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;performsFirstActionWithFullSwipe&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="기타"&gt;기타&lt;/h3&gt;
&lt;p&gt;색깔, 이미지 지정 가능&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;deleteAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemRed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;deleteAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;trash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;performsFirstActionWithFullSwipe&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;true&lt;/code&gt;: 스와이프 하면 첫 번째 액션이 즉시 실행&lt;/li&gt;
&lt;li&gt;&lt;code&gt;false&lt;/code&gt;: 스와이프 된 상태에서 나타는 버튼을 눌러서 액션 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="실제-적용-코드-이슈-트래킹-앱"&gt;실제 적용 코드 (이슈 트래킹 앱)&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UITableView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trailingSwipeActionsConfigurationForRowAt&lt;/span&gt; &lt;span class="n"&gt;indexPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IndexPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UISwipeActionsConfiguration&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;deleteAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;makeDeleteAction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;closeAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;makeCloseAction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UISwipeActionsConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;deleteAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;closeAction&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;performsFirstActionWithFullSwipe&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeDeleteAction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UIContextualAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;deleteAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UIContextualAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destructive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completionHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// &lt;/span&gt;&lt;span class="cs"&gt;TODO:&lt;/span&gt;&lt;span class="c1"&gt; 얼럿 띄우고 삭제 로직&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;completionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;deleteAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;trash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;deleteAction&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeCloseAction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UIContextualAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;closeAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UIContextualAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completionHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// &lt;/span&gt;&lt;span class="cs"&gt;TODO:&lt;/span&gt;&lt;span class="c1"&gt; 이슈 닫기 로직&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;completionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;closeAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;archivebox.fill&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;closeAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemIndigo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;closeAction&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</description></item><item><title>UIKit. UIView와 CALayer</title><link>https://kelly-chui.github.io/posts/ios-uikit-uiview-calayer/</link><pubDate>Tue, 17 Sep 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/ios-uikit-uiview-calayer/</guid><description>&lt;p&gt;&lt;code&gt;UIView&lt;/code&gt;는 화면의 직사각형 영역을 나타내고, 그 안에 콘텐츠를 관리하는 객체이다. 대부분의 UI 요소가 &lt;code&gt;UIView&lt;/code&gt;를 상속받으며, 화면에 콘텐츠를 보여주고 터치나 제스처를 감지하는 역할을 한다.&lt;/p&gt;
&lt;h2 id="uiview로-콘텐츠-표시하기"&gt;UIView로 콘텐츠 표시하기&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;UIView&lt;/code&gt;는 UIKit에서 제공하는 메소드나 Core Graphics를 이용해 직사각형 영역에 콘텐츠를 그릴 수 있다. 배경색만 있는 간단한 &lt;code&gt;UIView&lt;/code&gt;를 만들어보자.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;magentaRectView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UIView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;magentaRectView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;magentaRectView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;magenta&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;magentaRectView&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;오토레이아웃 제약은 임의로 정했다.&lt;/p&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/ios-uikit-uiview-calayer/image-001-optimized-image.webp"&gt;&lt;p&gt;&lt;code&gt;UIView&lt;/code&gt;는 내부에 다른 &lt;code&gt;UIView&lt;/code&gt;를 포함할 수 있다. 상위 뷰에 포함된 뷰를 서브 뷰라고 한다. &lt;code&gt;UIViewController&lt;/code&gt;의 &lt;code&gt;view&lt;/code&gt; 프로퍼티도 &lt;code&gt;UIView&lt;/code&gt; 인스턴스이기 때문에, 위 마젠타 사각형도 &lt;code&gt;UIView&lt;/code&gt; 내부의 &lt;code&gt;UIView&lt;/code&gt;이다. 좀 더 직관적으로 확인하려면, 마젠타 사각형 안에 검은색 사각형을 추가해보자.&lt;/p&gt;</description></item><item><title>UIKit. TableViewCell에 UITextView 넣고 동적으로 높이 변경하기</title><link>https://kelly-chui.github.io/posts/ios-uikit-tableviewcell-uitextview-dynamic-height/</link><pubDate>Sun, 15 Sep 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/ios-uikit-tableviewcell-uitextview-dynamic-height/</guid><description>&lt;p&gt;&lt;code&gt;UITableViewCell&lt;/code&gt; 안에 &lt;code&gt;UITextView&lt;/code&gt;를 넣고, 입력되는 텍스트 길이에 따라 셀의 높이가 자동으로 늘어나도록 처리했다.&lt;/p&gt;
&lt;p&gt;처음에는 그냥 &lt;code&gt;UITextView&lt;/code&gt;를 셀에 넣으면 알아서 늘어날 줄 알았는데, &lt;code&gt;UITextView&lt;/code&gt; 자체의 스크롤과 &lt;code&gt;UITableView&lt;/code&gt;의 셀 높이 계산이 같이 걸려 있어서 생각보다 손이 조금 간다ㅏ.&lt;/p&gt;
&lt;p&gt;핵심은 &lt;code&gt;UITextView&lt;/code&gt;의 스크롤을 끄고, 텍스트가 변경될 때 &lt;code&gt;UITableView&lt;/code&gt;에 높이를 다시 계산하라고 알려주는 것이다.&lt;/p&gt;
&lt;h2 id="구현-순서"&gt;구현 순서&lt;/h2&gt;
&lt;h3 id="1-uitextview에서-다음-속성들-체크-해제"&gt;1. &lt;code&gt;UITextView&lt;/code&gt;에서 다음 속성들 체크 해제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Show Horizontal Indicator&lt;/code&gt;: 텍스트가 가로 크기를 초과한 경우, 가로 스크롤 바 표시&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Show Vertical Indicator&lt;/code&gt;: 텍스트가 세로 크기를 초과한 경우, 세로 스크롤 바 표시&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Scrolling Enabled&lt;/code&gt;: 스크롤 가능 여부 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Bounce on Scroll&lt;/code&gt;: 스크롤이 끝에 도달했을 때, 가볍게 튕기는 애니메이션 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-nib-등록"&gt;2. &lt;code&gt;Nib&lt;/code&gt; 등록&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;UINib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nibName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;GrowingCell&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;forCellReuseIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;GrowingCell&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="3-데이터-소스-추가"&gt;3. 데이터 소스 추가&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;ViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UITableViewDataSource&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UITableView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numberOfRowsInSection&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;count&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UITableView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cellForRowAt&lt;/span&gt; &lt;span class="n"&gt;indexPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IndexPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UITableViewCell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cell&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dequeueReusableCell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;withIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;GrowingCell&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;indexPath&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;GrowingCell&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;UITableViewCell&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;indexPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="4-델리게이트-작성"&gt;4. 델리게이트 작성&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="nc"&gt;GrowingCellProtocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateHeightOfRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GrowingCell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UITextView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="5-뷰-컨트롤러가-델리게이트-채택하고-구현"&gt;5. 뷰 컨트롤러가 델리게이트 채택하고 구현&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;ViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GrowingCellProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateHeightOfRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GrowingCell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UITextView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;newSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sizeThatFits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;CGSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CGFloat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;greatestFiniteMagnitude&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;newSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setAnimationsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;beginUpdates&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;endUpdates&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setAnimationsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;thisIndexPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scrollToRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;thisIndexPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;animated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;추가. 공식 문서에서는 &lt;code&gt;beginUpdates()&lt;/code&gt; 보다  &lt;code&gt;performBatchUpdates(_:completion:)&lt;/code&gt; 의 사용을 권장하고 있음.&lt;/p&gt;</description></item><item><title>UIKit. Responder Chain</title><link>https://kelly-chui.github.io/posts/ios-uikit-responder-chain/</link><pubDate>Wed, 04 Sep 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/ios-uikit-responder-chain/</guid><description>&lt;p&gt;앱에서 터치 입력과 같은 유저 인터랙션을 처리할 때, UIKit은 &lt;code&gt;UIResponder&lt;/code&gt; 객체를 통해 이벤트를 감지하고 적절히 처리한다. 그렇다면 이벤트가 발생했을 때, 어떻게 적절한 &lt;code&gt;UIResponder&lt;/code&gt; 객체를 찾아서 처리할까?&lt;/p&gt;
&lt;p&gt;UIKit에서 이벤트가 어떻게 전달되고 처리되는지, 특히 &lt;code&gt;UIResponder&lt;/code&gt;와 Responder chain의 흐름을 정리했다.&lt;/p&gt;
&lt;h2 id="uiresponder와-uievent"&gt;UIResponder와 UIEvent&lt;/h2&gt;
&lt;p&gt;개발자 문서에서 &lt;code&gt;UIResponder&lt;/code&gt;는 &amp;lsquo;이벤트에 응답하고 처리하기 위한 추상 인터페이스&amp;rsquo;로 소개한다. 여기서 이벤트란, UIKit에서 &lt;code&gt;UIEvent&lt;/code&gt;라는 클래스로 표현되는 단일 유저 인터랙션(예: 터치)을 의미한다. &lt;code&gt;UIEvent&lt;/code&gt; 역시 개발자 문서에서 &amp;lsquo;앱에서 단일 유저 인터랙션을 설명하기 위한 객체&amp;rsquo;라고 정의되어 있다.&lt;/p&gt;</description></item><item><title>UIKit. UIStackView 레이아웃</title><link>https://kelly-chui.github.io/posts/ios-uikit-uistackview/</link><pubDate>Tue, 27 Aug 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/ios-uikit-uistackview/</guid><description>&lt;p&gt;&lt;code&gt;UIStackView&lt;/code&gt;는 여러 뷰를 한 방향으로 묶어 배치할 수 있는 컨테이너 뷰다. 직접 서브뷰마다 제약 조건을 하나씩 걸지 않아도, 스택 뷰에 arranged subview로 추가하면 스택 뷰가 내부 뷰들의 배치를 관리한다.&lt;/p&gt;
&lt;p&gt;스택 뷰는 기기의 방향, 화면 크기, 그리고 &lt;code&gt;axis&lt;/code&gt;, &lt;code&gt;alignment&lt;/code&gt;, &lt;code&gt;distribution&lt;/code&gt;, &lt;code&gt;spacing&lt;/code&gt; 같은 프로퍼티를 바탕으로 내부 뷰의 레이아웃을 결정한다.&lt;/p&gt;
&lt;p&gt;이 글에서는 스택 뷰의 주요 프로퍼티가 레이아웃에 어떤 영향을 주는지 정리한다.&lt;/p&gt;
&lt;h2 id="intrinsiccontentsize"&gt;intrinsicContentSize&lt;/h2&gt;
&lt;p&gt;스택 뷰의 레이아웃을 이해하려면 먼저 &lt;code&gt;intrinsicContentSize&lt;/code&gt;를 알아야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;intrinsicContentSize&lt;/code&gt;는 뷰가 자신의 내용만을 기준으로 자연스럽게 가지는 크기다. 대표적인 예시는 &lt;code&gt;UILabel&lt;/code&gt;이다.&lt;/p&gt;</description></item><item><title>UIKit. 화면 전환 방식</title><link>https://kelly-chui.github.io/posts/ios-uikit-screen-transitions/</link><pubDate>Wed, 21 Aug 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/ios-uikit-screen-transitions/</guid><description>&lt;p&gt;UIKit에서는 화면을 전환하는 방법이 여러 가지 있지만, 실제로는 상황에 따라 적절한 방식을 선택하면 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;화면을 계층적으로 이동한다 → &lt;strong&gt;Show&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Split View의 Detail 화면을 변경한다 → &lt;strong&gt;Show Detail&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;현재 화면 위에 독립적인 화면을 띄운다 → &lt;strong&gt;Present Modally&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;큰 화면에서는 팝오버, 작은 화면에서는 모달로 표시한다 → &lt;strong&gt;Present as Popover&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이번 글에서는 각각의 방식이 어떤 상황에서 사용되는지 간단히 정리해 보려고 한다.&lt;/p&gt;
&lt;h2 id="화면-전환-방식"&gt;화면 전환 방식&lt;/h2&gt;
&lt;h3 id="show"&gt;Show&lt;/h3&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;내비게이션 컨트롤러를 사용하는 경우 새로운 뷰 컨트롤러를 &lt;strong&gt;Push&lt;/strong&gt;하여 화면을 전환한다. 사용자가 계층 구조를 따라 이동하는 대부분의 화면 전환이 여기에 해당한다.&lt;/p&gt;</description></item><item><title>DevOps. Git 기초</title><link>https://kelly-chui.github.io/posts/devops-git-overview/</link><pubDate>Tue, 20 Aug 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/devops-git-overview/</guid><description>&lt;h2 id="깃-파일-라이프-사이클"&gt;깃 파일 라이프 사이클&lt;/h2&gt;
&lt;img alt="image.png" loading="lazy" src="https://kelly-chui.github.io/posts/devops-git-overview/image-001-optimized-image.webp"&gt;&lt;ol&gt;
&lt;li&gt;Tracked: 관리 대상 파일, 이미 스냅샷에 포함되어 있는 파일 - 깃이 이미 알고 있는 파일들…
&lt;ol&gt;
&lt;li&gt;Unmodified: 수정하지 않은 파일&lt;/li&gt;
&lt;li&gt;Modified: 수정한 파일&lt;/li&gt;
&lt;li&gt;Staged: 커밋으로 저장소에 기록할 파일&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Untracked: 관리 대상이 아닌 파일, 스냅샷 Staging Area에도 포함되지 않은 파일&lt;/li&gt;
&lt;/ol&gt;
&lt;img alt="Screenshot 2024-08-20 at 2.17.11\u202fPM.png" loading="lazy" src="https://kelly-chui.github.io/posts/devops-git-overview/image-002-optimized-image.webp"&gt;&lt;p&gt;위 캡처에서&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;test2&lt;/code&gt;: (Staged) 현재 Staging Area에 포함된 파일, 커밋하면 저장소에 기록된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;test&lt;/code&gt;: (Untracked) 트래킹 되지 않은 파일, 스냅샷에도 Staging Area에도 존재하지 않고 워킹 디렉토리에만 존재한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;README&lt;/code&gt;: (Modified) 수정된 파일, 커밋을 하기 위해서는 Staged 상태로 만들어야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="파일-새로-추적하기-git-add"&gt;파일 새로 추적하기 (git add)&lt;/h3&gt;
&lt;img alt="Screenshot 2024-08-20 at 2.20.42\u202fPM.png" loading="lazy" src="https://kelly-chui.github.io/posts/devops-git-overview/image-003-optimized-image.webp"&gt;&lt;p&gt;&lt;code&gt;git add&lt;/code&gt; 명령은 파일을 새로 추적할 때도 사용하고, 수정한 파일을 Staged 상태로 만들 때도 사용한다.&lt;/p&gt;</description></item><item><title>UIView와 UIViewController의 차이 + 뷰 생명주기 메소드</title><link>https://kelly-chui.github.io/posts/ios-uikit-uiview-vs-uiviewcontroller-lifecycle/</link><pubDate>Tue, 20 Aug 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/ios-uikit-uiview-vs-uiviewcontroller-lifecycle/</guid><description>&lt;h2 id="공식문서-먼저"&gt;공식문서 먼저&lt;/h2&gt;
&lt;h3 id="uiview"&gt;UIView&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;An object that manages the content for a rectangular area on the screen.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;화면의 사각형 영역을 나타내며, 내부에 다른 뷰를 포함할 수 있다. 대부분의 UI 요소가 UIView를 상속받는다.&lt;/li&gt;
&lt;li&gt;콘텐츠를 화면에 나타내고, 사용자의 터치나 제스처를 감지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="uiviewcontroller"&gt;UIViewController&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;An object that manages a view hierarchy for your UIKit app.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;하나 이상의 뷰를 관리하고, 이 뷰들이 사용자와 상호작용하는 방법을 제어한다.&lt;/li&gt;
&lt;li&gt;뷰를 생성하고, 화면에 표시하며, 뷰의 생명 주기를 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="두-줄-요약"&gt;두 줄 요약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;UIView는 화면의 직사각형 영역에 표시되는 콘텐츠를 관리하고, 사용자의 동작을 감지하는 역할을 한다.&lt;/li&gt;
&lt;li&gt;UIViewController는 이 UIView를 관리하는 역할을 한다. (사용자의 동작을 감지하면 이 뷰가 어떻게 상호 작용을 해야 할지, UIView 라이프 사이클 관리 등…)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="뷰-라이프-사이클--뷰-컨트롤러-관련-메소드"&gt;뷰 라이프 사이클 + 뷰 컨트롤러 관련 메소드&lt;/h2&gt;
&lt;img loading="lazy" src="https://kelly-chui.github.io/posts/ios-uikit-uiview-vs-uiviewcontroller-lifecycle/image-001-optimized-image.webp"&gt;&lt;p&gt;순서대로 작성하면&lt;/p&gt;</description></item><item><title>SwiftUI Tutorials. Building Lists and Navigation</title><link>https://kelly-chui.github.io/posts/swiftui-tutorials-02-building-lists-and-navigation/</link><pubDate>Tue, 25 Jun 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/swiftui-tutorials-02-building-lists-and-navigation/</guid><description>&lt;p&gt;사용자가 전체 랜드마크 목록을 보고 각 위치에 대한 세부 정보를 볼 수 있는 방법을 제공해야 한다.&lt;/p&gt;
&lt;p&gt;모든 랜드마크에 대한 정보를 표시할 수 있는 뷰를 생성하고 사용자가 탭하여 랜드마크에 대한 디테일 뷰를 볼 수 있는 스크롤 목록을 동적으로 생성한다. UI를 미세 조정하고 싶으면 Xcode를 사용하여 다양한 장치 크기에서 미리보기를 렌더링 하면 된다.&lt;/p&gt;
&lt;h2 id="section-1-create-a-landmark-model"&gt;Section 1. Create a landmark model&lt;/h2&gt;
&lt;p&gt;첫 번째 튜토리얼에선, 모든 정보들을 커스텀 뷰에 하드코딩했다. 이제는 뷰로 전달할 수 있는 데이터를 저장하는 모델을 만들 것이다.&lt;/p&gt;</description></item><item><title>SwiftUI Tutorials. Creating and Combining Views</title><link>https://kelly-chui.github.io/posts/swiftui-tutorials-01-creating-and-combining-views/</link><pubDate>Tue, 25 Jun 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/swiftui-tutorials-01-creating-and-combining-views/</guid><description>&lt;h2 id="section-1-create-a-new-project-and-explore-the-canvas"&gt;Section 1. Create a new project and explore the canvas&lt;/h2&gt;
&lt;p&gt;SwiftUI를 사용하는 새로운 Xcode 프로젝트를 만든다. 캔버스, 미리보기 및 SwiftUI 템플릿 코드를 탐색한다.&lt;/p&gt;
&lt;h3 id="step-1-3"&gt;Step 1-3&lt;/h3&gt;
&lt;p&gt;프로젝트 생성 부분&lt;/p&gt;
&lt;h3 id="step-4"&gt;Step 4&lt;/h3&gt;
&lt;p&gt;SwiftUI 앱 라이프 사이클을 사용하는 앱은 &lt;code&gt;App&lt;/code&gt; 프로토콜을 컨펌하는 스트럭처를 가지고 있다. 스트럭처의 &lt;code&gt;body&lt;/code&gt; 프로퍼티는 디스플레이에 보여줄 컨텐츠를 제공하는 하나 이상의 씬이 있다. &lt;code&gt;@main&lt;/code&gt; 어트리뷰트는 앱의 엔트리 포인트를 지정한다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// LandmarksApp.swift&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Landmarks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Created by Kelly Chui on 6/25/25.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;SwiftUI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;LandmarksApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt; &lt;span class="n"&gt;Scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;WindowGroup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="step-5-7"&gt;Step 5-7&lt;/h3&gt;
&lt;p&gt;기본적으로, SwiftUI 뷰 파일은 스트럭처와 프리뷰를 선언한다. 구조체는 &lt;code&gt;View&lt;/code&gt; 프로토콜을 컨펌하며, 뷰의 내용과 레이아웃을 정의한다. &lt;code&gt;preview&lt;/code&gt; 선언은 해당 뷰의 미리보기를 생성한다.&lt;/p&gt;</description></item><item><title>SwiftUI Tutorials. Handling User Input</title><link>https://kelly-chui.github.io/posts/swiftui-tutorials-03-handling-user-input/</link><pubDate>Tue, 25 Jun 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/swiftui-tutorials-03-handling-user-input/</guid><description>&lt;p&gt;랜드마크 앱에서 사용자는 좋아하는 장소를 플래그하고, 그들이 좋아하는 장소만 필터해서 볼 수 있다. 이런 기능을 만들기 위해서, 리스트에 스위치를 추가하여 유저들이 그들이 좋아하는 장소에만 포커스를 맞출 수 있도록 해야한다. 그리고 별 모양 버튼을 추가하고, 탭하면 유저들이 랜드마크에 favorite 플래그를 할 수 있도록 한다.&lt;/p&gt;
&lt;h2 id="section-1-mark-favorite-landmarks"&gt;Section 1. Mark favorite landmarks&lt;/h2&gt;
&lt;p&gt;리스트를 개선해서 사람들이 한 눈에 favorite를 찾을 수 있도록 시작한다. &lt;code&gt;Landmark&lt;/code&gt; 스트럭처에 프로퍼티를 하나 추가해서, 이 랜드마크가 favorite인지 초기 상태를 읽을 수 있도록 한다. 그리고 favorite 표시된 랜드마크에는 별 아이콘을 보여준다.&lt;/p&gt;</description></item><item><title>Network. Network 오버뷰</title><link>https://kelly-chui.github.io/posts/cs-network-overview/</link><pubDate>Tue, 30 Apr 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-overview/</guid><description>&lt;h2 id="네트워크"&gt;네트워크&lt;/h2&gt;
&lt;p&gt;노드와 링크(엣지)들이 서로 연결되어 리소스를 공유하는 구조이며, 그래프 형태로 표현할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;노드: 서버, 라우터, 스위치&lt;/li&gt;
&lt;li&gt;링크: 유선 or 무선&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="키워드"&gt;키워드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;네트워크 토폴로지&lt;/li&gt;
&lt;li&gt;클라이언트와 서버&lt;/li&gt;
&lt;li&gt;패킷&lt;/li&gt;
&lt;li&gt;주소와 전송 방식&lt;/li&gt;
&lt;li&gt;프로토콜&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="네트워크-참조-모델"&gt;네트워크 참조 모델&lt;/h2&gt;
&lt;p&gt;통신이 이루어지는 단계를 계층적으로 표현한 모델&lt;/p&gt;
&lt;h3 id="osi-model"&gt;OSI Model&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;계층&lt;/th&gt;
&lt;th&gt;대표 역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;HTTP, DNS 등 네트워크 서비스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Presentation&lt;/td&gt;
&lt;td&gt;인코딩, 압축, 암호화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session&lt;/td&gt;
&lt;td&gt;세션 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;TCP, UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;라우팅, IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Link&lt;/td&gt;
&lt;td&gt;같은 LAN에서의 통신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physical&lt;/td&gt;
&lt;td&gt;비트 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="tcpip-model"&gt;TCP/IP Model&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;응용 계층(Application Layer): OSI 모델에서 세션, 표현, 응용 계층&lt;/li&gt;
&lt;li&gt;전송 계층(Transport Layer): OSI 모델에서 전송 계층&lt;/li&gt;
&lt;li&gt;인터넷 계층(Internet Layer): OSI 모델에서 네트워크 계층&lt;/li&gt;
&lt;li&gt;네트워크 액세스 계층(Network Access Layer): OSI 모델에서 데이터 링크 계층 (+ 물리 계층)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="캡슐화와-역캡슐화encapsulation-decapsulation"&gt;캡슐화와 역캡슐화(Encapsulation, Decapsulation)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;캡슐화
&lt;ul&gt;
&lt;li&gt;데이터가 계층를 거치면서 헤더가 추가되는 과정, 상위 계층로부터 내려받은 패킷을 페이로드로 하고, 각 계층에 포함된 프로토콜의 헤더나 트레일러를 덧붙이는 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;역캡슐화
&lt;ul&gt;
&lt;li&gt;캡슐화 과정에서 덧붙인 헤더들을 각 계층에서 확인한 뒤, 제거하는 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;각 계층마다 패킷을 부르는 명칭이 다르다.&lt;/p&gt;</description></item><item><title>Network. Physical &amp; Data Link Layer</title><link>https://kelly-chui.github.io/posts/cs-network-physical-data-link-layer/</link><pubDate>Tue, 30 Apr 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-physical-data-link-layer/</guid><description>&lt;h2 id="physical--data-link-layer"&gt;Physical &amp;amp; Data Link Layer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Physical Layer: 전기, 광, 무선 등의 신호를 통해 데이터를 전송하는 계층&lt;/li&gt;
&lt;li&gt;Data Link Layer: 같은 LAN에서 데이터를 올바르게 전달하기 위한 계층&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="주요-기술"&gt;주요 기술&lt;/h2&gt;
&lt;h3 id="ethernet"&gt;Ethernet&lt;/h3&gt;
&lt;p&gt;같은 LAN 안에서 데이터를 주고받는 대표적인 기술이다.&lt;/p&gt;
&lt;h4 id="ethernet-frame"&gt;Ethernet Frame&lt;/h4&gt;
&lt;p&gt;이더넷 기반의 네트워크에서 주고받는 프레임, 프리앰블, 송 · 수신 MAC 주소, 타입/길이, 데이터, FCS로 구성&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프리앰블(preamble)
&lt;ul&gt;
&lt;li&gt;8 Bytes&lt;/li&gt;
&lt;li&gt;이더넷 프레임을 수신하고 있다는 것을 알려주는 부분&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MAC 주소(MAC address)
&lt;ul&gt;
&lt;li&gt;12(6 + 6) Bytes&lt;/li&gt;
&lt;li&gt;이더넷 프레임을 송 · 수신할 장치의 MAC 주소를 가지고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;타입/길이(type/length)
&lt;ul&gt;
&lt;li&gt;2 Bytes&lt;/li&gt;
&lt;li&gt;1500 이상의 값일 경우에는 프로토콜의 타입을 뜻함&lt;/li&gt;
&lt;li&gt;1500 이하의 경우엔 LLC라는 값으로 타입 판별한다. 하지만 요즘은 거의 대부분 Ethernet II를 사용하기 때문에 명확하게 타입으로 사용함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터(data)
&lt;ul&gt;
&lt;li&gt;최대 1500Bytes&lt;/li&gt;
&lt;li&gt;실제로 송수신할 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FCS
&lt;ul&gt;
&lt;li&gt;프레임의 오류가 있는지 여부를 확인하기 위한 트레일러&lt;/li&gt;
&lt;li&gt;CRC(Cyclic redundancy check)라는 오류 검출용 값이 명시됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="wireless"&gt;Wireless&lt;/h3&gt;
&lt;p&gt;유선이 아닌 전파로 통신하는 방법이다. 대표적으로 와이파이가 있다. 주파수 간섭 문제를 최소화 하기 위해 채널이라는 하위 주파수 대역으로 세분화한다.&lt;/p&gt;</description></item><item><title>Combine. Combine in Practice - WWDC19</title><link>https://kelly-chui.github.io/posts/wwdc-combine-in-practice/</link><pubDate>Mon, 18 Mar 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-combine-in-practice/</guid><description>&lt;h2 id="a-unified-declarative-api-for-processing-values-over-time"&gt;A unified, declarative API for processing values over time&lt;/h2&gt;
&lt;p&gt;Combine에서는 값이 시간의 흐름에 따라 전달되는 과정을 Publisher로 표현하고, 여러 연산자를 연결해 데이터가 변환되는 흐름을 선언적으로 작성한다. 아래 예제에서는 Notification으로 전달된 Data를 &lt;code&gt;MagicTrick&lt;/code&gt;으로 디코딩한다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;trickNamePublisher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NotificationCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newTrickDownloaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;?[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Output: Data, Failure: Never&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tryMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;decoder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MagicTrick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Output: MagicTrick, Failure: Error&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;decode&lt;/code&gt; 연산자를 사용하면 위 변환을 다음과 같이 줄여서 작성할 수 있다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;trickNamePublisher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NotificationCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newTrickDownloaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;?[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Output: Data, Failure: Never&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MagicTrick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// Output: MagicTrick, Failure: Error&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id="error-handling"&gt;Error Handling&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;모든 Publisher는 자신이 발생시킬 수 있는 실패의 종류를 명확하게 정의한다.&lt;/li&gt;
&lt;li&gt;실패가 발생하지 않거나 이미 처리된 경우에는 &lt;code&gt;Never&lt;/code&gt;를 Failure 타입으로 사용한다.&lt;/li&gt;
&lt;li&gt;Combine은 실패를 감지하고 복구할 수 있는 다양한 연산자를 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Combine의 Publisher는 전달하는 값의 타입인 &lt;code&gt;Output&lt;/code&gt;과 실패할 때 전달하는 오류의 타입인 &lt;code&gt;Failure&lt;/code&gt;를 함께 가진다. 두 타입이 연산자를 거치며 어떻게 바뀌는지 확인하면 데이터 흐름과 오류 흐름을 함께 추적할 수 있다.&lt;/p&gt;</description></item><item><title>Combine. Introducing Combine - WWDC19</title><link>https://kelly-chui.github.io/posts/wwdc-combine-introducing-combine/</link><pubDate>Sun, 17 Mar 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/wwdc-combine-introducing-combine/</guid><description>&lt;h2 id="combine"&gt;Combine&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Customize handling of asynchronous events by combining event-processing operators.&lt;/p&gt;
&lt;p&gt;결합된 이벤트 처리 연산자를 이용하여 비동기 이벤트 처리를 커스터마이즈 하는 방법&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Combine은 데이터 흐름을 간편하게 처리하고 비동기 이벤트를 관리하기 위한 프레임워크다. 다양한 소스에서 발생하는 이벤트를 같은 방식으로 다루고, 이벤트 사이의 상호작용을 연산자로 조합할 수 있다.&lt;/p&gt;
&lt;h2 id="특징"&gt;특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Generic: 제네릭을 사용해 다양한 타입의 데이터 흐름을 표현한다.&lt;/li&gt;
&lt;li&gt;Type safe: Publisher의 출력 타입과 실패 타입을 컴파일 타임에 확인한다.&lt;/li&gt;
&lt;li&gt;Composition first: 작은 Publisher와 연산자를 조합해 더 큰 흐름을 만든다.&lt;/li&gt;
&lt;li&gt;Request driven: Subscriber가 필요한 만큼의 값을 요청하는 방식으로 흐름을 제어한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="핵심-개념"&gt;핵심 개념&lt;/h2&gt;
&lt;h3 id="publisher"&gt;Publisher&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;값과 에러가 어떻게 생성되는지를 정의한다.&lt;/p&gt;</description></item><item><title>Network. HTTP(HyperText Transfer Protocol)</title><link>https://kelly-chui.github.io/posts/cs-network-http/</link><pubDate>Thu, 14 Mar 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-http/</guid><description>&lt;h2 id="http"&gt;HTTP&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;HTTP: HyperText Transfer Protocol&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;HTTP는 웹에서 클라이언트와 서버가 데이터를 주고받기 위한 애플리케이션 계층 프로토콜이다.&lt;/p&gt;
&lt;p&gt;클라이언트가 요청(Request)을 보내면 서버가 응답(Response)을 반환하는 Request-Response 방식으로 동작한다.&lt;/p&gt;
&lt;h2 id="동작-방식"&gt;동작 방식&lt;/h2&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;↓ HTTP Request
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;↓ HTTP Response
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id="특징"&gt;특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Request-Response 방식으로 동작한다.&lt;/li&gt;
&lt;li&gt;Stateless를 기본으로 한다.&lt;/li&gt;
&lt;li&gt;HTTP/1.x와 HTTP/2는 TCP를 기반으로 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="버전별-변화"&gt;버전별 변화&lt;/h2&gt;
&lt;h3 id="http10"&gt;HTTP/1.0&lt;/h3&gt;
&lt;p&gt;기본적으로 요청마다 연결을 새로 만드는 방식이 널리 사용됐다. 서버에서 여러 파일을 가져올 때 TCP 연결을 반복해서 설정하면 지연이 커질 수 있다. 다만 HTTP/1.0에서도 &lt;code&gt;Connection: keep-alive&lt;/code&gt;를 확장 기능으로 사용할 수 있었다.&lt;/p&gt;</description></item><item><title>Network. 네트워크 기기</title><link>https://kelly-chui.github.io/posts/cs-network-network-devices/</link><pubDate>Thu, 14 Mar 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-network-devices/</guid><description>&lt;h2 id="네트워크-기기의-처리-범위"&gt;네트워크 기기의 처리 범위&lt;/h2&gt;
&lt;p&gt;네트워크 장비는 주로 어떤 계층의 정보를 보고 전달 여부를 결정하는지에 따라 구분한다. 실제 장비는 여러 계층의 기능을 함께 제공할 수 있으므로 아래 구분은 대표적인 처리 기준이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;애플리케이션 계층: L7 스위치&lt;/li&gt;
&lt;li&gt;인터넷 계층: 라우터, L3 스위치&lt;/li&gt;
&lt;li&gt;데이터 링크 계층: L2 스위치, 브리지&lt;/li&gt;
&lt;li&gt;데이터 링크·물리 계층: NIC, 무선 AP&lt;/li&gt;
&lt;li&gt;물리 계층: 리피터&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="애플리케이션-계층을-처리하는-기기"&gt;애플리케이션 계층을 처리하는 기기&lt;/h2&gt;
&lt;p&gt;애플리케이션 계층의 정보를 기반으로 트래픽을 처리하는 장비&lt;/p&gt;
&lt;h3 id="l7스위치로드-밸런서"&gt;L7스위치(로드 밸런서)&lt;/h3&gt;
&lt;p&gt;HTTP 헤더, URL 경로, 쿠키처럼 애플리케이션 계층의 정보를 읽어 요청을 적절한 백엔드 서버로 분산하는 장비 또는 소프트웨어&lt;/p&gt;</description></item><item><title>TCP/IP 4계층</title><link>https://kelly-chui.github.io/posts/cs-network-tcp-ip-layer-4/</link><pubDate>Wed, 13 Mar 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/cs-network-tcp-ip-layer-4/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;TCP/IP 4계층은 OSI 7계층과 더불어 인터넷에서 데이터를 주고받기 위해 사용하는 대표적인 네트워크 참조 모델이다.&lt;/p&gt;
&lt;p&gt;네트워크 통신 과정을 역할에 따라 계층으로 분리하여 관리하며, 각 계층은 독립적인 역할을 수행한다.&lt;/p&gt;
&lt;p&gt;덕분에 특정 계층의 구현이 변경되더라도 다른 계층에 미치는 영향을 최소화할 수 있다.&lt;/p&gt;
&lt;h2 id="계층-구성"&gt;계층 구성&lt;/h2&gt;
&lt;h3 id="애플리케이션application"&gt;애플리케이션(Application)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;대표 프로토콜: HTTP, HTTPS, DNS, FTP, SMTP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;응용 프로그램이 사용되는 프로토콜 계층 이메일, 웹 서비스 등 서비스를 실질적으로 제공&lt;/p&gt;
&lt;h3 id="전송transport"&gt;전송(Transport)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;대표 프로토콜: TCP, UDP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;송신자와 수신자 간의 데이터 전송을 담당하는 계층&lt;/p&gt;</description></item><item><title>Swift. Meta Type</title><link>https://kelly-chui.github.io/posts/swift-meta-type/</link><pubDate>Fri, 26 Jan 2024 00:00:00 +0000</pubDate><guid>https://kelly-chui.github.io/posts/swift-meta-type/</guid><description>&lt;h2 id="소개"&gt;소개&lt;/h2&gt;
&lt;p&gt;메타 타입은 이러한 타입 자체를 값으로 다루기 위한 타입이다.&lt;/p&gt;
&lt;p&gt;즉, 인스턴스가 아니라 타입 그 자체를 표현하는 타입이라고 생각하면 된다.&lt;/p&gt;
&lt;h2 id="종류"&gt;종류&lt;/h2&gt;
&lt;h3 id="type"&gt;&lt;code&gt;.Type&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;구조체, 클래스, 열거형 등 구체적인 타입의 메타 타입이다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="protocol"&gt;&lt;code&gt;.Protocol&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;프로토콜 자체의 메타 타입이다.&lt;/p&gt;
&lt;div class="code-theme-github"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;AnyObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;Protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;AnyObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id="언제-사용할까"&gt;언제 사용할까?&lt;/h2&gt;
&lt;p&gt;메타 타입은 생각보다 자주 등장한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;타입 자체를 함수의 인자로 전달할 때&lt;/li&gt;
&lt;li&gt;런타임에 타입을 비교하거나 확인할 때&lt;/li&gt;
&lt;li&gt;타입 정보를 저장해야 할 때&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type(of:)&lt;/code&gt;를 사용할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="필요한-이유"&gt;필요한 이유&lt;/h2&gt;
&lt;p&gt;메타 타입이 필요한 가장 큰 이유는 타입 자체를 값처럼 다루기 위해서이다.&lt;/p&gt;</description></item></channel></rss>