<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>iOS Developer ChaCha</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 08 Oct 2023 11:37:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>iOS Developer ChaCha</title>
            <url>https://images.velog.io/images/kuruma-42/profile/0f5a0b5d-4bdb-4aaf-a480-01547f8eb3e3/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. iOS Developer ChaCha. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kuruma-42" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[OS - Chap2]]></title>
            <link>https://velog.io/@kuruma-42/OS-Chap2</link>
            <guid>https://velog.io/@kuruma-42/OS-Chap2</guid>
            <pubDate>Sun, 08 Oct 2023 11:37:23 GMT</pubDate>
            <description><![CDATA[<pre><code>    - </code></pre><ol>
<li>시스템 콜</li>
</ol>
<ul>
<li>정의: 시스템 호출(system call)은 운영 체제의 커널이 제공하는 서비스에 대해, 응용 프로그램의 요청에 따라 커널에 접근하기 위한 인터페이스이다.</li>
<li>mode bit 를 가지고 설명<ul>
<li>유저 프로세스가 실행되고 있다. ( mode bit = 1 )</li>
<li>유저 프로세스단에서 시스템 콜한다 부른다. ( 커널에 서비스를 요청 )</li>
<li>Trap 발생 ( mode bit = 0 )</li>
<li>Excute System Call ( 하드웨어에 접근할 특권을 얻게 됨 )</li>
<li>return ( mode bit = 1 )</li>
<li>유저 프로세스 단으로 다시 돌아온다 ( return from system call )</li>
</ul>
</li>
</ul>
<ol start="2">
<li>Context Switching
    - 개념, 내용, 언제 사용되는지, 어떤식으로 구현되는지</li>
</ol>
<ul>
<li>개념<ul>
<li>멀티프로세싱을 지원하는 OS에서 실행 중인 프로세스를 멈추고 다른 프로세스를  실행하고자 결정하면 커널에서 현재 실행 중이던 프로세스의 정보를 나중에 다시  실행될 때를 대비하여 현재 상태를 PCB를 저장하고 새롭게 실행되는 프로세스의 정보를 CPU가 사용할 수 있도록 load한 후 프로세스 코드를 실행시키는데 이러한 과정을 Context Switching이라 한다. </li>
</ul>
</li>
<li>구동 방식 <ul>
<li>Process는 다양한 원인에 의해 중간에 종료될 수 있다.  (interrupt나 system call에 의해서)  종료되는 P0의 상태를 PCB0(Process Control Block) 저장한다.  새로 시작될 P1의 상태를 PCB1에서 불러오고 해당 프로세스를 실행한다.  상기와 같은 방식으로 Context Switching이 일어난다. 
￼
 </li>
</ul>
</li>
</ul>
<ol start="3">
<li>Threads
Thread</li>
</ol>
<ul>
<li>스레드의 개념: 기본적인 CPU활용의 단위이다. ( 프로세스의 경량화된 방식 ) <ul>
<li>한 프로세스 내의 실행의 흐름 </li>
</ul>
</li>
<li>장점<ul>
<li>많은 자원을 공유하기 때문에 반응이 빠르고</li>
<li>자원공유로 자원을 효율적으로 쓸 수 있고</li>
<li>경제적이고</li>
<li>Multi Process 방식에 적합하다 </li>
</ul>
</li>
</ul>
<p>4.Process </p>
<ul>
<li><p>프로세스의 개념: 실행중인 프로그램</p>
</li>
<li><p>프로세스의 상태: new, running, waiting, ready, terminated </p>
</li>
<li><p>프로세스의 동작</p>
<ul>
<li>new: job queue에 올라가고 어떤 것을 메모리에 올릴지 job scheduler가 정한다. </li>
<li>ready: ready queue에서 CPU할당을 대기한다 (CPU scheduler가 정한다.)</li>
<li>running: CPU를 할당 받은 상태</li>
<li>wating: I/O 나 이벤트가 있을 때 대기 상태가 된다. 이벤트가 끝나면 다시 ready queue로 간다. </li>
<li>terminated: 실행이 끝난 상태  </li>
</ul>
</li>
<li><p>Process 와 Thread의 차이 </p>
<ul>
<li>Process간 자원 공유가 안 되지만 Thread는 공유가 된다 </li>
<li>스레드는 프로세스 내에서 동작하기 때문에 메모리 영역을 독립적으로 받지 못함  </li>
</ul>
</li>
<li><p>프로세스 대신 쓰레드를 사용 시 장점이 무엇인지 논하라</p>
<ul>
<li>Code, Data, Heap의 영역을 공유하기 때문에 Context Switching이 빠름</li>
<li>새로운 프로세스를 생성하는 것이 아니므로 생성과 소멸도 빠르다.</li>
<li>쓰레드간 통신 방법이 훨씬 간단함.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[OS - Chap1 ]]></title>
            <link>https://velog.io/@kuruma-42/OS-Chap1</link>
            <guid>https://velog.io/@kuruma-42/OS-Chap1</guid>
            <pubDate>Fri, 06 Oct 2023 10:42:32 GMT</pubDate>
            <description><![CDATA[<p>컴퓨터 구조 </p>
<ul>
<li>컴퓨터의 구조는 4가지의 컴포넌트로 나눌 수 있다. <ul>
<li>하드웨어: 기초적인 컴퓨팅 자원을 제공한다( CPU, Memory, I/O devices)</li>
<li>OS: 다양한 어플리케이션과 유저 사이에서 하드웨어를 제어하고 협응한다. </li>
<li>어플리케이션 프로그램: 유저들의 컴퓨팅 문제를 해결해줄 시스템<ul>
<li>ex) 워드 프로세서, 컴파일러, 웹브라우저, 데이터베이스, 비디오 게임 </li>
</ul>
</li>
<li>사용자: 사람, 머신, 다른 컴퓨터들 </li>
</ul>
</li>
<li>하드웨어가 가장 밑단 계에 있고, 그 위에 OS가 있다( OS도 프로그램이지만 가장 밑에 들어간다.)</li>
<li>그 위에 시스템과 어플리케이션이 들어간다. </li>
</ul>
<p>Operating System 정의 </p>
<ul>
<li>User View vs. System View </li>
<li>User View<ul>
<li>사용이 쉽고</li>
<li>프로그램을 실행해준다.</li>
</ul>
</li>
<li>OS View <ul>
<li>OS는 자원할당을 한다. <ul>
<li>모든 자원을 관리한다 ( 하드웨어 포함 )</li>
<li>동일한 자원에 동시에 접근했을 떄 제어한다. </li>
</ul>
</li>
<li>OS는 제어 프로그램이다. <ul>
<li>프로그램의 실행을 제어한다. </li>
<li>에러와 적절하지 않은 자원의 사용을 막기 위해 ( 프로그램이 깨지지 않게 보호 )</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Operating System Definition (Cont.)</p>
<ul>
<li>모두 통용되는 정의는 아니다. </li>
<li>‘당신이 OS 시스템을 주문하면 모든 벤더들이 담아주는 모든 것’ 이지만 엄청 다양하다. </li>
<li>‘프로그램에 항상 상주하는 프로그램’ 은 Kernel이다. </li>
<li>Kernel을 OS의 한 부분이다( 그러나 매우 큰 부분이다)<ul>
<li>항상 메모리에 상주한다.</li>
<li>OS라 함은 주로 커널이라 생각할 때가 많다. </li>
</ul>
</li>
</ul>
<p>Computer Startup </p>
<ul>
<li>BootStrap Program OS를 끌어당겨서 컴퓨터를 시작하게 해준다.</li>
<li>일반적으로 ROM( Read Only Memory )나 EEPROM에 저장되어있는 펌웨어이다. </li>
<li>각종 초기화나 진단을 해준다. </li>
<li>마지막 단계로 커널을 불러오고(Disc에서 가져온다) 실행한다. </li>
<li>OS가 올라가야지 Disc Access가 가능하다.</li>
<li>Bootstrap에서부터 시스템을 초기화 하고 시작한다. </li>
</ul>
<p>Computer System Organization </p>
<ul>
<li>Computer-System Operation <ul>
<li>한 개나 한 개 이상의 CPU, Device Controller가 공유 메모리에 접근을 제공하는 버스에 연결되어있다. </li>
<li>하나의 메모리를 공유하기 때문에 경쟁을 한다. </li>
</ul>
</li>
</ul>
<p>Computer System Operation </p>
<ul>
<li>I/O Devices 와 CPU가 병렬적으로 실행된다. </li>
<li>각각의 Device Controller는 특정 장치에 대한 책임을 갖는다. </li>
<li>각각의 Device Controller는 자체 버퍼를 가지고 있따. </li>
<li>CPU는 데이터를 메인 메모리에게 주고 컨트롤러에 버퍼를 통해 정보를 가져온다. </li>
<li>I/O는 장치로 부터 버퍼 컨트롤러로 데이터를 옮긴다. </li>
<li>Device Controller는 CPU에게 interrupt를 통해 알려준다. </li>
</ul>
<p>Common Function of Interrupts </p>
<ul>
<li>Interrupt가 들어오면 서비스 제어권이 넘어간다. </li>
<li>현재 실행하던 프로그램의 주소를 Intupprt 구조에서 반드시 저장한다. </li>
<li>새로 들어온 Interrupt는 다른 Interrupt가 끝나기 전까지 들어오는 것이 불가능하다. Interrupt 유실을 막기 위해서이다. </li>
<li>Trap은 Software-Generated와 에러나 유저의 요청에 의해서 생긴다. </li>
<li>Operating System은 Interrupt Driven이다.<ul>
<li>H/W Interrupt </li>
<li>S/W Interrupt: trap </li>
</ul>
</li>
</ul>
<p>Interrupt TimeLine </p>
<ul>
<li>Interrupt tarnsfer Done ( 전송이 끝나면 )</li>
<li>유저 프로그램을 중단하고 </li>
<li>I/O 처리를 한다. </li>
<li>다시 유저 프로그램을 실행한다. </li>
</ul>
<p>Two I/O Methods </p>
<ul>
<li>동기방식 Process가 끝날 때 까지 기다린다. </li>
<li>비동기 방식 바로 일을 시작하고 끝났다고 알려줌 </li>
</ul>
<p>Operating Ststem Structure </p>
<ul>
<li><p>Multiprogramming은 효율성을 위해 필요하다 </p>
<ul>
<li>단일 유저는 CPU와 I/O Device를 항상 바쁘게 유지할 수 없다. </li>
<li>멀티프로그래밍은 코드와 데이터로 조직된 일이기 때문에 CPU는 항상 실행할 일을 가지고 있다. </li>
<li>모든 작업의 부분집합은 메모리에 지속적으로 있다. </li>
<li>하나의 작업은 Job Scheduling을 통해 선택 되고 실행된다. </li>
<li>CPU가 대기해야할 때 (ex: I/O 때문에 ) 다른 작업으로 바꿔줌 </li>
</ul>
</li>
<li><p>Time Sharing ( MultiTasking ) </p>
<ul>
<li>짧게 CPU를 할당하여 여러가지 프로그램을 사용할 수 있게 한다. </li>
<li>CPU의 작업을 빈번하게 바꾸면서 각각의 작업이 실행되면서 유저와 상호작용할 수 있다. </li>
<li>Response Time 은 Should be &lt; 1 second </li>
<li>각각의 유저는 최소 실행되는 프로그램이 메모리에 있어야 한다. ( Process )</li>
<li>만약 다수의 동시에 실행되려고 대기중이라면 ( CPU Scheduling )</li>
<li>만약 프로세스가 메모리에 맞지 않는다면, 스와핑을 통해 실행되도록 해준다. </li>
</ul>
</li>
<li><p>Time Sharing과 Multi Programming의 가장 큰 차이는 </p>
<ul>
<li>Time Sharing ( CPU Schediling ) </li>
<li>Multi Programming ( Job Scheduling ) </li>
</ul>
</li>
</ul>
<p>Operating System Operations </p>
<ul>
<li>하드웨어를 통한 interrupt </li>
<li>소프트웨어 또는 요청이 만든 Exceiption 이나 Trap <ul>
<li>( Division by Zero, Request for operating system sercie) </li>
</ul>
</li>
<li>프로세스 문제는 무한 루프나, 프로세스가 서로를 변경하려고 하거나 OS를 변경하려고 하는 것들을 포함한다.</li>
<li>Dual Mode는 OS가 스스로의 시스템과 컴포넌트를 지키도록 해준다. <ul>
<li>User Mode 와 Kernel mode가 있다. </li>
<li>Mode bit는 하드웨어에 의해 제공 된다.<ul>
<li>시스템이 실행될 때 유저코드 또는 커널 코드를 구분할 수 있는 능력을 제공 </li>
<li>어떤 수행은 오직 커널에서만 실행되도록 특권 처럼 디자인 된다. </li>
<li>시스템 콜은 유저 모드를 커널 모드로 바꿔주고, 다시 유저에게 반환한다. </li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Transition from User to Kernel Mode </p>
<ul>
<li>유저 프로세스가 실행되고 있다.  ( mode bit = 1 ) </li>
<li>유저 프로세스단에서 시스템 콜릉ㄹ 부른다. ( 커널에 서비스를 요청 )</li>
<li>Trap 발생 ( mode bit = 0 ) </li>
<li>Excute System Call ( 하드웨어에 접근할 특권을 얻게 됨 ) </li>
<li>return ( mode bit = 1 ) </li>
<li>유저 프로세스 단으로 다시 돌아온다 ( return from system call ) </li>
</ul>
<p>Process Management </p>
<ul>
<li>Process란 실행중인 프로그램이다. <ul>
<li>프로세스는 시스템의 일의 단위이다. </li>
<li>프로그램은 수동적인 엔티티이고, 프로세스는 동적인 엔티티이다. </li>
</ul>
</li>
<li>프로세스는 자원을 할당 받아야 한다. <ul>
<li>CPU, Memory, I/O, files, Initialization data </li>
</ul>
</li>
<li>프로세스의 종료는 모든 재사용 가능한 자원을 반납해야한다. </li>
<li>단일 스레드 프로세스는 하나의 프로그램 카운터를 가지고있고 다음에 실횡될 곳의 주소를 가지고있다.<ul>
<li>프로세스는 순차적으로 한 개씩 완료될 때 까지 실행된다.</li>
</ul>
</li>
<li>멀티 스레드 프로세스는 스레드마다 프로그램 카운터를 가지고있다. </li>
<li>일반적으로 시스템은 많은 프로세스를 가지고 있고, 어떤 유저와 어떤 OS에서 단일 혹은 여러 개의 CPU에서 Concurrent하게 실행된다. </li>
<li>CPU와 프로세스와 / 스레드간의 다중화로 인한 동시성 </li>
</ul>
<p>Process ManageMent Activites </p>
<ul>
<li>OS는 프로세스 관리에 대해서 다음과 같은 활동과 연관이 있다. </li>
<li>유저와 시스템 프로세스에 대한 생성과 삭제 </li>
<li>프로세스의 Suspending과 재개 </li>
<li>프로세스 동기화에 대한 메커니즘 제공 </li>
<li>프로세스 소통에 관한 메커니즘 제공 </li>
<li>데드락(교착 상태 - 자원을 서로 잡고 안 놔주는 경우)에 대한 처리 메커니즘 제공</li>
</ul>
<p>Memory Management </p>
<ul>
<li>프로세싱 전후에 모든 데이터는 메모리에 올라온다. </li>
<li>모든 수행은 실행되기 위해 메모리에 올라온다. </li>
<li>메모리 관리는 CPU 활용률과 와 컴퓨터가 유저에게 반응하기 위한 최적화하기 위해 메모리에 뭘 올릴지 결정한다</li>
<li>Memory Management Activities <ul>
<li>계속 추적한다 어떤 파트의 메모리가 현재 상되고 누구에 의해 사용되는지 </li>
<li>어떤 프로세스나 데이터가 메모리에 들어오고 나갈지 결정한다. </li>
<li>필요에 따라 메모리 공간을 할당하고 회수한다. </li>
</ul>
</li>
</ul>
<p>Storage Management </p>
<ul>
<li>OS는 규격과 저장 정보의 논리적인 뷰를 제공한다. </li>
<li>물리적인 프로퍼티들을 논리적 저장 단위로 추상화한다. ( file ) </li>
<li>FileSystem Management <ul>
<li>파일이 모여서 디렉토리가 됨 </li>
<li>대부분의 프로그램에서 접근 제어는 누가 접근할 수 있는지 결정하는 것이다. </li>
<li>OS Activities Include<ul>
<li>파일과 디렉토리를 생성한다.</li>
<li>원시적으로 파일과 디렉토리를 조작한다. </li>
<li>파일을 제 2 저장소에 맵핑한다. </li>
<li>파일을 안정된 저장소에 백업한다. </li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Mass Storage management </p>
<ul>
<li>일반적으로 디스크는 메인메모리에서 사용되는 게 맞지 않는 데이터를 저장하거나 긴 기간 저장해야 하는 데이터를 저장할 때 쓰인다. </li>
<li>적절한 관리가 가장 중요하다. 
컴퓨터 작동의 전체 속도는 디스크 서브 시스템과 그 알고리즘에 달려있다 </li>
<li>OS Activities <ul>
<li>Free Space Mangement </li>
<li>저장 공간 할당 </li>
<li>디스크 스케줄링 </li>
</ul>
</li>
</ul>
<p>I/O Subsystem </p>
<ul>
<li>OS의 목표중 하나는 특정 하드웨어 장치의 특성을 숨겨준다<ul>
<li>유저들이 디바이스의 특성을 하나하나 다 알고 사용하고 대응하려면 피로도가 엄청 상승한다. </li>
</ul>
</li>
<li>I/O subsystem responsible for <ul>
<li>I/O 버퍼링(전송되는 동안 잠시 데이터를 보관한다), 캐싱( 빠른 저장소에 일부 데이터를 퍼포먼스를 위해 저장해놓는다),  Spooling 의 데이터 관리를 한다. </li>
<li>디바이스 드라이버 인터페이스 </li>
<li>하드웨어 장치에 대한 드라이버 </li>
</ul>
</li>
</ul>
<p>Protection and Security </p>
<ul>
<li>Protection: Access나 Process에 의한 자원 보호 </li>
<li>Security<ul>
<li>내부 외부에 대항한 방어 시스템</li>
<li>시스템이 유저들간에서 누가 어떤 것을 해도 되는지 구별한다. ( Access Controll ) <ul>
<li>User 식별된다 (user ID, security IDs) 각 유저에 대해서 이름과 연관되어 있는 번호로</li>
<li>User ID 와 연관된 모든 파일과 프로세스의 엑세스 컨트로를 결정한다. </li>
<li>Group Identifier는 허락한다 사용자 집단에게 정의 되고 제어 관리되도록, 또한 각각의 파일과 프로세스에 연관 되도록</li>
<li>Privileage esclation 특정 아이디가 더 큰 권한을 갖도록 허락해준다. ( Root 권한을 갖게할 것인지 아닌지 ) </li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[RealmDB & Combine Reference]]></title>
            <link>https://velog.io/@kuruma-42/RealmDB-Combine-Reference</link>
            <guid>https://velog.io/@kuruma-42/RealmDB-Combine-Reference</guid>
            <pubDate>Fri, 11 Mar 2022 04:48:27 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.mongodb.com/developer/how-to/realm-swiftui-combine-first-app/">RealmDB Docs</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Monad]]></title>
            <link>https://velog.io/@kuruma-42/Monad</link>
            <guid>https://velog.io/@kuruma-42/Monad</guid>
            <pubDate>Sun, 27 Feb 2022 16:31:28 GMT</pubDate>
            <description><![CDATA[<h3 id="motive">Motive</h3>
<ul>
<li>프로젝트가 너무 바쁘다 보니 개념을 정립하고 싶어서 주말에 공부하다가, 예전에 처음 Swift를 공부할 때 빠르게 한 번 보고 지나간 모나드를 공부하기로 했다.</li>
<li>RealmDB에서 가져온 값이 대부분 Result? 타입으로 가져오는데 무지성으로 CompactMap을 쓰는 게 싫어서 공부를 하려고 했으나 시간이 잘 나지 않았다.</li>
</ul>
<h3 id="context">Context</h3>
<ul>
<li>Context는 지금껏 프로그래밍을 하면서 많이 봐왔지만, 개념을 정리하는 것은 이번이 처음인 것 같다.</li>
<li>컨텍스트는 &#39;Contents&#39;를 담은 그 무엇인가를 뜻한다. </li>
<li>담배각에 안에 담배가 있다면 담배가 컨텐츠이고, 담배각은 컨텍스트이다.</li>
</ul>
<h3 id="optional-as-an-example">Optional As An Example</h3>
<ul>
<li>많은 서적과 블로그 자료들을 찾아보면 대부분 예를 들을 때, Optional을 예로 든다. 그 이유는 Optional이 아마 예를 만들기 가장 쉬운 Monad이기 때문인 것 같다. </li>
<li>Optional을 Wrapped 타입을 인자로 받는 (제네릭)타입이다.</li>
<li>즉, 타입을 인자로 받는 모나드의 첫 번째 조건을 만족하고 </li>
<li>Optional&lt;<code>Int</code>&gt;.init(2)은 다른 타입의 형태를 만들 수 있기 때문에 두 번째 조건을 만족한다.</li>
<li>세 번째 조건은 내 개인적으로는 .map 메서드가 사용이 가능하면 세 번째 조건은 달성된다고 생각하는데 자세한 설명은 아래에서 하겠다. </li>
</ul>
<h4 id="example">Example</h4>
<pre><code class="language-swift">Optional(2)</code></pre>
<ul>
<li>상기와 같이 2라는 숫자를 옵셔널로 감싸면, Context 안에 2라는 Contents가 들어간다고 생각하면 된다. </li>
<li>만약 값이 있다면 &#39;컨텍스트는 2라는 값을 가지고 있다&#39;</li>
<li>만약 값이 없다면 &#39;컨텍스트는 있는데 값이 없다&#39; 라고 할 수 있다.</li>
</ul>
<pre><code class="language-swift">func addThree(_ num: Int) -&gt; Int {
    return num +3
}</code></pre>
<ul>
<li>상기의 메서드에는 Optional(2)를 인자로 전달해도 정상적인 동작을 하지 않는다. </li>
<li>당연히 인자의 타입이 Int로 선언 되어 있기도 하지만, 컨텍스트에 둘러 쌓여 있는 값에 +3을 할 수 없는 것이다. </li>
</ul>
<h3 id="functor">functor</h3>
<ul>
<li>한국말로는 함수객체라고 한다</li>
<li>함수객체란 맵을 적용할 수 있는 컨테이너 타입이다.</li>
<li>우리가 사용하는 Array, Dictionary, Set 등 Swift의 많은 컬렉션 타입이 함수객체이다. </li>
<li>map은 컨테이너 내부의 값을 변형시킬 수 있는 고차함수이다. </li>
<li>그럼 아까의 addThree를 맵을 사용해 적용해보겠다.</li>
</ul>
<pre><code class="language-swift">Optional(2).map(addThree)</code></pre>
<pre><code class="language-swift">var exampleValue : Int? = 2
exampleValue.map {$0 + 3} // Optional(5)</code></pre>
<ul>
<li>상기의 두가지 예로 표현이 가능하겠다. </li>
</ul>
<h3 id="monad">Monad</h3>
<ul>
<li>함수객체 중에서 자신의 컨텍스트와 같은 컨텍스트의 형태로 맵핑할 수 있는 함수객체를 닫힌 함수 객체라고 한다. 모나드는 닫힌 함수객체이다. </li>
<li>즉, Map을 적용할 수 있는 컨테이너이고, 자신의 컨텍스트와 같은 형태로 맵핑할 수 있는 닫힌 함수 객체로 정의할 수 있다. </li>
<li>함수객체는 포장된 값에 함수를 적용할 수 있다. </li>
<li>모나드도 포장된 값을 처리하고 포장된 값을 컨텍스트에 다시 반환하는 함수(맵)을 적용 가능하다. </li>
<li>이 mapping의 결과가 함수객체와 똑같은 컨텍스트를 반환하는 함수객체를 모나드라고 할 수 있고, 이런 맵핑을 수행하도록 flatMap이라는 메서드를 활용한다.</li>
</ul>
<h3 id="condition-of-monad">Condition of Monad</h3>
<ul>
<li>타입을 인자로 받는 타입(특정 타입의 값을 포장)</li>
<li>특정 타입의 값을 포장한 것을 반환하는 메서드가 존재 </li>
<li>포장된 값을 변환하여 같은 형태로 포장하는 메서드가 존재</li>
</ul>
<h3 id="flatmap">flatMap</h3>
<ul>
<li><a href="https://developer.apple.com/documentation/swift/sequence/2905332-flatmap">flatMap : Apple Docs</a>
<img src="https://images.velog.io/images/kuruma-42/post/26ad7022-d01c-4c04-9efb-c61d317344d9/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-02-28%20%EC%98%A4%EC%A0%84%201.21.16.png" alt=""></li>
<li>상기의 사진은 flatMap이 Deprecated된 것을 보여주는 이미지인데 </li>
<li>나도 RealmResult 타입을 맵핑하다가 알게되었고, flatMap이 왜 Deprecated 되었는지는 차후에 알아보려고 한다.</li>
<li><a href="https://useyourloaf.com/blog/replacing-flatmap-with-compactmap/">Replace flatMap</a></li>
<li>그래서 나도 대부분 .compactMap으로 처리를 했다. </li>
<li>어차피 nil을 다 없애주면서 같은 위상으로 펼쳐주기 때문이다.</li>
<li><a href="https://developer.apple.com/documentation/swift/sequence/2950916-compactmap">compactMap : Apple Docs</a>
<img src="https://images.velog.io/images/kuruma-42/post/6927201d-4bff-48ae-ad74-b9b515c30216/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-02-28%20%EC%98%A4%EC%A0%84%201.21.47.png" alt=""></li>
<li>상기의 예시처럼 같은 위상으로 펼쳐준다. </li>
</ul>
<h3 id="review">Review</h3>
<ul>
<li>한 번은 개념적으로 공부하고 넘어가고 싶었다.</li>
<li>한 번 봐서 끝낼 개념이 아니고 일을 하면서 좀 더 주변 지식이 쌓이면 그 때 마다 다시 보면 또 다르고 넓게 보일 개념같다. </li>
<li>최근 함수형 프로그래밍에 관심이 생기면서 개념을 탄탄하게 하기 위해 개념을 정리한다. </li>
<li>최근 기본서를 다시 보면서 모르는 부분을 골라서 읽고 있는데 추후에도 기본 개념들을 정리해 나갈 생각이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Data Structure & SwiftUI References]]></title>
            <link>https://velog.io/@kuruma-42/Data-Structure-SwiftUI-References</link>
            <guid>https://velog.io/@kuruma-42/Data-Structure-SwiftUI-References</guid>
            <pubDate>Fri, 18 Feb 2022 04:50:19 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.geeksforgeeks.org">Data Structure Geeks</a>
<a href="https://www.hackingwithswift.com/100/swiftui">HackingWithSwift SwiftUI Lecture</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Grand Central Dispatch (GCD)]]></title>
            <link>https://velog.io/@kuruma-42/Grand-Central-Dispatch-GCD</link>
            <guid>https://velog.io/@kuruma-42/Grand-Central-Dispatch-GCD</guid>
            <pubDate>Sun, 13 Feb 2022 06:10:15 GMT</pubDate>
            <description><![CDATA[<p>최근 회사에서 프로젝트로 공부할 시간이 전혀 없어서 주말을 이용해 
개념을 정립하는 식의 공부를 하기로 했다. 절대적인 시간을 늘리기로
다짐 했기 때문에 앞으로는 퇴근하고도 블로그 작성을 멈추지 않기로했다. </p>
<p><img src="https://images.velog.io/images/kuruma-42/post/0df9eed9-321e-4baa-ace0-205359b7505f/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-02-12%20%EC%98%A4%ED%9B%84%206.06.58.png" alt=""></p>
<p>멀티코어 하드웨어에서 시스템 적으로 관리되는 큐에게 일을 넘겨주는 것으로 코드를 동시적으로 실행시킨다.</p>
<p><img src="https://images.velog.io/images/kuruma-42/post/dd1f31e1-d40e-412f-bb4d-30e7f4cfff2a/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-02-12%20%EC%98%A4%ED%9B%84%206.33.38.png" alt=""></p>
<p>디스패치 또는 Grand Central Dipatch (GCD)는 언어적 특징과 런타임 라이브러리, 그리고 macOS, iOS, WatchOS 그리고 tvOS 등에 있는 멀티코어 하드웨어에서 실행되는 동시성 코드를 서포트 하기위해 시스템이 체계를 갖추고 종합적으로 발전하도록 하는 시스템 강화등을 가지고 있다.</p>
<p>BSD subsystem, Core Foundation 그리고 CoCoa APIs 등은 계속 위와 같은 강점들을 시스템과 당신의 앱이 더 빨리 , 더 효율적으로 그리고 향상된 반응을 실행하도록 확장해 왔다. </p>
<p>컴퓨팅 코어 수가 서로 다른 여러 컴퓨터 또는 여러 애플리케이션이 이러한 코어를 두고 경쟁하는 환경에서 단일 애플리케이션이 여러 코어를 효과적으로 사용하는 것이 얼마나 어려운지 생각해 보자.</p>
<p>시스템 레벨에서 동작하는 GCD는 실행되고 있는 앱의 수요를 더 수용한다, 그들에게 가능한 시스템 자원을 균형있게 매칭해준다. </p>
<ul>
<li>BSD subsystem : BSD(Berkeley Software Distribution)는 1977년부터 1995년까지 미국 캘리포니아 대학교 버클리(University of California, Berkeley)의 CSRG(Computer Systems Research Group)에서 개발한 유닉스 운영 체제이다. </li>
</ul>
<p><img src="https://images.velog.io/images/kuruma-42/post/77dca44c-55c7-418c-9e94-84014c648e47/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-02-12%20%EC%98%A4%ED%9B%84%207.16.01.png" alt=""></p>
<p>순차적으로 동시적으로 당신의 앱의 메인스레드 또는 백그라운드 스레드에서 일의 실행을 관리하는 객체. </p>
<p><img src="https://images.velog.io/images/kuruma-42/post/6e08016f-93be-498f-8707-68021395c3fe/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-02-12%20%EC%98%A4%ED%9B%84%207.24.07.png" alt=""></p>
<p>DispatchQueue는 당신의 애플리케이션이 블록 형태로 일을 제출할 수 있는  FIFO( First In First Out ) 큐이다. Dispatch queue는 일들을 순차적으로 또는 동시적으로 처리한다. </p>
<p>dispatch queues에 제출된 일은 시스템에서 관리하는 스레드 풀에서 실행된다. 당신의 앱의 메인스레드를 대표하는 dispatch queue를 제외하고는 시스템은 어떤 스레드를 사용할지 보장할 수 없다.</p>
<p>당신은 동기적으로 또는 비동기 적으로 당신의 일을 스케줄링 할 수 있고, 당신이 실행할 일을 동기적으로 스케쥴링할 때는, 당신의 코드는 실행이 끝날 때 까지 기다린다. 당신이 실행할 코드를 비동기적으로 스케줄링 한다면, 당신의 코드는 당신이 실행한 코드를 기다리지 않고 계속 실행된다. </p>
<ul>
<li>Import! 
동기적으로 실행될 일을 메인큐에 놓으려고 실행하면 Deadlock이 발생한다.</li>
</ul>
<p><img src="https://images.velog.io/images/kuruma-42/post/90f29c90-50d8-451e-8eca-acd8b74ebcd5/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-02-12%20%EC%98%A4%ED%9B%84%208.05.42.png" alt=""></p>
<p>과도한 스레드 생성을 피하기 
일의 동시적 실행을 디자인 할 때, 스레드를 차단하는 메소드를 생성하지 말라. 동시적으로 dipatch queue에 스케줄링 된 테스크가 스레드를 막을 때, 시스템은 다른 큐잉된 동시적인 테스크를 실행시키기 위해 추가적으로 스레드를 발생시킨다. 만약 너무 많은 테스크 블럭이 있으면, 당신의 앱의 스레드는 바닥이 날 수도 있다. </p>
<p>앱이 너무 많은 스레드를 사용하는 다른 경우는 private concurrent dispatch queues를 사용하는 경우이다. 왜냐하면 각 dipatch queue는 thread 자원을 소모한다, 추가적인 concurrent dispatch queue를 만드는 것은 thread 소비 문제를 악화시킨다. 직렬 작업의 경우, 당신의 시리얼 큐중 global concurrent queue로 쓸 대상을 설정한다. 이렇게 하면 당신은 큐의 직렬 처리를 유지하면서 동시에 분리된 여러 개의 큐가 스래드를 생성하는 것을 최소화 할 수 있다. </p>
<h3 id="what-is-serial-queue-">What is Serial Queue ?</h3>
<ul>
<li>직렬 큐 </li>
<li>순서가 중요한 작업일 때 사용한다. </li>
<li>한 번에 한 개의 큐만 사용한다. </li>
</ul>
<pre><code class="language-swift">let serialQueue = DispatchQueue(label: &quot;chacha.serial.queue&quot;)

serialQueue.async {
    print(&quot;Task 1 started&quot;)
    // Do some work..
    print(&quot;Task 1 finished&quot;)
}
serialQueue.async {
    print(&quot;Task 2 started&quot;)
    // Do some work..
    print(&quot;Task 2 finished&quot;)
}

/*
Serial Queue prints:
Task 1 started
Task 1 finished
Task 2 started
Task 2 finished
*/</code></pre>
<h3 id="what-is-concurrent-queue-">What is Concurrent Queue ?</h3>
<ul>
<li>병렬 큐 </li>
<li>동시에 여러개의 큐를 사용한다. </li>
<li>data race 가 일어날 확률이 존재한다. </li>
</ul>
<pre><code class="language-swift">let concurrentQueue = DispatchQueue(label: &quot;chacha.concurrent.queue&quot;, attributes: .concurrent)

concurrentQueue.async {
    print(&quot;Task 1 started&quot;)
    // Do some work..
    print(&quot;Task 1 finished&quot;)
}
concurrentQueue.async {
    print(&quot;Task 2 started&quot;)
    // Do some work..
    print(&quot;Task 2 finished&quot;)
}

/*
Concurrent Queue prints:
Task 1 started
Task 2 started
Task 1 finished
Task 2 finished
*/</code></pre>
<h3 id="what-is-data-race">What is Data Race</h3>
<ul>
<li>데이터레이스는 여러 개의 스레드가 하나의 메모리에 동기화 과정 없이 접근하고 최소 한 개의 데이터만 쓰여졌을 때 발생한다. 예를 들면, 우리가 메인스레드에서 한 배열에 있는 데이터를 읽는 도중에 백그라운드 스레드에서 현재 읽고 있는 배열에 새로운 값을 입력하고 있는 경우이다. Data Race는 이상한 테스트 결과와 알 수 없는 충돌의 원인이 된다. 그러므로 정기적으로 Thread Sanitizer 를 사용하는 연습을 하는 것이 좋다. </li>
</ul>
<h3 id="using-a-barrier-on-a-concurrent-queue-to-synchronize-writes">Using a barrier on a concurrent queue to synchronize writes</h3>
<ul>
<li>병렬 큐에 동기적인 Write를 하는 베리어를 사용하라. Barrier Flag를 사용하면 더 안전한 스레드 운용을 할 수 있다. </li>
</ul>
<pre><code class="language-swift">final class Messenger {

    private var messages: [String] = []

    private var queue = DispatchQueue(label: &quot;messages.queue&quot;, attributes: .concurrent)

    var lastMessage: String? {
        return queue.sync {
            messages.last
        }
    }

    func postMessage(_ newMessage: String) {
        queue.sync(flags: .barrier) {
            messages.append(newMessage)
        }
    }
}

let messenger = Messenger()
// Executed on Thread #1
messenger.postMessage(&quot;Hello ChaCha!&quot;)
// Executed on Thread #2
print(messenger.lastMessage) // Prints: Hello Chacha!</code></pre>
<p>상기의 코드는 데이터가 읽어짐과 동시에 입력하도록 세팅한 후 베리어를 쓰는 예제 코드이다. 
flags: .barrier는 평행적으로 동시에 들어온 순간 병렬 큐를 직렬 큐로 바꾼다. 베리어와 함께 선언된 일은 앞서 제출된 일들이 모두 완료될 때 까지 연기된다. 마지막 작업이 끝난 이후 베리어 블록을 실행시키고 난 후 원래의 실행 상태로 돌아간다. </p>
<h3 id="asynchronous-vs-synchronous-tasks">Asynchronous vs synchronous tasks</h3>
<ul>
<li>DispatchQueue 작업은 동기적으로 또는 비동기적으로 작동할 수 있다.
주된 차이는 작업을 만들 때 발생한다. 동기적으로 시작하는 일은 작업이 끝날 때 까지 다른 스레드가 불리는 것을 막는다. 비동기적으로 시작한 일은 바로 리턴 되는데 다른 스레드들이 불리는 것을 막지 않는다. 만약 메인스레드에서 작업을 한다고 가정해보자, 오래 걸리는 동기 메소드를 사용하는 것을 스스로 막고싶을 것이다. 이것은 메인스레드를 막는 결과를 만들 것이며, UI는 아마 반응하지 않을 것이다. </li>
</ul>
<h3 id="how-about-the-main-thread">How about the main thread?</h3>
<p>main dispatch queue는 애플리케이션의 메인스레드에서 작업을 실행시키는 전반적으로 이용 가능한 직렬큐이다. 메인 스레드에서는 앱의 UI를 업데이트 하는 곳으로 사용되기 때문에 main thread queue를 사용할 때는 주의해야한다. 그러므로, 앞서 설명한 dispatch API들을 다른 스레드에서 작업을 수행시키는 것은 가치가 있다. 우리는 무거운 일을 백그라운드 큐로 시작할 수 있고 이 일이 끝나면 메인큐로 돌아올 수 있다. </p>
<h3 id="avoiding-excessive-thread-creation">Avoiding excessive thread creation</h3>
<p>아마 많은 큐를 만들어서 앱의 퍼포먼스를 향상시키려고 할 것이다. 안타깝게도,
스레드를 생성하는 것은 리소스를 사용하기 때문에 과도한 스레드를 만드는 것은 피해야한다. </p>
<p>이러한 과도한 스레드 생성에 일반적인 두개의 시나리오가 있다. 
너무 많은 blocking 되는 작업( await 를 사용하는 )을 병렬큐에 추가하면 
앱에서는 다른 작업을 처리하기 위해서 추가적인 스레드를 만든다. 그리고 dipatch queues가 너무 많이 존재해도 스레드 자원을 모두 소모한다. </p>
<h3 id="how-to-prevent-excessive-thread-creation">How to prevent excessive thread creation?</h3>
<p>가장 좋은 방법은 글로벌 병렬 dispatch queues를 사용하는 것이다. 
이렇게 하면 너무 많은 concurrnet queue를 생성하는 것을 막을 수 있을 것이다. 이것과는 별개로, 우리는 long-blocking task를 실행하는 것 자체를 주의해야한다. </p>
<pre><code class="language-swift">DispatchQueue.global().async {
    /// Concurrently execute a task using the global concurrent queue. Also known as the background queue.
}</code></pre>
<p>글로벌 큐는 상기와 같이 만들 수 있다. 
이 글로벌 병렬큐는 백그라운드 큐라고 알려져있기도 하다, 그리고 메인큐의 옆에서 사용된다. </p>
<h3 id="review">Review</h3>
<ul>
<li>쓸 줄은 알아도 정확히 뭔지 모른다면 안 쓰느니만 못하다는 생각이 들었다.</li>
<li>개념을 정확히 하자는 스스로의 프로젝트를 계속하겠다. </li>
<li>오늘 이 것을 공부하는 와중에도 회사에서 고쳐야겠다고 생각하는 코드들이 많다.</li>
<li>베리어 부분은 실제로 적용하고 쓰는데는 학습시간이 좀 더 필요하다고 생각한다. </li>
<li>현재도 깊게 공부한 것은 아니지만 당장 이번에 하고있는 iCloud Data Back up logic에 적용해서 해결할 수 있는 문제들이 많을 것 같다.</li>
</ul>
<ul>
<li>참고문서 
<a href="https://developer.apple.com/documentation/DISPATCH">Apple Docs GCD</a>
<a href="https://developer.apple.com/documentation/dispatch/dispatchqueue">Apple Docs DispatchQueue</a>
<a href="https://www.avanderlee.com/swift/concurrent-serial-dispatchqueue/">Code Lab</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Apple Privacy & Data ]]></title>
            <link>https://velog.io/@kuruma-42/Apple-Privacy-Data</link>
            <guid>https://velog.io/@kuruma-42/Apple-Privacy-Data</guid>
            <pubDate>Wed, 09 Feb 2022 02:42:49 GMT</pubDate>
            <description><![CDATA[<p><a href="https://developer.apple.com/kr/app-store/user-privacy-and-data-use/">Privacy &amp; Data</a></p>
<p>데이터 스크래핑 및 데이터 거래를 좋아하지 않을 수 있겠다는 생각을 해본다.
화면을 숨기면서 유저 데이터를 스크래핑 하는 부분이 AppStore를 통과할 수 있을까?</p>
<p><a href="https://developer.apple.com/kr/app-store/review/guidelines/#5.1.1">Appstore Review Guidelines 5.1.1</a>
프로젝트 진행이 아무리 바빠도 이러한 상기의 심사 데이터와<br>Privacy 정책의 변화를 체크하고 가지 않으면 개발 목적조차 잘못될 수 있다.
페이스북 같은 일이 얼마든지 일어날 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WWDC21 ARC Reference]]></title>
            <link>https://velog.io/@kuruma-42/WWDC21-ARC-Reference</link>
            <guid>https://velog.io/@kuruma-42/WWDC21-ARC-Reference</guid>
            <pubDate>Fri, 04 Feb 2022 08:43:27 GMT</pubDate>
            <description><![CDATA[<p><a href="https://developer.apple.com/videos/play/wwdc2021/10216/">WWDC21 ARC Reference</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[How To Separate UI For Each Device ]]></title>
            <link>https://velog.io/@kuruma-42/How-To-Separate-UI-For-Each-Device</link>
            <guid>https://velog.io/@kuruma-42/How-To-Separate-UI-For-Each-Device</guid>
            <pubDate>Fri, 28 Jan 2022 02:32:03 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-swift">/// Mark: - UIDevice Extension
/// Feature: 디바이스별 UI 및 로직 대응을 위한 extension
/// HowToUse: UIDevice.current. 뒤에 아래의 메소드명을 적으면 됨.
extension UIDevice {
    public var isiPhoneSE: Bool {
        if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.phone &amp;&amp; (UIScreen.main.bounds.size.height == 568 || UIScreen.main.bounds.size.width == 320) {
            return true
        }
        return false
    }

    // iphone 7,8
    public var isiPhoneSE2: Bool {
        if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.phone &amp;&amp; (UIScreen.main.bounds.size.height == 667 || UIScreen.main.bounds.size.width == 375) {
            return true
        }
        return false
    }

    public var isiPhonePlus: Bool {
        if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.phone &amp;&amp; (UIScreen.main.bounds.size.height == 736 || UIScreen.main.bounds.size.width == 414) {
            return true
        }
        return false
    }

    public var isiPad: Bool {
        if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad &amp;&amp; (UIScreen.main.bounds.size.height == 1024 || UIScreen.main.bounds.size.width == 768) {
            return true
        }
        return false
    }
    public var isiPadPro12: Bool {
        if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad &amp;&amp; (UIScreen.main.bounds.size.height == 1366 || UIScreen.main.bounds.size.width == 1366) {
            return true
        }
        return false
    }
}</code></pre>
<p>SwiftUI에서 가끔 ViewControllerRepresentable을 프로토콜을 준수하다 보면 
얘기치 않게 ZStack으로 해결해야 하는 경우가 생긱는데 이럴 경우에는 아주 문제가 복잡해진다.</p>
<p>이번의 경우에는 보안키패드를 적용하면서 보안 키패드 솔루션이 UIKit용으로 나온 것이었고,
이것을 SwiftUI로 Wrapping해서 쓰다보니 투명한 판처럼 보안 키패드만 눌리고, 다른 UIButton이 
눌리지 않게 되었다. </p>
<p>이럴 경우 패딩을 잘 쓸 수 없는데 어쩔 수 없이 상기의 UIDevice.current를 조건문으로 줘서 
iPhone SE, SE2, 7, 8 과 같이 화면이 작은 Device에는 개별 대응을 해줘야한다.</p>
<p>SwiftUI에 맞는 라이브러리들이 하루빨리 많이 나왔으면 좋겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SwiftUI Memory Leak -> Check Retain Cycle]]></title>
            <link>https://velog.io/@kuruma-42/SwiftUI-Memory-Leak-Check-Retain-Cycle</link>
            <guid>https://velog.io/@kuruma-42/SwiftUI-Memory-Leak-Check-Retain-Cycle</guid>
            <pubDate>Tue, 25 Jan 2022 12:02:11 GMT</pubDate>
            <description><![CDATA[<h3 id="memory-problem">Memory Problem</h3>
<ul>
<li>어느샌가 앱을 테스트하면 메모리가 해제되는 것이 거의 없고 메모리가 쌓여만 갔다. 
Combine으로 하는 Observing이 계속 쌓이는 것일까? 하는 생각을하고 ViewModel에 
deinit을 달고 print를 전부 찍어봤다. </li>
</ul>
<h3 id="reason-why-memory-problem-happend">Reason Why Memory Problem Happend?</h3>
<p>우리 앱에서 메모리 관리에서 가장 중요한 것이 deinit이다 </p>
<pre><code class="language-swift"> deinit {
        subscription.removeAll()
    }</code></pre>
<p>우리는 deinit에서 옵저빙을 해제시켜준다. 
그렇다면 모델에서 deinit이 불리지 않았다는 것은
계속 Init으로 쌓이기만하고 메모리는 해제되지 않았다는 것이다.</p>
<h3 id="arc">ARC?</h3>
<p>ARC란 Auto Refernece Counting라는 개념인데 Reference Count가 0이 되면 자연스럽게 메모리에서 해제시켜주는 Swift의 메모리 관리 모델이다.</p>
<p>View가 Disappear 되면서 Reference Count는 0이 되었을텐데 메모리가 계속 쌓인다는 것으로 볼 때 
의심할 수 있는 것은 한 가지다. &#39;Retain Cycle&#39; </p>
<h3 id="retain-cycle">Retain Cycle?</h3>
<p>강한 참조가 일어나는 부분이 어디일까 하고 우리 앱에 전체에 [weak self]와 [unowned self]를 
검색해보았다. 놀랍게도 그 어떤 뷰모델도 weak self와 unowned self를 사용한 곳이 없었다. 
Combine을 사용한 부분에서 @published를 참조하고 있고, sink 있는 곳에서 fix가 뜨니깐 
무의식적으로 self를 붙이라니깐 fix 버튼을 눌러 모든 곳에 강한 참조를 발생시킨 것이다. </p>
<pre><code class="language-swift">    /// Mark: - setObserver
    /// feature : 유저가 4자리를 입력하면 비밀번호가 맞는지 여부를 체크한다.
    /// 비밀번호가 맞으면 appScreenLockMode의 상태를 바꿔 잠금을 해제하고
    /// 비밀번호가 틀리면 비밀번호를 리셋하고 비밀번호가 흔들리는 shake애니메이션을 실행시킨다.
    func setObserver(appScreenLockOffAction: @escaping () -&gt; Void) {
        let appScreenLockNumberSubscriber = $plainText
            .removeDuplicates()
            .subscribe(on: DispatchQueue.main)
            .sink { [unowned self] value in
                // 비밀번호 4자리를 입력했을 경우
                if value.count == 4 {
                    // 비밀번호를 해쉬한다.
                    let hashedText = value.sha256()
                    print(&quot;APP SCREEN LOCK :: PLAINTEXT HASH &amp; PASSWORD CHECK&quot;)
                    // 해쉬된 값이 저장되어있는 비밀번호와 같을 경우
                    if hashedText == getUD(key: self.APP_SCREEN_LOCK_PASSWORD) {
                        DispatchQueue.main.async {
                            print(&quot;APP SCREEN LOCK :: USER ENTER CORRECT PASSWORD&quot;)
                            // Nfilter Data 초기화
                            isNfilterNeedClear.toggle()
                            // 비밀번호 상태 값 초기화
                            resetPasswordString()
                            print(&quot;APP SCREEN LOCK :: PROGRAMMATICALLY OPERATE PRESS KEY PAD RELOAD BUTTON &quot;)
                            appScreenLockMode = .OffScreenLock
                            appScreenLockMode?.getMessageAppScreenLockMode()
                            appScreenLockOffAction()
                        }
                    }
                    // 해쉬된 값이 저장되어있는 비밀번호와 다른 경우
                    else {
                        DispatchQueue.main.async {
                            print(&quot;APP SCREEN LOCK :: USER ENTER WRONG PASSWORD&quot;)
                            // 비밀번호가 틀렸을 경우 Shake Animation 효과
                            isWrongPassword = true
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                                isWrongPassword = false
                            }
                            // Nfilter Data 초기화
                            isNfilterNeedClear.toggle()
                            // 비밀번호 상태 값 초기화
                            resetPasswordString()
                            print(&quot;APP SCREEN LOCK :: PROGRAMMATICALLY OPERATE PRESS KEY PAD RELOAD BUTTON &quot;)
                        }
                    }
                }
            }
            .store(in: &amp;subscription)
    }</code></pre>
<p>상기의 테스트 코드에서 [unowned self]를 적용하자 @stateObject의 deinit이 불렸고
Observing을 해제했다. </p>
<h3 id="review">Review</h3>
<ul>
<li>fix가 뜬다고 self 붙이라고 뜨면 무지성으로 self를 누르면 안 된다. </li>
<li>weak self의 경우 self를 gurad let으로 한 번 확인해 준 후에 로직을 짜는 편이 좋고</li>
<li>unowned self의 경우 뷰나 오브젝트가 확실하게 존재할 때 써야 한다.  </li>
<li>프로젝트 전체에 적용해서 Refactoring에 들어가기 전에 Self, unowned self, weak self, ARC에 대해서 세미나를 진행한 후 작업에 들어가야겠다.</li>
<li>세미나를 진행하기 전에 블로그에 정리자료를 올려야겠다.</li>
<li>이렇게라도 발견하고, 개선할 수 있어서 좋은 것 같다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Visit Apple Github]]></title>
            <link>https://velog.io/@kuruma-42/Visit-Apple-Github</link>
            <guid>https://velog.io/@kuruma-42/Visit-Apple-Github</guid>
            <pubDate>Mon, 10 Jan 2022 01:21:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/kuruma-42/post/554a2b7e-8d21-4353-8a0c-7d9fe3389774/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-10%20%EC%98%A4%EC%A0%84%209.52.03.png" alt="">
고등학생들이 오픈 캠퍼스를 하면 가고싶은 대학교에 가듯, 애플의 깃허브에 방문해봤다.
그리고 Swift Repository는 C++이라 참고 코딩 방식을 참고하기 어려워서 </p>
<p>swift-driver등에 들어가서 여기 직원들은 어떤 식으로 코딩을 할까 알아봤다.</p>
<p><img src="https://images.velog.io/images/kuruma-42/post/98facd59-de0b-4746-95b6-8ea0d2526e80/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-10%20%EC%98%A4%EC%A0%84%2010.16.36.png" alt=""></p>
<p><img src="https://images.velog.io/images/kuruma-42/post/fae03d73-5c1c-407d-8e3d-ebb365e93db5/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-10%20%EC%98%A4%EC%A0%84%2010.16.07.png" alt=""></p>
<p>찾아보니깐 SwiftLint를 세밀하게 커스터마이징해서 Lint를 적용하고 있는듯 했으나 
어느정도 앞으로 코딩을 할 때 스타일 정도 참고를 할 수 있겠다고 생각했다.</p>
<p>정말 큰 프로젝트가 돌아가고, 많은 커밋이 일어나고 있으며 130개가 넘는 풀리퀘가 있었고, 
수 많은 컨트리뷰터가 있었다. </p>
<p>현재는 나도 경력이 1년차이고, 대규모의 iOS 프로젝트가 어떻게 관리되고 어떻게 만들어지는지 
본 적이 없기 때문에 이런 식으로 다른 대기업의 깃허브에 방문해서 그들의 작업 방식을 참고해보는 것도 좋다고 생각했다. </p>
<p>어렴풋이 왜 회사 일 하기도 바쁜 개발자들에게, 오픈소스에 기여해보라고 하는지 알 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FireBase Google Analytics Log Setting ]]></title>
            <link>https://velog.io/@kuruma-42/FireBase-Google-Analytics-Log-Setting</link>
            <guid>https://velog.io/@kuruma-42/FireBase-Google-Analytics-Log-Setting</guid>
            <pubDate>Thu, 06 Jan 2022 05:10:37 GMT</pubDate>
            <description><![CDATA[<h3 id="why-we-use-google-analytics-log-">Why we use Google Analytics Log ?</h3>
<ul>
<li>GoogleAnalytics를 사용하는 이유는 간단하다. </li>
<li>유저를 특정해 수익을 창출하기 위해서이다. </li>
<li>어떤 이벤트를 특정 연령대가 클릭을 많이 한다는 것만 알아도 우리는 쉽게 마케팅을 할 수 있다.</li>
<li>이번에는 Google Analytics를 커스터마이징 하게 되었다. </li>
</ul>
<p><a href="https://firebase.google.com/docs/analytics/events?platform=ios">FireBase Docs: Event Logging</a> 
상기의 문서를 참고해서 만들었다. </p>
<pre><code class="language-swift">Analytics.logEvent(AnalyticsEventSelectContent, parameters: [
  AnalyticsParameterItemID: &quot;id-\(title!)&quot;,
  AnalyticsParameterItemName: title!,
  AnalyticsParameterContentType: &quot;cont&quot;,
])</code></pre>
<p>상기의 AnalyticsEventSelectContent kFIRSelectContent 추천 이벤트를 로깅하는 방법을 보여준다. </p>
<pre><code class="language-swift">Analytics.logEvent(&quot;share_image&quot;, parameters: [
  &quot;name&quot;: name as NSObject,
  &quot;full_text&quot;: text as NSObject,
])</code></pre>
<p>상기의 코드는 이벤트 이름을 커스터마이징 한 것을 보여준다. 
아무래도 권장해준 AnalyticsEventSelectContent로 이벤트를 보내게 되면 이벤트를 모두 Parameter로 분류할 수 있을 것 같았지만, 기획 마케팅 팀에서는 데이터 확인이 안 된다고 했다. </p>
<p>그래서 확인해본 결과 parameter로 보낸 데이터는 실시간 개요에서 확인이 가능했으며 
5 - 30분 사이에 모두 휘발되는 데이터였다 </p>
<p>그래서 우리는 이벤트 이름 자체를 커스터마이징 해줄 필요가 있었고 </p>
<pre><code class="language-swift">/// setAnalyticsButton : 데이터 연결하기 버튼 이벤트를 눌렀을 때 Google Analytics에 log를 보내는 function
/// - Parameters:
///   - eventName: 이벤트 이름 ( ex 은행 국민은행 ios) 이 String 형태로 옴.
///   - name: 페이지 이름을 보냄 ( Analytics에서 30분이 지나면 없어지는 데이터)
///   - content: 이벤트 이름과 같은 형태 (ex 은행 국민은행 ios)이 String 형태로 옴 ( Analytics에서 30분이 지나면 없어지는 데이터)
func setAnalyticsButton(eventName: String, name: String, content: String) {
#if DEBUG
    // FOR TEST
    //    print(&quot;-------------------------------------------------------------------------&quot;)
    //    // 이벤트 이름
    //    var event = eventName + &quot;_ios&quot;
    //    print(&quot;GOOGLE ANALYTICS BUTTON EVENT NAME :: \(event)&quot;)
    //
    //    // Google Analytics 실시간 개용에서 보여질 데이터
    //    let sendData = &quot;\(name) [\(content)])&quot;
    //    print(&quot;setAnalyticsButton ::: \(sendData)&quot;)
    //
    //    // 이벤트 송신
    //    Analytics.logEvent(event, parameters: [
    //        AnalyticsParameterItemName: sendData
    //    ])
    //    print(&quot;-------------------------------------------------------------------------&quot;)

#else

    print(&quot;-------------------------------------------------------------------------&quot;)
    // 이벤트 이름
    var event = eventName + &quot;_ios&quot;
    print(&quot;GOOGLE ANALYTICS BUTTON EVENT NAME :: \(event)&quot;)

    // Google Analytics 실시간 개용에서 보여질 데이터
    let sendData = &quot;\(name) [\(content)]&quot;
    print(&quot;SET ANALYTICS BUTTON ::: \(sendData)&quot;)

    // 이벤트 송신
    Analytics.logEvent(event, parameters: [
        AnalyticsParameterItemName: sendData
    ])
    print(&quot;-------------------------------------------------------------------------&quot;)
#endif
}</code></pre>
<ul>
<li>상기의 코드랩 처럼 이벤트 이름으로 보내도록 커스터마이징 해줬다. </li>
<li>여기서 디버그모드 빌드인지 릴리즈 빌드인지 구분해줘야 했고, </li>
<li>릴리즈 버전에서만 로그가 집계가 되야하기 때문에 #if Debug 와 #else로 나눴고</li>
<li>디버그 쪽에는 테스트가 가능한 코드만 남겨놨다.</li>
</ul>
<p><img src="https://images.velog.io/images/kuruma-42/post/8ea1c4b0-ed8e-400f-a380-dc41ad1dbe73/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-06%20%EC%98%A4%ED%9B%84%202.06.34.png" alt=""></p>
<ul>
<li>이벤트 이름이 찍히는 것을 확인할 수 있다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[How To Send E-mail In App ]]></title>
            <link>https://velog.io/@kuruma-42/How-To-Send-E-mail-In-App</link>
            <guid>https://velog.io/@kuruma-42/How-To-Send-E-mail-In-App</guid>
            <pubDate>Wed, 05 Jan 2022 10:34:04 GMT</pubDate>
            <description><![CDATA[<h3 id="why-we-use-this">Why We Use This?</h3>
<ul>
<li>생각보다 앱에서 이메일을 보내야 하는 일이 있을 수 있다. </li>
<li>우선 이 기능을 사용하려면 iPhone 네이티브 앱에 자신의 메일을 연동해놔야 한다. </li>
<li>하지만 iOS유저의 경우 메일을 연동 해놓는 사람들이 그렇게 까지 많지 않아서 </li>
<li>진짜 어쩔 수 없는 상황이 아니라면 메일은 서버사이드에서 해결하는 것이 좋지 않을까 생각한다.</li>
</ul>
<h3 id="reference">Reference</h3>
<ul>
<li><a href="https://developer.apple.com/documentation/messageui/mfmailcomposeviewcontroller">Apple Docs :: MFMailComposeViewController</a></li>
</ul>
<h3 id="import">Import</h3>
<pre><code class="language-swift">import MessageUI</code></pre>
<p>MessageUI를 먼저 임포트를 해준다. </p>
<h3 id="protocol">Protocol</h3>
<pre><code class="language-swift">class MailViewController : UIViewController, MFMailComposeViewControllerDelegate</code></pre>
<p>우선은 상기의 코드에서 볼 수 있듯 메일 기능에 대한 콜백을 받기위해 MFMailComposeViewControllerDelegate을 준수해준다. </p>
<h3 id="code">Code</h3>
<pre><code class="language-swift">if !MFMailComposeViewController.canSendMail() {
    print(&quot;Mail services are not available&quot;)
    return
}</code></pre>
<p>상기의 코드는 이 디바이스가 메일이 연동이 되어있는지 안 되어있는지 판단해주는 펑션이다.
네이티브 메일 앱에 연동이 안 되어있으면 이 앱은 사용할 수 없는데 바로 여기서 필터링 해주는 것이다. </p>
<pre><code class="language-swift">let composeVC = MFMailComposeViewController()
composeVC.mailComposeDelegate = self

// Configure the fields of the interface.
composeVC.setToRecipients([&quot;address@example.com&quot;])
composeVC.setSubject(&quot;Hello!&quot;)
composeVC.setMessageBody(&quot;Hello from California!&quot;, isHTML: false)

// Present the view controller modally.
self.present(composeVC, animated: true, completion: nil)</code></pre>
<p>연동이 되어있는 곳에 메일의 내용을 작성해준다. 
받는 사람의 경우 배열로 되어있어서 여러명에게 메일을 작성할 수 있고,
제목과 메세지 내용을 작성할 수 있다. </p>
<p>그렇다면 파일 첨부도 가능할까 생각할 수 있다. </p>
<pre><code class="language-swift">let attachmentData = try Data(contentsOf: fileURL)
                mailComposer.addAttachmentData(attachmentData, mimeType: &quot;txt&quot;, fileName: &quot;Youtube_Keyword_data&quot;)</code></pre>
<p>데이터의 URL을 지정해준뒤 상기의 코드를 작성해주면 쉽게 첨부할 수 있다. 
그렇다면 이제는 메세지 기능의 콜백은 어디서 받는 방법이다.</p>
<p><a href="https://developer.apple.com/documentation/messageui/mfmailcomposeviewcontrollerdelegate">Apple docs : MFMailComposeViewControllerDelegate</a>
<a href="https://developer.apple.com/documentation/messageui/mfmailcomposeviewcontrollerdelegate/1616880-mailcomposecontroller">Apple docs : mailComposeController(_:didFinishWith:error:)</a>
<a href="https://developer.apple.com/documentation/messageui/mfmailcomposeresult">Apple docs : MFMailComposeResult enum</a>
상기의 문서 세개를 조합해보면 
어떻게 선언을 해야하는지, 결과로는 어떤 enum 타입이 떨어지는지 자세히 나와있다. </p>
<pre><code class="language-swift">func mailComposeController(controller: MFMailComposeViewController,
                           didFinishWithResult result: MFMailComposeResult, error: NSError?) {
    // Check the result or perform other tasks.

    // Dismiss the mail compose view controller.
    controller.dismiss(animated: true, completion: nil)
}</code></pre>
<p>코드는 상기와 같다. 
이제 여기서 해줄 일은 result 타입별로 실행시켜줄 일을 나누는 것 뿐이다. 
switch 코드만 넣어주면 된다. </p>
<pre><code class="language-swift">    //MARK:- MailcomposerDelegate

    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        switch result {
        case .cancelled:
            print(&quot;User cancelled&quot;)
            break

        case .saved:
            print(&quot;Mail is saved by user&quot;)
            break

        case .sent:
            print(&quot;Mail is sent successfully&quot;)
            break

        case .failed:
            print(&quot;Sending mail is failed&quot;)
            break
        default:
            break
        }

        controller.dismiss(animated: true)

    }</code></pre>
<p>상기의 delegate function에서 메일 기능의 콜백을 받을 수 있다. 
전체적인 완성코드는 하기에 첨부하겠다. </p>
<pre><code class="language-swift">//
//  emailViewController.swift
//  FileManagerRealmBackUpTest
//
//  Created by Yong Jun Cha on 2021/12/07.
//

import Foundation
import MessageUI
import UIKit
import Combine

class EmailViewController : UIViewController, MFMailComposeViewControllerDelegate {

    var cancellables: [AnyCancellable] = []

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    @IBAction func sendKeywordDataByUsingEmail(_ sender: Any) {
        if MFMailComposeViewController.canSendMail() {
            let mailComposer = MFMailComposeViewController()
            mailComposer.setSubject(&quot;경기도 알림톡 유튜브 검색어 백업자료&quot;)
            mailComposer.setMessageBody(&quot;\(Date())까지의 경기도 알림톡 유튜브 검색자료 백업데이터 입니다. &quot;, isHTML: false)
            mailComposer.setToRecipients([&quot;yongjun.cha@snplab.io&quot;])
            let fileManager = FileManager()
            let documentURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let fileURL = documentURL.appendingPathComponent(&quot;Youtube_Keyword_data.txt&quot;)
            print(&quot;URL :: \(documentURL.description)&quot;)



            do {
                let attachmentData = try Data(contentsOf: fileURL)
                mailComposer.addAttachmentData(attachmentData, mimeType: &quot;txt&quot;, fileName: &quot;Youtube_Keyword_data&quot;)
                mailComposer.mailComposeDelegate = self
                self.present(mailComposer, animated: true
                             , completion: nil)
            } catch let error {
                print(&quot;We have encountered error \(error.localizedDescription)&quot;)
            }

        } else {
            print(&quot;Email is not configured in settings app or we are not able to send an email&quot;)
        }
    }



    //MARK:- MailcomposerDelegate

    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        switch result {
        case .cancelled:
            print(&quot;User cancelled&quot;)
            break

        case .saved:
            print(&quot;Mail is saved by user&quot;)
            break

        case .sent:
            print(&quot;Mail is sent successfully&quot;)
            break

        case .failed:
            print(&quot;Sending mail is failed&quot;)
            break
        default:
            break
        }

        controller.dismiss(animated: true)

    }
}</code></pre>
<h3 id="review">Review</h3>
<ul>
<li>Protocol &amp; Delegate Pattern을 이해하고나니 공식 문서를 보는 것이 훨씬 간편해졌다.</li>
<li>다만 SwiftUI에 적용할 때는 ViewContorller를 사용해야하는 경우도 적지 않고</li>
<li>그렇게 할 때 따로 Injector를 이용해서 넣어줘야하기 때문에 SwiftUI식으로 넣는 방법도 </li>
<li>CodeLab해봐야겠다는 생각을 했다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[How To Use RealmDB With Combine Future ]]></title>
            <link>https://velog.io/@kuruma-42/How-To-Use-RealmDB-With-Combine-Future</link>
            <guid>https://velog.io/@kuruma-42/How-To-Use-RealmDB-With-Combine-Future</guid>
            <pubDate>Wed, 05 Jan 2022 09:33:21 GMT</pubDate>
            <description><![CDATA[<h3 id="when-should-we-send-sign-to-observer">When should We Send Sign To Observer?</h3>
<ul>
<li>Combine Future를 사용하면서 Realm의 Transaction이 정확히 언제 끝나는지</li>
<li>그 때는 언제인지 어떻게 보내야 하는지 생각보다 문서가 없었고, 시도되지 않은 것 같아서 글을 남긴다.</li>
</ul>
<h3 id="why-we-use-combine-future">Why We Use Combine Future?</h3>
<ul>
<li>RealmToken을 쓰면 UI업데이트는 다 거기서 하면 되지 않냐고 생각하는 사람이 있겠지만, </li>
<li>생각보다 순차적으로 일이 끝나야하고 세부 컨트롤이 들어가야하는 일이 많다. </li>
<li>RealmToken으로 다 해결되면 내가 이 글을 안 쓰고 있었을 것 같다. </li>
</ul>
<h3 id="codelab">CodeLab</h3>
<pre><code class="language-swift">   public func insertFormattedKeyword(keywordEngineType: Int, keywords: [Keyword], promise: @escaping(Result&lt;Bool,Error&gt;) -&gt; Void) {
        realm.executeTransactionAsync {
            do {
                guard let keywordEngine = realm.object(ofType: KeywordEngine.self, forPrimaryKey: keywordEngineType) else {
                    print(&quot;Contact \(keywordEngineType) not found&quot;)
                    return
                }
                try realm.write() {
                    print(&quot;KEYWORD COUNT DAO :: \(keywords.count)&quot;)
                    keywordEngine.keywords.append(objectsIn: keywords)
                    print(&quot;KEYWORD TIMESTAMP UPDATE : \(Date())&quot;)
                    keywordEngine.timestamp = Date()
                }
                promise(.success(true))
            } catch let error as NSError {
                print(&quot;\(error)&quot;)
                promise(.failure(error))
            }
        }
    }</code></pre>
<ul>
<li><p>그렇다면 언제 우리는 Combine Future에 success 신호를 보내야할까? </p>
</li>
<li><p>공식 문서에도 안 나와있고 내 나름대로 연구를 했다. </p>
</li>
<li><p>우선은 상기의 코드에서 try realm.write의 구간이 모두 끝난 후에 성공신호를 보내면 된다. </p>
</li>
<li><p>이유는 하기의 코드와 함께 설명하겠다. 
```swift
/**</p>
</li>
<li><p>parameter tokens: An array of notification tokens which were returned</p>
<pre><code>                   from adding callbacks which you do not want to be
                   notified for the changes made in this write transaction.</code></pre><ul>
<li><p>parameter block: The block containing actions to perform.</p>
</li>
<li><p>returns: The value returned from the block, if any.</p>
</li>
<li><p>throws: An <code>NSError</code> if the transaction could not be completed successfully.</p>
<pre><code>    If `block` throws, the function throws the propagated `ErrorType` instead.</code></pre></li>
<li><p>/
@discardableResult
public func write<Result>(withoutNotifying tokens: [NotificationToken] = [], _ block: (() throws -&gt; Result)) throws -&gt; Result {
  beginWrite()
  var ret: Result!
  do {</p>
<pre><code>  ret = try block()</code></pre><p>  } catch let error {</p>
<pre><code>  if isInWriteTransaction { cancelWrite() }
  throw error</code></pre><p>  }
  if isInWriteTransaction { try commitWrite(withoutNotifying: tokens) }
  return ret
}
```</p>
</li>
</ul>
</li>
<li><p>상기의 코드는 realm의 코드이다. </p>
</li>
<li><p>realm.write의 클로저에 코드들을 다 담으면 </p>
</li>
<li><p>beginWrite()로 realmDB의 트랜잭션을 시작하고</p>
</li>
<li><p>commitWrite()로 realmDB를 커밋한다. </p>
</li>
<li><p>따라서 write문이 끝나는 시점은 DB입력이 commit이나 cancel이 결정된 상태이다. </p>
</li>
<li><p>우리는 그 순간이 바로 약속의 시간 promise(.success(true))를 해야하는 순간이다. </p>
</li>
<li><p>Error Type도 정해져있으니 Catch 쪽에서는 NSError를 에러 타입으로 캐스팅해줘야한다. </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Protocol - Delegate Pattern]]></title>
            <link>https://velog.io/@kuruma-42/Protocol-Delegate-Pattern</link>
            <guid>https://velog.io/@kuruma-42/Protocol-Delegate-Pattern</guid>
            <pubDate>Tue, 21 Dec 2021 06:54:16 GMT</pubDate>
            <description><![CDATA[<h3 id="what-is-protocol---delegate-pattern">What is Protocol - Delegate Pattern</h3>
<ul>
<li>스스로 이 개념을 정의하자면 &#39;어떤 기능에 대한 Delegate Protocol을 준수한다면 해당 기능과 콜백 함수를 통해 커뮤니케이션이 가능해진다&#39; 라고 생각한다. </li>
</ul>
<h3 id="code-lab">Code Lab</h3>
<pre><code class="language-swift">struct Weather {
    let cityName : String
    let temperatuer : Double

    var temperatureString: String {
        return String(format: &quot;%.0f&quot;, temperatuer)
    }
}</code></pre>
<pre><code class="language-swift">protocol WeatherServiceDelegate: AnyObject {
    func didFetchWeather(_ weatherService: WeatherService, weather: Weather)
}</code></pre>
<ul>
<li>fetch가 끝났을 때 불릴 protocol 정의 </li>
</ul>
<pre><code class="language-swift">struct WeatherService {

    var delegate : WeatherServiceDelegate?

    func fetchWeather(cityName: String) {
        let weather = Weather(cityName: &quot;Hong Kong&quot;, temperatuer: 33)
        delegate?.didFetchWeather(self, weather: weather)
    }
}</code></pre>
<ul>
<li>상기의 func fetchWeather에서 weather 변수의 세팅이 끝난 후 <pre><code class="language-swift">delegate?.didFetchWeather(self, weather: weather)</code></pre>
</li>
<li>상기의 코드를 콜한다. </li>
</ul>
<pre><code class="language-swift">extension WeatherViewController: WeatherServiceDelegate {
    func didFetchWeather(_ weatherService: WeatherService, weather: Weather) {
        // Update UI
        temperatureLabel.attributedText = makeTemperatureText(with: weather.temperatureString)
        cityLabel.text = weather.cityName
    }
}</code></pre>
<ul>
<li>그러면 상기의 코드와 같이 WeatherServiceDelegate를 준수하고 있는 곳에서 콜백 함수가 동작하며</li>
<li>func fetchWeather의 종료 시점에 맞춰 정확히 UI를 업데이트 시켜줄 수 있다. </li>
</ul>
<h3 id="when-to-use">When To Use</h3>
<ul>
<li>Passing information back from child to parent ViewController </li>
<li>Want source of information but far from where it is set </li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<ul>
<li>상기의 코드랩은 가장 간단히 Protocol - Delegate Pattern을 구현한 예시이다. </li>
<li>현재의 프로젝트는 대부분 Combine으로 진행되어 있는데 장단이 존재하는 것 같다. </li>
<li>Protocol - Delegate Pattern를 사용한다면 기능의 문서화나 모듈화가 쉬워질 것 같다. </li>
<li>Combine Future를 사용하는 것과 Protocol - Delegate Pattern으로 커뮤니케이션 하는 것 둘 중 어떤 것이 어느 상황에서 좋은지 연구가 필요할 것 같다. </li>
<li>직접 만들지는 않았지만 MVVM 패턴에는 쓰기 어렵지 않을까 예상해본다. </li>
<li>공식 문서를 훨씬 더 잘 이해할 수 있게 되었다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[勉強の計画：Programmatic UI ]]></title>
            <link>https://velog.io/@kuruma-42/%E5%8B%89%E5%BC%B7%E3%81%AE%E8%A8%88%E7%94%BB%EF%BC%9AProgrammatic-UI</link>
            <guid>https://velog.io/@kuruma-42/%E5%8B%89%E5%BC%B7%E3%81%AE%E8%A8%88%E7%94%BB%EF%BC%9AProgrammatic-UI</guid>
            <pubDate>Wed, 15 Dec 2021 10:54:59 GMT</pubDate>
            <description><![CDATA[<p>今、会社では SWIFTUIで働いてますが自分の勉強のために
ストリボード無しでミニプロゼクトしたいと思う。</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Pattern We Should Use When We Make UI Programmatically ]]></title>
            <link>https://velog.io/@kuruma-42/Pattern-We-Should-Use-When-We-Make-UI-Programmatically</link>
            <guid>https://velog.io/@kuruma-42/Pattern-We-Should-Use-When-We-Make-UI-Programmatically</guid>
            <pubDate>Wed, 15 Dec 2021 05:37:47 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-swift">import UIKit

extension UIColor {
    static var mainPink = UIColor(red: 232/255, green: 68/255, blue: 133/255, alpha: 1)
}

class ViewController: UIViewController {

    let bitCoinImageView: UIImageView = {
        let imageName = &quot;Bitcoin_Logo&quot;
        let image = UIImage(named: imageName)
        let imageView = UIImageView(image: image)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()

    let descriptionTextView : UITextView = {
        let textView = UITextView()
        let attributedText = NSMutableAttributedString(string: &quot;Bet Your Money And Wait it&quot;, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 18)])
        attributedText.append(NSAttributedString(string: &quot;\n\n\nAre You Ready for loads and loads of func? Don&#39;t wait any longer! We hope to see in our coin store sooon&quot;, attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 13),
                                                                                                                                                                                        NSAttributedString.Key.foregroundColor: UIColor.gray]))
        textView.attributedText = attributedText
        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.textAlignment = .center
        textView.isEditable = false
        textView.isScrollEnabled = false
        return textView
    }()

    private let previousButton : UIButton = {
        let button = UIButton(type: .system)
        button.setTitle(&quot;Prev&quot;, for: .normal)
        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
        button.setTitleColor(.gray, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private let nextButton : UIButton = {
        let button = UIButton(type: .system)
        button.setTitle(&quot;Next&quot;, for: .normal)
        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
        button.setTitleColor(.gray, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitleColor(.mainPink, for: .normal)
        return button
    }()

    private let pageControl: UIPageControl = {
        let pc = UIPageControl()
        pc.currentPage = 0
        pc.numberOfPages = 4
        pc.currentPageIndicatorTintColor = .mainPink
        pc.pageIndicatorTintColor = UIColor(red: 249/255, green: 207/255, blue: 224/255, alpha: 1)

        return pc
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(bitCoinImageView)
        view.addSubview(descriptionTextView)

        setupLayout()
        setupBottomControls()

    }

    fileprivate func setupBottomControls() {

        let bottomControlStackView = UIStackView(arrangedSubviews: [previousButton, pageControl ,nextButton])
        bottomControlStackView.translatesAutoresizingMaskIntoConstraints = false
        bottomControlStackView.distribution = .fillEqually
        view.addSubview(bottomControlStackView)

        NSLayoutConstraint.activate([
            bottomControlStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            bottomControlStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            bottomControlStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            bottomControlStackView.heightAnchor.constraint(equalToConstant: 50)
        ])
    }



    private func setupLayout() {
        let topImageContainerView = UIView()
        topImageContainerView.backgroundColor = .white
        view.addSubview(topImageContainerView)
        topImageContainerView.translatesAutoresizingMaskIntoConstraints = false
        topImageContainerView.addSubview(bitCoinImageView)
        topImageContainerView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)

        [
            // BitConinImage
            bitCoinImageView.centerXAnchor.constraint(equalTo: topImageContainerView.centerXAnchor),
            bitCoinImageView.centerYAnchor.constraint(equalTo: topImageContainerView.centerYAnchor),
            bitCoinImageView.heightAnchor.constraint(equalTo: topImageContainerView.heightAnchor, multiplier: 0.5),

            // DescriptionTextView
            descriptionTextView.topAnchor.constraint(equalTo: bitCoinImageView.bottomAnchor, constant: 150),
            descriptionTextView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 24),
            descriptionTextView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -24),
            descriptionTextView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),

            // TopImageContainer
            topImageContainerView.topAnchor.constraint(equalTo: view.topAnchor),
            topImageContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            topImageContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),

            topImageContainerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
        ].forEach{ $0.isActive = true }

    }
}</code></pre>
<h3 id="conclusion">Conclusion</h3>
<ul>
<li>ViewDidLoad 부분을 더럽히지 않는다 </li>
<li>상단부분에 선언한 UI들 처럼 후행 클로저에 디테일한 세팅을 한 뒤 리턴해준다. </li>
<li>ViewDidLoad 아랫 부분에 Constraint관련 펑션을 생성해주고 그 안에서 관리한다. </li>
<li>SwiftUI로 프로젝트를 끝내고 나서 다시 공부를 하니 이해가 가는 부분이 많았다. </li>
<li>개인적으로는 스토리보드로 만드는 것 보다 취향에는 더 맞았다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[What is UIViewController In Swift?]]></title>
            <link>https://velog.io/@kuruma-42/What-is-UIViewController-In-Swift</link>
            <guid>https://velog.io/@kuruma-42/What-is-UIViewController-In-Swift</guid>
            <pubDate>Wed, 15 Dec 2021 05:11:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/kuruma-42/post/b71e6957-352e-4c39-9317-3c7edd3ed1fb/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-14%20%EC%98%A4%ED%9B%84%204.40.56.png" alt=""></p>
<h3 id="definition">Definition</h3>
<ul>
<li>UIKit app의 뷰 계층을 관리하는 객체 </li>
</ul>
<h3 id="declaration">Declaration</h3>
<ul>
<li>@MainActor class UIViewController : UIResponder</li>
</ul>
<h3 id="overview">OverView</h3>
<p>UIViewController 클래스는 일반적인 뷰컨트롤러에서 공유되는 동작들을 정의해놓았다.
개발자들은 아마 거의 UIViewController 클래스를 직접적으로 생성할 일은 거의 없고, 대신에 UIControllerView의 SubClass를 쓰거나, 메소드를 추가하거나, 뷰컨트롤러의 계층을 관리하는데 필요한 프로퍼티등을 추가할 것이다. 
<br>
뷰컨트롤러의 메인 역할은 하기와 같다 : </p>
<ul>
<li>뷰의 내용을 업데이트 하는 것, 대부분 내부적인 데이터의 변화에 대응하는 방식이다. </li>
<li>유저와 뷰의 상호작용에 대한 대응.</li>
<li>뷰를 리사이징 하거나, 전박적인 인터페이스의 레이아웃(배치라고 이해하면 편함)을 관리한다. </li>
<li>다른 객체와(*다른 뷰의 컨트롤러와) Coordinating(조정,조직) 한다. </li>
</ul>
<br>
뷰컨트롤러는 자신이 관리하거나 참여하고 있는 뷰 계층과 밀접하게 엮여있다. 
구체적으로 뷰 컨트롤러는 UIResponder 객체와 Responser Chain과 ViewController의 root view 그리고 일반적으로 다른뷰에 속해있는 슈퍼뷰들 사이에 삽입되어있다.
만약 뷰컨트롤러의 뷰중 아무것도 이벤트를 처리하지 않는다면 뷰 컨트롤러는 이벤트를 superview 쪽으로 전달하는 방법이 있다. 

<p>뷰 컨트롤러는 거의 혼자 쓰이지 않으며, 대부분 유저 인터페이스의 한 부분을 차지하고있는 여러개의 뷰 컨트롤러를 쓸 것 이다. 예를 들면, 하나의 뷰 컨트롤러는 테이블을 보여주고 있을 수 있고, 다른 뷰 컨트롤러는 그 테이블에서 선택된 아이템을 보여주고 있을 수 있다. </p>
<p>일반적으로 한 번에 하나의 뷰 컨트롤러의 뷰만 볼 수 있다. 뷰 컨트롤러는 새로운 뷰 집합을 표시하기 위해 다른 뷰 컨트롤러를 제공할 수도 있고, 원하는 대로 다른 뷰 컨트롤러의 컨텐츠 및 애니메이션 뷰를 위한 컨테이너 역할을 할 수도 있다.</p>
<h3 id="subclassing-notes">Subclassing Notes</h3>
<p>모든 앱은 최소한 한개의 UIViewController의 subclass를 가지고 있다. 대부분 앱들은 많은 커스텀 뷰 컨트롤러를 포함하고있다. 커스텀 뷰 컨트롤러는 전반적인 앱의 동작에 대해서 정의되어있고, 앱의 외형이나 외형에 대한 상호작용을 포함하고있다. 다음 섹션은 custom subclass의 동작 수행에 대해서 간단하게 살펴볼까 한다. </p>
<h3 id="view-management">View ManageMent</h3>
<p>각각의 뷰컨트롤러는 뷰 계층과, 뷰가 속해있는 클래스의 특성을 저장하고있는 루트 뷰를 관리한다. 
루트 뷰는 우선적으로 다른 뷰 계층의 콘테이너 역할을 수행한다. 루트뷰의 위치와 크기는 스스로 소유하고 있는 객체 예를 들면, 부모 뷰나 윈도우 앱에 의해 결정된다.</p>
<p>뷰 컨트롤러는 스스로의 뷰를 Lazy하게 불러온다. 처음 뷰를 로드할 경우 또는 뷰 컨트롤러의 뷰를 만들 때 뷰 속성들에 접근한다.  컨트롤러가 뷰를 구체화 하는 몇 가지 방법에 대해서 말해보자. </p>
<ul>
<li>뷰 컨트롤러와 우리 앱의 스토리 보드를 구체화 하자, 스토리보드는 뷰를 구체화 할 때 선호되는 방식이다. 
스토리보드와 함께 뷰와 뷰컨트롤러와 뷰의 연결들을 구체화 하자. 또한 뷰 컨트롤러와의 관계를 seagues를 사용하여 구체화 할 수 있다. seagues는 앱의 동작을 조작하는 것을 훨씬 쉽게 만들어 줄 것이다. <br></li>
<li>Nib file을 사용해서 뷰를 구체화해도 된다. Nib file은 하나의 뷰 컨트롤러에 여러개의 뷰들을 구체화 하는 가능하게 해준다. 그렇지만 뷰 컨트롤러와 seagues로 연결되거나 관계가 맺어질 순 없다. Nib file은 뷰 컨트롤러의 아주 최소한의 정보만을 저장한다. <br></li>
<li>loadView() 메소드를 사용해서 뷰를 구체화 할 수 있다. 이 메소드는 뷰의 계층을 프로그램적으로 그리고 컨트롤러의 뷰 속성 계층을 루트 뷰에 할당해준다. <br>

</li>
</ul>
<p>상기의 방법들은 마지막엔 결국 같은 결과를 얻게 되는데, 적절한 뷰들의 집합을 만들고, 뷰 속성을 통해 그들을 노출시킨다는 결과를 맞는다. </p>
<h4 id="important">important</h4>
<ul>
<li>뷰 컨트롤러는 자신이 만든 뷰와 모든 그들이 가지고 있는 서브뷰들의 단 하나의 소유자이다. 뷰 컨트롤러는 이런 뷰들을 만들고 그리고 뷰 컨트롤러 스스로가 뷰를 릴리즈 했을 경우와 같은 적절한 시기에 그들에 대한 소유권을 포기해야하는 역할이 있다. 만약 스토리보드 또는 닙 파일을 뷰 객체를 저장하기 위해서 사용했다면, 뷰 컨트롤러가 요청할 때 각 뷰 컨트롤러 객체는 스토리보드나 Nib로 생성한 뷰들의 복사본을 갖게된다. 그러나 만약 뷰를 수동적으로 생성했다면 (Make UI programmatically) 각 뷰 컨트롤러는 반드시 스스로의 유니크한 뷰의 집합을 가지고 있어야한다. 뷰는 뷰 컨트롤러간 공유가 불가능하다. </li>
</ul>
<p>뷰 컨트롤러의 루트뷰는 항상 할당된 사이즈에 맞게 생성된다. 앱의 뷰 계층에 있는 다른 뷰들은 오토레이아웃에 어디에 위치할지 크기는 어떻게 될지 등의 제약을 주기위해 인터페이스 빌더를 사용한다. 또한 프로그램적으로 이러한 제약을 설정할 수 있고, 이러한 제약들을 적합한 시기에 우리의 뷰에 추가할 수 있다. </p>
<h3 id="handling-view-related-notification">Handling View-Related Notification</h3>
<p>시각적으로 뷰가 변할 때, 서브 클래스들이 변화에 반응할 수 있도록 뷰 컨트롤러는 자동적으로 자신이 가지고 있는 메소드들을 실행한다. 
viewWillAppear(<em>:) 새로운 뷰가 스크린에 나올 때를 대비하기 위해 사용하라
viewWillDisAppear(</em>:) 뷰가 없어질 때 변화를 저장하거나 상태 정보를 저장하기 위해서 사용하라.</p>
<p><img src="https://images.velog.io/images/kuruma-42/post/702a6136-ea60-43c6-b802-3c577ece229f/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-15%20%EC%98%A4%EC%A0%84%2011.59.37.png" alt="">
상기의 이미지는 실현 가능한 뷰 컨트롤러의 뷰와 상태 변화를 이미지화한 그림이다. will 콜백이 did 콜백과 쌍을 이루는 것은 아니다. 꼭 알아야 하는 것은 만약 프로세스를 will로 시작했다면, 프로세스를 종료할 때는 will과 did 둘 다 사용해도 된다. </p>
<h3 id="implementing">Implementing</h3>
<p>커스텀 UIViewController 서브클래스는 뷰 컨트롤러의 컨테이너의 역할을 할 수 있다. 컨테이너 뷰 컨트롤러는 다른 뷰 컨트롤러가 소유하고 있는 내용들을 보여주는 것을 관리할 수 있다. 차일드 뷰는 있는 그대로 보여질 수 있고 뷰가 소유하고 있는 다른 뷰들과 함께 보여질 수 있습니다. 자연스럽게 이 메소드들은 의미론적으로 내가 만들기에 달려있습니다. 몇개의 하위 뷰들을 뷰 컨트롤러의 계층에 한번에 보여줄 것인지, 칠드런 뷰가 보여질 타이밍, 그리고 뷰컨트롤러 계층의 어디에서 나타날지 결정해야할 필요가 있다. 그리고 뷰 컨트롤러는 칠드런 뷰와 무엇을 어떻게 어떤 관계인지 정의한다. 클린한 퍼블릭 인터페이스를 만드는 것은 프라이빗한 많은 자잘한 디테일에 접근하지 않고도, 칠드런 쪽이 로직적으로 기능을 사용하는 것을 보장한다. </p>
<p>컨테이너 뷰 컨트롤러는 칠드런의 루트뷰를 뷰 계층에 포함시키기 전에 반드시 관계가 되어있어야 한다. 
이런 것들은 iOS가 차일드 뷰에게 적절한 이벤트를 라우팅하거나 관리하는 것을 가능하게 한다. 마찬가지로 뷰 계층에서 차일드의 루트뷰를 제거하고 난 뒤에, 스스로를 차일드 뷰 컨트롤러와의 관계를 끊어놔야 한다. 
이러한 관계를 끊기 위해서는, 컨테이너는 베이스 클래스에서 정의되어진 특정 메소들을 불러야한다. 
이런 메소드들은 클라이언트를 통해서 부르도록 만들어지지 않았으며, 이것은 컨테이너가 해야하는 동작을 구현할 때만 포함시키도록 한다. </p>
<ul>
<li>addChild(_:)</li>
<li>removeFromParent()</li>
<li>willMove(toParent:)</li>
<li>didMove(toParent:)</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<ul>
<li>프로젝트를 진행하다보면 너무 당연하게 뷰컨트롤러를 쓰는데 </li>
<li>뷰 컨트롤러의 정의와 동작을 알고 지나가고 싶었다. </li>
<li>UIKit app의 뷰 계층을 관리하는 객체 </li>
<li>뷰 컨트롤러는 자신이 만든 뷰와 모든 그들이 가지고 있는 서브뷰들의 단 하나의 소유자이다.</li>
<li>기능과 배치, 유저와의 상호작용들을 컨트롤한다. </li>
<li>영어 공부를 할 겸 공식 문서를 번역해봤다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[What is UIWindow in Swift?]]></title>
            <link>https://velog.io/@kuruma-42/What-is-UIWindow-in-Swift</link>
            <guid>https://velog.io/@kuruma-42/What-is-UIWindow-in-Swift</guid>
            <pubDate>Tue, 14 Dec 2021 07:24:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/kuruma-42/post/fd344f9d-b44d-4e62-9fb8-19d197eb1dd9/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-14%20%EC%98%A4%ED%9B%84%2012.01.45.png" alt=""></p>
<h3 id="definition">Definition</h3>
<ul>
<li>앱의 유저 인터페이스와 이벤트를 뷰에 디스패치하는 개체들의 화면이다. <br>

</li>
</ul>
<h3 id="overview">OverView</h3>
<p>윈도우는 많은 이벤트와 다른 많은 앱의 근간이 되는 많은 업무들을 수행하기 위해서 뷰 컨트롤러와 함께 동작한다. 
UIKit는 대부분의 윈도우와 관련된 상호작용을 다루고, 다른 많은 앱 동작수행이 필요한 객체들과 동작한다.
우리가 윈도우를 사용하는 때는 다음의 상황들이다. </p>
<ul>
<li>앱의 컨텐츠를 보여주기 위한 메인 윈도우 제공 </li>
<li>추가적인 컨텐츠를 위한 추가적인 윈도우 생성 </li>
</ul>
<p>보통 Xcode는 우리 앱에 메인 윈도우를 제공한다. 새로운 iOS프로젝트는 스토리보드를 앱의 뷰를 정의하기 위해 사용한다. 스토리보드는 App Delegate 객체에 Xcode 템플릿을 자동으로 제공해주는 윈도우 프로퍼티의 존재를 요구한다. 만약 우리의 앱이 스토리보드를 사용하지 않는다면( 프로그램 적으로, 선언적으로 UI를 만들경우) 윈도우를 스스로 만들어줘야한다. </p>
<p>대부분의 하나의 메인 스크린에서 앱 컨텐츠를 보여주는 앱은 대부분 하나의 윈도우만을 필요로한다. 
메인 스크린에 추가적인 윈도우를 만들 수 있음에도 불구하고, 추가적인 윈도우는 대게 외부적인 스크린을 보여줄(ex : 결된 스크린에 컨텐츠를 보여주거나.) 때 쓰인다. </p>
<p>또한, UIWindow 객체를 편리하게 다른 기능과 함께 사용할 수 있다 : </p>
<ul>
<li>윈도우와 연결되어 시각적으로 영향을 미칠 수 있는 z축 계통의 윈도우를 세팅하거나 </li>
<li>윈도우를 보여주고 키보드 이벤트를 넣을 때나 </li>
<li>윈도우의 Coordinate 시스템으로 부터의 Coordinate 밸류를 바꾸고싶을 때 </li>
<li>위도우의 RootViewController를 바꾸고 싶을 때 </li>
<li>윈도우에 보여지고 있는 디스플레이를 바꾸고 싶을 때 이다. </li>
</ul>
<p>윈도우는 그들 자신의 시각적 특징을 전부 가지고 있지는 않다 대신, 윈도우는 하나나 하나 이상의 루트뷰컨트롤러에 관리받고 있는 뷰를 호스팅한다. 스토리보드에 RootViewController를 설정해보자, 그리고 어떤 뷰든 우리 앱에 적절한 인터페이스를 추가해보자. </p>
<p>그리고 UIWindow 서브클래스를 거의 안 써야 할 것이다. 개발자들이 윈도우에서 수행하려고 하는 기능은 더 높은 레벨의 뷰 컨트롤러에서 수행하게되면 훨씬 쉽게 만들 수 있다. </p>
<h3 id="conclusion">Conclusion</h3>
<ul>
<li>당연하게 쓰는 것도, 알고 있고 없고는 큰 차이가 있다. </li>
<li>영어 공부하는 셈 치고 번역하면서 읽었다.</li>
<li>윈도우 서브클래스를 많이 건드리지말고 웬만하면 뷰컨트롤러 쪽에서 작업하라는 행간이 보이는 것 같기도 하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[How to Connect HealthKit]]></title>
            <link>https://velog.io/@kuruma-42/How-to-Connect-HealthKit-p974onx2</link>
            <guid>https://velog.io/@kuruma-42/How-to-Connect-HealthKit-p974onx2</guid>
            <pubDate>Mon, 06 Dec 2021 10:37:51 GMT</pubDate>
            <description><![CDATA[<h3 id="apple-healthkit">Apple HealthKit</h3>
<ul>
<li>내가 구현한 것은 아니지만 회사 QA에 걸음수가 핸드폰과 비교했을 때 다르다는 QA가 올라왔다.</li>
<li>QA가 내게 할당되었다. 왜 불길한 예감은 한 번도 틀린 적이 없는지 </li>
<li>걸음 수의 차이가 많이 나는 사람들을 조사해보니 대부분 애플워치의 이용자들이었다. </li>
<li>Apple 공식 문서와 WWDC강의를 참고해서 문제를 해결했다. <br>
<br>

</li>
</ul>
<p><a href="https://developer.apple.com/videos/play/wwdc2020/10664/">WWDC2020 : Getting started with HealthKit</a>
<a href="https://developer.apple.com/videos/play/wwdc2020/10184">WWDC2020 : Synchronize health data with HealthKit</a>
<a href="https://developer.apple.com/videos/play/wwdc2020/10656">2020 : Beyond counting steps</a>
<a href="https://developer.apple.com/documentation/healthkit">HealthKit Officail Document</a>
<a href="https://developer.apple.com/documentation/healthkit/setting_up_healthkit">HealthKit Article : Setting Up HealthKit</a>
<br>
<br></p>
<h4 id="check-your-device-first">Check Your Device first</h4>
<pre><code class="language-swift">override func viewDidLoad() {
        super.viewDidLoad()
     if HKHealthStore.isHealthDataAvailable() {

     }
 }</code></pre>
<ul>
<li>상기의 코드에서 첫번째 if문을 보면 디바이스의 헬스키트 사용 가능 여부를 체크해준다.<br>
<br>


</li>
</ul>
<h4 id="request-authorization-to-user-everytime">Request Authorization To User EveryTime</h4>
<pre><code class="language-swift">override func viewDidLoad() {
        super.viewDidLoad()
        if HKHealthStore.isHealthDataAvailable() {

            let healthStore = HKHealthStore()
            let readDataTypes : Set = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!,
                                       HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.distanceWalkingRunning)!
            ]

            healthStore.requestAuthorization(toShare: nil, read: readDataTypes) { (success, error) in
                if !success {
                    // Handle the error here.
                } else {
                    // Enter your logic here.
                }
            }
        }
</code></pre>
<ul>
<li>기기가 헬스키트가 사용 가능하다면, HKHealthStore의 인스턴스를 생성해주고 </li>
<li>유저에게 요청할 데이터 타입과 타입 안에서 원하는 목록을 선택해준다 ex) stepcount, distanceWalking Running</li>
<li>request Autorization을 시작하면 하단에서 애플의 권한 승인 창이 올라온다. <br>
<br>
#### Do Not Think User Use Your App as You Think</li>
<li>여기서 커다란 착각을 하는 게 유저들이 권한을 열어줄 것을 전제로 앱을 만들면 안 된다.</li>
<li>최근 유저들은 굉장히 방어적이고, 알람이나 권한을 대부분 꺼놓는다.</li>
<li>정말 필요한 앱만 겨우겨우 권한을 열어놓는다. </li>
<li>첫 번째로 우리 앱에서 구현되었던 스탭카운터의 문제 이 권한 쪽에서 발생했다.
걸음수와 운동시간을 HealthKit로 쿼리해오면서 두가지 데이터를 각각 쿼리하고 걸음수가 있으면 당연히 걸은 시간이 존재하기 마련이니 Combine Zip을 이용해 두 가지 데이터를 묶었다. 데이터 정제를 끝내면 걸음수와 걸은 시간을 각각 Send했는데 유저가 걸은 시간이나 거리에 대해서 권한을 승인을 안 하면 Zip이 제 기능을 안 하면서 데이터가 저장되지 않는 현상이 발생했다. </li>
<li>애플의 공식 문서에서는 자료를 요청할 때 마다 권한을 항상 확인하라고 명시해놨다.</li>
<li>유저가 언제든 앱 사용 초기든 중간이든 권한을 열고 닫을 수 있으니 이에 대해 유연하게 로직을 만들라고 했다. <br>
<br>
#### Then, Can I Check What Authorization User Permitted?</li>
<li>대답은 그렇지 않다. </li>
<li>그렇다면 로직의 도입 부분에서 유저의 권한을 체크해서 분기를 나누면 될 줄 알았다.</li>
</ul>
<p><img src="https://images.velog.io/images/kuruma-42/post/9d07aaf0-3853-4a5a-b0c4-75ee02cfeb62/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-03%20%EC%98%A4%EC%A0%84%2011.55.18.png" alt=""></p>
<ul>
<li><a href="https://developer.apple.com/documentation/healthkit/hkhealthstore/1614154-authorizationstatus">HealthKit authorizationStatus(for:)</a></li>
<li>상기의 굉장히 유저의 권한을 체크해줄 것 만 같은 기능을 발견했고 모든 구현을 마쳤다. </li>
<li>상기의 기능을 구현하면 하기의 상태값을 주는 것만 같았다. <br>

</li>
</ul>
<p><img src="https://images.velog.io/images/kuruma-42/post/dd548ad4-fd5e-4c77-b736-9a8242c193f9/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-03%20%EC%98%A4%EC%A0%84%2011.53.47.png" alt=""></p>
<ul>
<li>그치만 계속 notDetermined 값만 로그에 남았다. </li>
<li>애플 놈들이 또 유저를 생각해버린게 틀림없다. <br>
![](https://images.velog.io/images/kuruma-42/post/980c52ca-029f-4d50-93f6-ef5ce23592d2/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-03%20%EC%98%A4%ED%9B%84%2012.15.09.png)</li>
<li>예민한 건강 정보의 유출 가능성 예방을 돕고자, 우리가 만드는 앱은 유저가 권한을 부여했는지 안 했는지 알 수 없다. 만약 허가를 못 받았다면 간단하게 없는대로 보여주지 말고 만들..<br>
![](https://images.velog.io/images/kuruma-42/post/55e341cc-8f30-43b0-80e0-b5d060d25187/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-03%20%EC%98%A4%ED%9B%84%202.01.35.png)</li>
<li>앞으론 discussion을 꼭 읽어야겠다. </li>
<li>그렇다면 날짜를 날짜까지만 컨버팅 한 후 milliSecondSince1970 형태로 바꿔서 primaryKey를 만들고 </li>
<li>Realm 의 insert update 와 데이터 유무 체크로 분기를 나누면 될 듯 하다. <br>

</li>
</ul>
<pre><code class="language-swift">    let sampleType = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)
        let today = Date()
        let startDate = Calendar.current.date(byAdding: .day, value: -7, to: today)
        let predicate = HKQuery.predicateForSamples(withStart: startDate, end: today, options: HKQueryOptions.strictEndDate)
        let query = HKSampleQuery.init(sampleType: sampleType!,
                                       predicate: predicate,
                                       limit: HKObjectQueryNoLimit,
                                       sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)])</code></pre>
<ul>
<li>상기의 부분이 대부분의 코드랩에서 나와있는 HKSampleType을 이용한 쿼리다. 
<a href="https://developer.apple.com/documentation/healthkit/hksampletype">Apple Document : HKSampleType</a></li>
</ul>
<pre><code class="language-swift">   /// Mark: -  한달의 걸음 수와 걸은 시간을 날짜별로 가져오는 함수
    func getOneMonthStepCountAndWalkingTimePerDay() {
        var realmWalking : [Walking] = []
        let sampleType = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)
        let today = Date()
        let startDate = Calendar.current.date(byAdding: .day, value: -7, to: today)
        let predicate = HKQuery.predicateForSamples(withStart: startDate, end: today, options: HKQueryOptions.strictEndDate)
        let query = HKSampleQuery.init(sampleType: sampleType!,
                                       predicate: predicate,
                                       limit: HKObjectQueryNoLimit,
                                       sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)]) { (query, results, error) in

            var dateOneIndexBeforeBuffer : Date? = nil
            var walkingDataBuffer : Walking = Walking()
            var stepCountBuffer : Int = 0
            var walkingSecondBuffer : Double = 0.0

            print(&quot;------------------------------------------------------------------------------------&quot;)
            results?.compactMap{
                $0
            }.forEach{ stepCountInfo in


                // Apple Watch와 중복 계산을 막아준다.
                if !stepCountInfo.description.contains(&quot;Watch&quot;){
                    // Day 구분을 위해 StartDate에서 시간을 지워준다.
                    let startDate = convertStringToDate(dateString: (convertDateToString(date: stepCountInfo.startDate, format: &quot;yyMMdd&quot;)), format: &quot;yyMMdd&quot;)

                    // 하나 전 인덱스와 비교해준다.
                    if dateOneIndexBeforeBuffer != nil {
                        // 시작일이 전 인덱스의 시작일과 다르다면 날짜가 바뀐 것.

                            print(&quot;RESULT :: \(stepCountInfo.description)&quot;)
                        if startDate &lt; dateOneIndexBeforeBuffer! {

                            // 날짜가 바뀌면 해당 날짜와, 해당 일의 걸음 수 걸음 시간 각각의 총 합을 객체에 넣어준다.
                            // 인덱스 (인덱스는 날짜로 선언한다 가변적인 데이터에 대응하기 위해 같은 날짜의 데이터는 Realm에서 Update &amp; Insert를 진행한다.
                            walkingDataBuffer.id = dateOneIndexBeforeBuffer!.millisecondsSince1970
                            print(&quot;*** WALKING DATE PER CELL  :: \(walkingDataBuffer.id)&quot;)

                            // 걸음 수
                            walkingDataBuffer.walkingCount = stepCountBuffer
                            print(&quot;*** TOTAL WALKING COUNT PER CELL  :: \(walkingDataBuffer.walkingCount)&quot;)

                            // 운동 시간
                            walkingDataBuffer.walkingSecond = Int(round(walkingSecondBuffer))
                            print(&quot;*** TOTAL WALKING TIME PER CELL  :: \(walkingDataBuffer.walkingSecond)&quot;)
                            print(&quot;------------------------------------------------------------------------------------&quot;)

                            // 리셋 버퍼 벨류
                            walkingSecondBuffer = 0.0
                            stepCountBuffer = 0

                            // DB에 들어갈 객체에 넣어준다.
                            realmWalking.append(walkingDataBuffer)
                        }

                    }

                    // 걸은 시간
                    // 운동을 마친 시간과 시작 시간의 timeIntervalSinceReferenceDate 값을 빼주면 운동을 한 시간이 계산된다.
                    let walkingSecond = stepCountInfo.endDate.timeIntervalSince1970 - stepCountInfo.startDate.timeIntervalSince1970

                    // 걸은 시간을 더해준다.
                    walkingSecondBuffer += walkingSecond

                    // 걸음 수
                    let stepCount = Int(stepCountInfo.description.components(separatedBy: &quot; count&quot;)[0])

                    // 걸음 수를 더해준다.
                    stepCountBuffer += stepCount ?? 0

                    // 다음 인덱스에서 확인할 수 있게 Date를 dateOneIndexBeforeBuffer 에 저장해준다.
                    dateOneIndexBeforeBuffer = startDate
                }

            }

        }
        healthStore.execute(query)
    }</code></pre>
<p><img src="https://images.velog.io/images/kuruma-42/post/4efe8a17-4dfa-4169-8848-d5006dcf2cc3/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-06%20%EC%98%A4%EC%A0%84%2010.38.18.png" alt=""></p>
<ul>
<li>로그를 확인해 보면 iPhone12, iPhone13 등등 내 아이디가 등록되어있는 모든 기기들에서 </li>
<li>걸음 수, 걷기 시작한 시간, 걷기가 끝난 시간 등이 나온다. </li>
<li>하루에 한 데이터가 나오는 게 아니라 감지된 모든 데이터가 나온다 </li>
<li>상기의 로그를 찍은 디바이스는 회사에서 쓰는 테스트폰이어서 로그가 많이 안 찍히고 데이터의 갯수가 적지만</li>
<li>일반 사용자의 핸드폰으로 로그를 찍으면 앱이 꺼질 정도로 많은 양의 데이터가 들어온다.</li>
<li>여기서 우리 앱은 Device 에 관계 없이 모든 데이터를 더하기 때문에 거리 차이가 났다.</li>
<li>애플 워치를 차고있는 사람과는 거의 2배 가량의 걸음수 차이를 보여줬다.</li>
<li>이유는 애플디바이스는 디바이스간 싱크를 내부에서 맞춰서 건강 앱에서 보여주지만 </li>
<li>우리는 싱크를 맞추지 않았기 때문이다. </li>
</ul>
<br>

<pre><code class="language-swift"> if !stepCountInfo.description.contains(&quot;Watch&quot;)</code></pre>
<ul>
<li>상기의 코드를 사용하여  Apple Watch 디바이스의 데이터를 제외하고 합을 구했지만 </li>
<li>100 - 200 걸음의 오차는 계속 생겼다. </li>
<li>95%의 코드랩 자료들은 상기와 같은 연산을 사용했다.</li>
<li>그렇지만 이렇게 하면 정확한 자료를 얻기는 어렵다. </li>
</ul>
<br>

<pre><code class="language-swift">   ///  설정한 기간동안의 걸음 수를 조회할 수 있는 쿼리.
    /// - Parameter completion: cumulative parameter sum
    func getTodaysSteps(completion: @escaping (Double) -&gt; Void) {
        let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!

        let now = Date()
        let startDate = Calendar.current.date(byAdding: .day, value: -6, to: Date())!
        let predicate = HKQuery.predicateForSamples(
            withStart: startDate,
            end: now,
            options: .strictStartDate
        )

        let query = HKStatisticsQuery(
            quantityType: stepsQuantityType,
            quantitySamplePredicate: predicate,
            options: .separateBySource
        ) { _, result, _ in
            guard let result = result, let sum = result.sumQuantity() else {
                print(&quot;Step Zero&quot;)
                completion(0.0)
                return
            }
            print(&quot;result check \(result)&quot;)
            completion(sum.doubleValue(for: HKUnit.count()))
        }
        healthStore.execute(query)
    }</code></pre>
<ul>
<li>상기의 데이터는 HKStatisticsQuery를 사용한 데이터인데 </li>
<li>정해진 기간의 데이터를 더하거나, 축적연산을 하는 것 위주로 가능했다.</li>
<li>내가 원하는 것은 통계 데이터를 콜렉션 형태로 보는 것 이다. </li>
<li>데이터는 정확하나 정제가 안 되므로 HKStatisticsQuery는 사용하지 않기로 했다. </li>
<li><a href="https://developer.apple.com/documentation/healthkit/hkstatisticsquery">Apple Doc : HKStatisticsQuery</a></li>
</ul>
<pre><code class="language-swift">// 날짜별 스탭카운트 얻기
    func getStepCountPerDay(finishCompletion: @escaping () -&gt; Void){

        guard let sampleType = HKObjectType.quantityType(forIdentifier: .stepCount)
            else {
                return
        }
        let calendar = Calendar.current
        var dateComponents = DateComponents()
        dateComponents.day = 1

        var anchorComponents = calendar.dateComponents([.day, .month, .year], from: Date())
        anchorComponents.hour = 0
        let anchorDate = calendar.date(from: anchorComponents)

        let stepsCumulativeQuery = HKStatisticsCollectionQuery(quantityType: sampleType, quantitySamplePredicate: nil, options: .cumulativeSum, anchorDate: anchorDate!, intervalComponents: dateComponents
        )

        // Set the results handler
        stepsCumulativeQuery.initialResultsHandler = {query, results, error in
            let endDate = Date()
            let startDate = calendar.date(byAdding: .day, value: -30, to: endDate, wrappingComponents: false)
            if let myResults = results{
                myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { [self] statistics, stop in
                    if let quantity = statistics.sumQuantity(){
                        let date = statistics.startDate
                        let steps = quantity.doubleValue(for: HKUnit.count())
                        print(&quot;START DATE :: \(statistics.startDate)&quot;)
                        print(&quot;STEP COUNT :: \(steps)&quot;)
                        print(&quot;-------------------------------------------------------------&quot;)
                    }
                }
            } else {
                print(&quot;STEP COUNT DATA NIL&quot;)
            }
        }
        HKHealthStore().execute(stepsCumulativeQuery)
        finishCompletion()
    }

</code></pre>
<p><img src="https://images.velog.io/images/kuruma-42/post/759abbfc-d389-4f3a-8fae-ec8a799211c4/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-06%20%EC%98%A4%ED%9B%84%205.50.43.png" alt=""></p>
<ul>
<li>연속적인 고정된 인터벌의 기간 으로 여러개의 통계적인 데이터를 콜렉션 형태로 가져오는 쿼리타입</li>
<li>각 일자에 있는 걸음수의 통계적 합을 나타내기 위해 <pre><code class="language-swift">if let quantity = statistics.sumQuantity()</code></pre>
</li>
<li>상기의 코드를 작성해서 각 일자의 통계적 합을 구합니다. </li>
</ul>
<p><img src="https://images.velog.io/images/kuruma-42/post/3a40b2d1-9ae4-47e3-9413-ba95e3844885/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-06%20%EC%98%A4%ED%9B%84%205.01.39.png" alt=""></p>
<ul>
<li>로그를 찍어보면 고정된 시작 일자와 </li>
<li>하루 걸은 걸음수의 총합을 보여줍니다. </li>
<li>이 데이터를 확인해 보면 정확히 건강 앱의 데이터와 일치하는 것을 확인 할 수 있습니다. </li>
</ul>
<h3 id="tip">Tip</h3>
<ul>
<li>공식문서를 찾아보면서 이것 저것 클릭하다보니 엄청 중요한 내용을 봤다. 
<a href="https://developer.apple.com/design/human-interface-guidelines/healthkit/overview/">AHIG: AppleHealthKit</a></li>
<li>대략적으로 어떻게 프로그램 해야하는지 미리 잘 알려주고 </li>
<li>해도 되는 것 안 해야하는 것을 미리 보여주기 때문에 반드시 미리 읽고 구현하기 바란다. </li>
<li>아이콘에 대해서도 어떻게 사용해야 하는지 알려주는데 이게 생각보다 중요하다. 
<img src="https://images.velog.io/images/kuruma-42/post/f8bec9c6-b0fa-485f-8b62-bc57c1b33d21/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-06%20%EC%98%A4%ED%9B%84%207.33.41.png" alt=""></li>
<li>상기의 사진 처럼 애플에서 제공해주는 아이콘만을 써야하며, 그림자나 보더등을 주면 심사에서 떨어질 수 있다. </li>
<li>그리고 클라이언트 쪽에 HealthKit이라는 단어는 쓰면 안 되며, HealthKit은 어디까지나 개발자를 위해서 만든 용어이므로, 아이콘 사용과 마찬가지로 지키지 않을 시에는 심사에서 떨어질 수 있다. </li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<ul>
<li>블로그 자료들에는 한계가 분명했다.</li>
<li>결국 공식 문서를 참고해야 제시하는 원칙을 지켜가면서 개발할 수 있다.</li>
<li>코드랩을 참고하더라도, 반드시 공식문서가 숙지되어 있는 상태에서 진행하는 게 좋다. </li>
<li>개발하기 전에 Native앱과 연동할 때에는 AHIG 미리 확인하면 시간을 많이 줄일 수 있다.</li>
<li>유저를 많이 생각하기 때문에 개발자 입장에서는 조금 힘들 수 있다. </li>
<li>그래도 문서를 읽어가는 힘을 기를 수 있어서 너무 좋은 경험이었다. </li>
</ul>
]]></description>
        </item>
    </channel>
</rss>