<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>VesselWheel</title>
    <link>https://vesselwheel.tistory.com/</link>
    <description>Swift 개발자 시작(2023~ )</description>
    <language>ko</language>
    <pubDate>Thu, 21 May 2026 06:46:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>JasonYang</managingEditor>
    <image>
      <title>VesselWheel</title>
      <url>https://tistory1.daumcdn.net/tistory/6254708/attach/54d4dd201b28449d92e3036d41663c6b</url>
      <link>https://vesselwheel.tistory.com</link>
    </image>
    <item>
      <title>단위 테스트 코드란?</title>
      <link>https://vesselwheel.tistory.com/236</link>
      <description>&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트코드는 소프트웨어의 기능과 동작이 개발자가 의도한 대로 정상적으로 작동하는지 확인하기 위해 작성하는 코드입니다. 즉, 실제 서비스 코드와 별도로 작성되어, 특정 기능이나 메소드가 기대한 결과를 내는지 자동으로 검증하는 역할을 합니다&lt;a href=&quot;https://www.startupcode.kr/company/blog/archives/17&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://jlee0505.tistory.com/116&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span&gt;&lt;a href=&quot;https://jhzlo.tistory.com/28&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;7&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트코드는 일반적으로 다음과 같은 목적을 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;버그 사전 발견&lt;/b&gt;: 개발 과정에서 예상치 못한 문제를 미리 찾아내어, 서비스의 품질을 높일 수 있습니다&lt;a href=&quot;https://velog.io/@ecvheo1/Test-Test-Code%EB%8A%94-%EC%99%9C-%EC%9E%91%EC%84%B1%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://www.startupcode.kr/company/blog/archives/17&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span&gt;&lt;a href=&quot;https://velog.io/@duwnstj12/Test-%EC%BD%94%EB%93%9C%EC%9D%98-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드 품질 및 신뢰성 향상&lt;/b&gt;: 기능이 변경되거나 확장될 때 기존 동작이 잘 유지되는지 빠르게 검증할 수 있습니다&lt;a href=&quot;https://geumba.tistory.com/72&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;4&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://jlee0505.tistory.com/116&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span&gt;&lt;a href=&quot;https://velog.io/@duwnstj12/Test-%EC%BD%94%EB%93%9C%EC%9D%98-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리팩토링의 안전망&lt;/b&gt;: 코드 구조를 개선할 때, 기존 기능이 깨지지 않았는지 테스트코드를 통해 확인할 수 있습니다&lt;a href=&quot;https://geumba.tistory.com/72&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;4&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span&gt;&lt;a href=&quot;https://velog.io/@duwnstj12/Test-%EC%BD%94%EB%93%9C%EC%9D%98-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문서화 역할&lt;/b&gt;: 테스트코드는 코드의 사용법과 예상 동작을 명확하게 보여주어, 새로운 개발자나 협업자에게도 도움이 됩니다&lt;a href=&quot;https://www.startupcode.kr/company/blog/archives/17&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span&gt;&lt;a href=&quot;https://geumba.tistory.com/72&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;4&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수 용이&lt;/b&gt;: 프로젝트가 커질수록 유지보수가 어려워지지만, 테스트코드가 있으면 변경 후에도 정상 동작을 자동으로 확인할 수 있습니다&lt;a href=&quot;https://velog.io/@duwnstj12/Test-%EC%BD%94%EB%93%9C%EC%9D%98-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span&gt;&lt;a href=&quot;https://jhzlo.tistory.com/28&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;7&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트코드는 범위와 목적에 따라 단위 테스트(개별 함수/메소드 검증), 통합 테스트(여러 모듈의 상호작용 검증), UI 테스트(사용자 인터페이스 동작 검증) 등으로 나뉩니다&lt;a href=&quot;https://velog.io/@ecvheo1/Test-Test-Code%EB%8A%94-%EC%99%9C-%EC%9E%91%EC%84%B1%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://www.startupcode.kr/company/blog/archives/17&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span&gt;&lt;a href=&quot;https://jlee0505.tistory.com/116&quot; data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, 테스트코드는 소프트웨어 개발에서 코드의 안정성과 품질을 보장하고, 개발 및 유지보수 과정을 효율적으로 만들어주는 필수적인 도구입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Citations:&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@ecvheo1/Test-Test-Code%EB%8A%94-%EC%99%9C-%EC%9E%91%EC%84%B1%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80&quot;&gt;https://velog.io/@ecvheo1/Test-Test-Code%EB%8A%94-%EC%99%9C-%EC%9E%91%EC%84%B1%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.inflab.com/20230404-test-code/&quot;&gt;https://tech.inflab.com/20230404-test-code/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.startupcode.kr/company/blog/archives/17&quot;&gt;https://www.startupcode.kr/company/blog/archives/17&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://geumba.tistory.com/72&quot;&gt;https://geumba.tistory.com/72&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jlee0505.tistory.com/116&quot;&gt;https://jlee0505.tistory.com/116&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@duwnstj12/Test-%EC%BD%94%EB%93%9C%EC%9D%98-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90&quot;&gt;https://velog.io/@duwnstj12/Test-%EC%BD%94%EB%93%9C%EC%9D%98-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jhzlo.tistory.com/28&quot;&gt;https://jhzlo.tistory.com/28&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://techblog.woowahan.com/17404/&quot;&gt;https://techblog.woowahan.com/17404/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/given-test-code/&quot;&gt;https://tech.kakaopay.com/post/given-test-code/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kakaoentertainment-tech.tistory.com/78&quot;&gt;https://kakaoentertainment-tech.tistory.com/78&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Xcode Study</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/236</guid>
      <comments>https://vesselwheel.tistory.com/236#entry236comment</comments>
      <pubDate>Thu, 24 Apr 2025 15:39:54 +0900</pubDate>
    </item>
    <item>
      <title>PM, 7가지 핵심 코드</title>
      <link>https://vesselwheel.tistory.com/235</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;7가지&amp;nbsp;핵심&amp;nbsp;코드&lt;br /&gt;1. 제품&amp;nbsp;설계(Product&amp;nbsp;Design)&lt;br /&gt;&amp;bull; 시장&amp;nbsp;적합성을&amp;nbsp;확보하기&amp;nbsp;위한&amp;nbsp;문제&amp;nbsp;정의와&amp;nbsp;해결책&amp;nbsp;제시.&lt;br /&gt;&amp;bull; &amp;ldquo;0&amp;nbsp;to&amp;nbsp;1&amp;rdquo;(혁신)과&amp;nbsp;&amp;ldquo;1&amp;nbsp;to&amp;nbsp;N&amp;rdquo;(성장)&amp;nbsp;전략의&amp;nbsp;중요성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;2. 경제학(Economics)&lt;br /&gt;&amp;bull; 비즈니스&amp;nbsp;모델&amp;nbsp;설계와&amp;nbsp;시장&amp;nbsp;진입&amp;nbsp;전략&amp;nbsp;분석.&lt;br /&gt;&amp;bull; 단위&amp;nbsp;경제학을&amp;nbsp;활용해&amp;nbsp;제품의&amp;nbsp;경제적&amp;nbsp;지속&amp;nbsp;가능성&amp;nbsp;확보.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;3. 심리학(Psychology)&lt;br /&gt;&amp;bull; 사용자&amp;nbsp;행동&amp;nbsp;이해&amp;nbsp;및&amp;nbsp;동기부여&amp;nbsp;전략&amp;nbsp;개발.&lt;br /&gt;&amp;bull; 틱톡&amp;nbsp;사례처럼&amp;nbsp;심리학적&amp;nbsp;요소를&amp;nbsp;활용해&amp;nbsp;고객&amp;nbsp;충성도&amp;nbsp;강화.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;4. 사용자&amp;nbsp;경험(User&amp;nbsp;Experience)&lt;br /&gt;&amp;bull; UX&amp;nbsp;디자인과&amp;nbsp;사용성&amp;nbsp;개선으로&amp;nbsp;고객&amp;nbsp;만족도&amp;nbsp;극대화.&lt;br /&gt;&amp;bull; 사용자의&amp;nbsp;인지&amp;nbsp;단계에&amp;nbsp;맞춘&amp;nbsp;디자인&amp;nbsp;전략&amp;nbsp;조정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;5. 데이터&amp;nbsp;과학(Data&amp;nbsp;Science)&lt;br /&gt;&amp;bull; 데이터&amp;nbsp;기반&amp;nbsp;의사결정을&amp;nbsp;통해&amp;nbsp;제품&amp;nbsp;성능&amp;nbsp;최적화.&lt;br /&gt;&amp;bull; 데이터&amp;nbsp;분석으로&amp;nbsp;문제&amp;nbsp;해결과&amp;nbsp;성장&amp;nbsp;기회&amp;nbsp;탐색.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;6. 법률과&amp;nbsp;정책(Law&amp;nbsp;and&amp;nbsp;Policy)&lt;br /&gt;&amp;bull; 법적&amp;nbsp;규제와&amp;nbsp;정책&amp;nbsp;준수로&amp;nbsp;리스크&amp;nbsp;관리.&lt;br /&gt;&amp;bull; 우버&amp;nbsp;사례처럼&amp;nbsp;법적&amp;nbsp;문제를&amp;nbsp;미리&amp;nbsp;대비해야&amp;nbsp;함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;7. 마케팅과&amp;nbsp;성장(Marketing&amp;nbsp;and&amp;nbsp;Growth)&lt;br /&gt;&amp;bull; 효과적인&amp;nbsp;마케팅&amp;nbsp;캠페인으로&amp;nbsp;시장&amp;nbsp;점유율&amp;nbsp;확대.&lt;br /&gt;&amp;bull; 성장&amp;nbsp;모델을&amp;nbsp;활용한&amp;nbsp;제품&amp;nbsp;확산&amp;nbsp;전략.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;주요&amp;nbsp;내용&amp;nbsp;요약&lt;br /&gt;&amp;bull; 성공적인&amp;nbsp;PM은&amp;nbsp;다양한&amp;nbsp;분야(제품&amp;nbsp;설계,&amp;nbsp;경제학,&amp;nbsp;심리학&amp;nbsp;등)에&amp;nbsp;대한&amp;nbsp;전문성과&amp;nbsp;통합적인&amp;nbsp;사고방식을&amp;nbsp;갖춰야&amp;nbsp;함.&lt;br /&gt;&amp;bull; 초기&amp;nbsp;단계에서는&amp;nbsp;충족되지&amp;nbsp;않은&amp;nbsp;수요를&amp;nbsp;파악하고&amp;nbsp;이를&amp;nbsp;해결하는&amp;nbsp;제품&amp;nbsp;개발이&amp;nbsp;중요.&lt;br /&gt;&amp;bull; 데이터&amp;nbsp;분석과&amp;nbsp;사용자&amp;nbsp;행동&amp;nbsp;이해는&amp;nbsp;제품&amp;nbsp;성능&amp;nbsp;최적화와&amp;nbsp;고객&amp;nbsp;충성도&amp;nbsp;확보의&amp;nbsp;핵심.&lt;br /&gt;&amp;bull; 법률&amp;nbsp;및&amp;nbsp;정책&amp;nbsp;준수는&amp;nbsp;기업&amp;nbsp;운영의&amp;nbsp;필수&amp;nbsp;요소이며,&amp;nbsp;이를&amp;nbsp;무시하면&amp;nbsp;큰&amp;nbsp;리스크&amp;nbsp;초래.&lt;br /&gt;&amp;bull; 마케팅과&amp;nbsp;성장&amp;nbsp;전략은&amp;nbsp;제품의&amp;nbsp;시장&amp;nbsp;성공을&amp;nbsp;좌우하는&amp;nbsp;중요한&amp;nbsp;요소.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;결론&lt;br /&gt;&amp;bull; 『7가지&amp;nbsp;코드』는&amp;nbsp;빅테크&amp;nbsp;및&amp;nbsp;스타트업&amp;nbsp;PM들에게&amp;nbsp;실용적인&amp;nbsp;가이드를&amp;nbsp;제공하는&amp;nbsp;책.&lt;br /&gt;&amp;bull; PM으로서&amp;nbsp;성공하려면&amp;nbsp;다양한&amp;nbsp;역량(기술,&amp;nbsp;데이터,&amp;nbsp;심리학&amp;nbsp;등)을&amp;nbsp;종합적으로&amp;nbsp;활용해야&amp;nbsp;함.&lt;br /&gt;&amp;bull; 이&amp;nbsp;책은&amp;nbsp;IT&amp;nbsp;업계에서&amp;nbsp;성공하기&amp;nbsp;위한&amp;nbsp;필독서로&amp;nbsp;평가받음.&lt;/p&gt;</description>
      <category>Voyage</category>
      <category>PM</category>
      <category>Product manager</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/235</guid>
      <comments>https://vesselwheel.tistory.com/235#entry235comment</comments>
      <pubDate>Sat, 12 Apr 2025 16:06:02 +0900</pubDate>
    </item>
    <item>
      <title>책 &amp;lt;인스파이어드&amp;gt; PM 으로서의 여정 시작</title>
      <link>https://vesselwheel.tistory.com/234</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@jinhoneybee/3&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://brunch.co.kr/@jinhoneybee/3&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724730875944&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;책 '인스파이어드'로 보는 PM/PO의 역할&quot; data-og-description=&quot;PM/PO는 뭐하는 사람이에요? | PM으로 처음 일을 시작하면서는 하루하루 주어진 일을 해내기에 바빴다. 5개월 차에 접어들며 이제 겨우 조금씩 데일리 업무들이 익숙해지기 시작하며 나라는 존재&quot; data-og-host=&quot;brunch.co.kr&quot; data-og-source-url=&quot;https://brunch.co.kr/@jinhoneybee/3&quot; data-og-url=&quot;https://brunch.co.kr/@jinhoneybee/3&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQQzBu/hyWScQJ3fk/erhldnEsOEIekqNKNF4KS0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c4yuhj/hyWSkVyuR3/lHk0tUgZjYN7JBsjQLh4Lk/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@jinhoneybee/3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://brunch.co.kr/@jinhoneybee/3&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQQzBu/hyWScQJ3fk/erhldnEsOEIekqNKNF4KS0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c4yuhj/hyWSkVyuR3/lHk0tUgZjYN7JBsjQLh4Lk/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;책 '인스파이어드'로 보는 PM/PO의 역할&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;PM/PO는 뭐하는 사람이에요? | PM으로 처음 일을 시작하면서는 하루하루 주어진 일을 해내기에 바빴다. 5개월 차에 접어들며 이제 겨우 조금씩 데일리 업무들이 익숙해지기 시작하며 나라는 존재&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;brunch.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;프로덕트/프로젝트 매니저가 한번 읽어볼만한 책&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024. 09. 01. Starting the job of Project Manager&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Voyage</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/234</guid>
      <comments>https://vesselwheel.tistory.com/234#entry234comment</comments>
      <pubDate>Tue, 27 Aug 2024 12:55:11 +0900</pubDate>
    </item>
    <item>
      <title>Socket.IO 통신</title>
      <link>https://vesselwheel.tistory.com/233</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@hayeon/%EC%86%8C%EC%BC%93%ED%86%B5%EC%8B%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@hayeon/%EC%86%8C%EC%BC%93%ED%86%B5%EC%8B%A0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716776558995&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;소켓통신&quot; data-og-description=&quot;1. 소켓통신 네트워크를 통해 서버-클라이언트 양쪽에 링크를 생성하고 그 링크를 통해 데이터를 주고 받는 것 이때 서버에 임의의 포트번호를 설정한 상태에서 클라이언트에서 해당 포트로 접&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@hayeon/%EC%86%8C%EC%BC%93%ED%86%B5%EC%8B%A0&quot; data-og-url=&quot;https://velog.io/@hayeon/소켓통신&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cz3Fkv/hyWdncv1xL/qIKwmdJzoUK65M2VteWKpk/img.png?width=1404&amp;amp;height=252&amp;amp;face=0_0_1404_252,https://scrap.kakaocdn.net/dn/JXAML/hyV9MSy4pu/80xOoSzbMR1GW9WIqhouO0/img.png?width=1404&amp;amp;height=252&amp;amp;face=0_0_1404_252,https://scrap.kakaocdn.net/dn/bOebrV/hyWdkfMgAb/oLkT2WjhZ12Keor7luXBKK/img.jpg?width=460&amp;amp;height=460&amp;amp;face=0_0_460_460&quot;&gt;&lt;a href=&quot;https://velog.io/@hayeon/%EC%86%8C%EC%BC%93%ED%86%B5%EC%8B%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@hayeon/%EC%86%8C%EC%BC%93%ED%86%B5%EC%8B%A0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cz3Fkv/hyWdncv1xL/qIKwmdJzoUK65M2VteWKpk/img.png?width=1404&amp;amp;height=252&amp;amp;face=0_0_1404_252,https://scrap.kakaocdn.net/dn/JXAML/hyV9MSy4pu/80xOoSzbMR1GW9WIqhouO0/img.png?width=1404&amp;amp;height=252&amp;amp;face=0_0_1404_252,https://scrap.kakaocdn.net/dn/bOebrV/hyWdkfMgAb/oLkT2WjhZ12Keor7luXBKK/img.jpg?width=460&amp;amp;height=460&amp;amp;face=0_0_460_460');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;소켓통신&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 소켓통신 네트워크를 통해 서버-클라이언트 양쪽에 링크를 생성하고 그 링크를 통해 데이터를 주고 받는 것 이때 서버에 임의의 포트번호를 설정한 상태에서 클라이언트에서 해당 포트로 접&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Xcode Study</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/233</guid>
      <comments>https://vesselwheel.tistory.com/233#entry233comment</comments>
      <pubDate>Mon, 27 May 2024 11:22:45 +0900</pubDate>
    </item>
    <item>
      <title>[ReFactoring]Weather777(URLSession -&amp;gt; async/await)</title>
      <link>https://vesselwheel.tistory.com/232</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Swift의&lt;span&gt;&amp;nbsp;&lt;/span&gt;async/await&lt;span&gt;&amp;nbsp;&lt;/span&gt;패턴을 사용하여 비동기 처리 부분을 변경하려면 기존의&lt;span&gt;&amp;nbsp;&lt;/span&gt;performRequestForecast&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드와&lt;span&gt;&amp;nbsp;&lt;/span&gt;getForecastWeather&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드를 수정해야 합니다. 다음은&lt;span&gt;&amp;nbsp;&lt;/span&gt;async/await를 사용하여 코드를 수정한 예시&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저,&lt;span&gt;&amp;nbsp;&lt;/span&gt;performRequestForecast&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수를&lt;span&gt;&amp;nbsp;&lt;/span&gt;async/await를 사용하도록 변경&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1714182719617&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func performRequestForecast(with url: URL?) async throws -&amp;gt; WeatherData {
    guard let url = url else {
        throw NetworkError.badUrl
    }

    let (data, _) = try await URLSession.shared.data(from: url)
    do {
        let weatherData = try JSONDecoder().decode(WeatherData.self, from: data)
        return weatherData
    } catch {
        throw NetworkError.decodingError
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;그 다음,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot; data-token-index=&quot;1&quot;&gt;getForecastWeather&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;메서드를&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot; data-token-index=&quot;3&quot;&gt;async/await&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;패턴으로 업데이트합니다. 이 메서드는 이제&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot; data-token-index=&quot;5&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;함수가 되고, 외부에서 호출할 때&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot; data-token-index=&quot;7&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;를 사용해야 합니다. 또한,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot; data-token-index=&quot;9&quot;&gt;completion&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;핸들러 대신&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot; data-token-index=&quot;11&quot;&gt;throws&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;를 사용하여 에러를 처리&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1714182766846&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public func getForecastWeather(latitude: Double, longitude: Double) async throws -&amp;gt; [(cityname: String, time: String, weatherIcon: String, weatherdescription: String, temperature: Double, wind: String, humidity: Int, tempMin: Double, tempMax: Double, feelsLike: Double, rainfall: Double)] {
    LocationManager.shared.setLocation(latitude: latitude, longitude: longitude)
    guard let currentLocation = LocationManager.shared.currentLocation else {
        throw NetworkError.badLocation
    }

    guard let url = URL.urlForForecastForLocation(currentLocation, apiKey: apiKey) else {
        throw NetworkError.badUrl
    }

    let weatherData = try await performRequestForecast(with: url)
    let processor = WeatherDataProcessor()
    let forecastData = processor.process(weatherData: weatherData)

    return forecastData
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제&lt;span&gt;&amp;nbsp;&lt;/span&gt;getForecastWeather&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드는 비동기 함수로 작동하며, 이를 호출할 때는&lt;span&gt;&amp;nbsp;&lt;/span&gt;await&lt;span&gt;&amp;nbsp;&lt;/span&gt;키워드를 사용해야 하며, 호출하는 측에서 에러 핸들링을 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;do-catch&lt;span&gt;&amp;nbsp;&lt;/span&gt;블록을 사용하거나 에러를 전파해야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;변경된 메서드를 사용하는 예시&lt;/p&gt;
&lt;pre id=&quot;code_1714182790466&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func someFunction() async {
    do {
        let forecastData = try await WeatherManager.shared.getForecastWeather(latitude: xx.xxxx, longitude: yy.yyyy)
        // 여기에서 forecastData 사용
    } catch {
        // 에러 처리
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Xcode Study</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/232</guid>
      <comments>https://vesselwheel.tistory.com/232#entry232comment</comments>
      <pubDate>Sat, 27 Apr 2024 10:53:19 +0900</pubDate>
    </item>
    <item>
      <title>[트러블 슈팅] [런 잇] 디바운스(Debounce)와 Combine을 활용한 Throttle 적용하기 그리고 Caching을 통한 디버깅</title>
      <link>https://vesselwheel.tistory.com/231</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. xcode intruments의 leaks를 활용한 디버깅 하기&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-02 16.46.15.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1052&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dn8R6u/btsGjRudnWP/zL1LhKC4GCKxsk7Y3j0P20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dn8R6u/btsGjRudnWP/zL1LhKC4GCKxsk7Y3j0P20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dn8R6u/btsGjRudnWP/zL1LhKC4GCKxsk7Y3j0P20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdn8R6u%2FbtsGjRudnWP%2FzL1LhKC4GCKxsk7Y3j0P20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1052&quot; data-filename=&quot;스크린샷 2024-04-02 16.46.15.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1052&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #1a1918; color: #f0efeb; text-align: start;&quot;&gt;startRequest:traits:auditToken:useBackgroundURL:requestPriority:callbackQueue:finished:networkActivity:error:]_block_invoke_2+0xc4&quot;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디버깅 문구를 생자로 찾아내기엔 스택오버플로우나, 공식문서에서는 찾기 어려웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Chat GPT의 도움을 받았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문구는 프로그래밍 중에 발생한 오류나 버그를 찾아내기 위한 디버깅 과정에서 볼 수 있는 로그의 일부입니다. 구체적으로 설명하자면, 이 로그는 특정 함수나 메소드의 호출 스택 중 하나를 나타내며, 여기서는 어떤 작업을 시작하기 위한 요청(startRequest)이 이루어지는 과정에서 콜백 함수(_block_invoke_2)가 호출되었음을 의미합니다. +0xc4는 해당 함수 내에서 오류가 발생한 위치를 상대적인 주소로 나타낸 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;traits: 요청을 처리할 때 필요한 특성이나 속성을 의미합니다.&lt;/li&gt;
&lt;li&gt;auditToken: 보안이나 인증과 관련된 토큰일 수 있으며, 요청이 유효한지 확인하는 데 사용될 수 있습니다.&lt;/li&gt;
&lt;li&gt;useBackgroundURL: 요청이 백그라운드에서 처리되어야 하는지 여부를 나타냅니다.&lt;/li&gt;
&lt;li&gt;requestPriority: 요청의 우선 순위를 지정합니다.&lt;/li&gt;
&lt;li&gt;callbackQueue: 콜백 함수가 실행될 큐(queue)를 지정합니다.&lt;/li&gt;
&lt;li&gt;finished: 요청이 완료되었을 때 실행될 콜백 함수를 의미합니다.&lt;/li&gt;
&lt;li&gt;networkActivity: 네트워크 활동과 관련된 정보를 나타냅니다.&lt;/li&gt;
&lt;li&gt;error: 오류가 발생했을 경우, 오류에 대한 정보를 담고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&lt;span&gt; &lt;/span&gt;로그를&lt;span&gt; &lt;/span&gt;통해&lt;span&gt; &lt;/span&gt;어떤&lt;span&gt; &lt;/span&gt;네트워크&lt;span&gt; &lt;/span&gt;요청이&lt;span&gt; &lt;/span&gt;처리&lt;span&gt; &lt;/span&gt;과정&lt;span&gt; &lt;/span&gt;중&lt;span&gt; &lt;/span&gt;오류를&lt;span&gt; &lt;/span&gt;만났다는&lt;span&gt; &lt;/span&gt;것을&lt;span&gt; &lt;/span&gt;알&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;. &lt;/span&gt;디버깅을&lt;span&gt; &lt;/span&gt;위해서는&lt;span&gt; +0xc4 &lt;/span&gt;위치에서&lt;span&gt; &lt;/span&gt;발생한&lt;span&gt; &lt;/span&gt;구체적&lt;span&gt; &lt;/span&gt;오류&lt;span&gt; &lt;/span&gt;내용을&lt;span&gt; &lt;/span&gt;조사해야&lt;span&gt; &lt;/span&gt;하며&lt;span&gt;, &lt;/span&gt;그&lt;span&gt; &lt;/span&gt;원인을&lt;span&gt; &lt;/span&gt;파악하여&lt;span&gt; &lt;/span&gt;적절한&lt;span&gt; &lt;/span&gt;수정을&lt;span&gt; &lt;/span&gt;진행해야&lt;span&gt; &lt;/span&gt;합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;기존에 콘솔창에도 나온 버그 문제를 leaks를 통해서 다시한번 되돌아보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-02 16.54.22.png&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EXHVn/btsGh49b49P/L6CsuFg2bHX0oqcnkByDs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EXHVn/btsGh49b49P/L6CsuFg2bHX0oqcnkByDs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EXHVn/btsGh49b49P/L6CsuFg2bHX0oqcnkByDs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEXHVn%2FbtsGh49b49P%2FL6CsuFg2bHX0oqcnkByDs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1162&quot; height=&quot;770&quot; data-filename=&quot;스크린샷 2024-04-02 16.54.22.png&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;편의시설 버튼을 연속 탭했을 때 발생하는 버그, 편의시설 탐색 기능이 정상 작동하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1초 안에 50번 초과의 query 요청을 할 수 없다는 것으로 이것은 MapKit의 서버에서 query 요청수를 제한시켜 놓은 것으로, 만약 유료 지도 API를 사용했다면 비용 상승의 효과로 이어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 사용자의 비정상적인 사용일지라도 개발자는 해당 문제를 미리 식별하여 조치해야한다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;해결책은 디바운스와 쓰로틀링, 그리고 캐싱 처리&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;디바운싱, Debounce 이란?&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연속적으로 발생한 이벤트를 하나로 처리하는 방식이다.&lt;/li&gt;
&lt;li&gt;주로 &lt;b&gt;처음&lt;/b&gt;이나 &lt;b&gt;마지막&lt;/b&gt;으로 실행된 함수만을 실행한다.&lt;/li&gt;
&lt;li&gt;성능상의 문제를 위해 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.webp&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8Zwm3/btsGiS7raMR/CI1NG98edIYx5o4bTpf26k/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8Zwm3/btsGiS7raMR/CI1NG98edIYx5o4bTpf26k/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8Zwm3/btsGiS7raMR/CI1NG98edIYx5o4bTpf26k/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8Zwm3%2FbtsGiS7raMR%2FCI1NG98edIYx5o4bTpf26k%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;661&quot; height=&quot;133&quot; data-filename=&quot;image.webp&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;쓰로틀링, Throttling 이란?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스로틀링은 출력을 조절한다는 의미로 이벤트를 일정주기마다 발생하도록 하는 기술&lt;/li&gt;
&lt;li&gt;100ms 를 준다면 이벤트는 100ms 동안 최대 한번만 발생하게 됨&lt;/li&gt;
&lt;li&gt;즉 마지막 함수가 호출된 후 일정시간이 지나기전에 다시 호출되지 않도록함&lt;/li&gt;
&lt;li&gt;연이어&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;발생한&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이벤트에&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;대해&lt;span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;일정한&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;delay&lt;/span&gt;를&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;포함시켜&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;연속적으로&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;발생한&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이벤트는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;무시하는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;방식을&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;뜻한다&lt;span&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;즉&lt;span&gt;, delay&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;시간동안&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;호출된&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;함수는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;무시한다&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@manikantasirumalla5/demystifying-debounce-and-throttle-in-combine-framework-75539c87b15e&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@manikantasirumalla5/demystifying-debounce-and-throttle-in-combine-framework-75539c87b15e&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711979443875&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Demystifying Debounce and Throttle in Combine Framework&quot; data-og-description=&quot;Combine, Apple&amp;rsquo;s framework for handling asynchronous and event-driven code, provides a range of operators to manipulate and control the&amp;hellip;&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@manikantasirumalla5/demystifying-debounce-and-throttle-in-combine-framework-75539c87b15e&quot; data-og-url=&quot;https://medium.com/@manikantasirumalla5/demystifying-debounce-and-throttle-in-combine-framework-75539c87b15e&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dLwhtj/hyVGODgmNd/Wd6dzRwz9uFKixBoAKPg00/img.png?width=448&amp;amp;height=233&amp;amp;face=0_0_448_233,https://scrap.kakaocdn.net/dn/blDkqp/hyVJU2V8Rk/XIn4k8hkVy5bk0gbOoL8Z1/img.png?width=534&amp;amp;height=322&amp;amp;face=0_0_534_322&quot;&gt;&lt;a href=&quot;https://medium.com/@manikantasirumalla5/demystifying-debounce-and-throttle-in-combine-framework-75539c87b15e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@manikantasirumalla5/demystifying-debounce-and-throttle-in-combine-framework-75539c87b15e&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dLwhtj/hyVGODgmNd/Wd6dzRwz9uFKixBoAKPg00/img.png?width=448&amp;amp;height=233&amp;amp;face=0_0_448_233,https://scrap.kakaocdn.net/dn/blDkqp/hyVJU2V8Rk/XIn4k8hkVy5bk0gbOoL8Z1/img.png?width=534&amp;amp;height=322&amp;amp;face=0_0_534_322');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Demystifying Debounce and Throttle in Combine Framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Combine, Apple&amp;rsquo;s framework for handling asynchronous and event-driven code, provides a range of operators to manipulate and control the&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. 편의시설 버튼에 Debounce 적용하기&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-01 12.21.06.png&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btZYVk/btsGh6kfLBo/XRsAkXkwI1IgGvKPVVCKd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btZYVk/btsGh6kfLBo/XRsAkXkwI1IgGvKPVVCKd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btZYVk/btsGh6kfLBo/XRsAkXkwI1IgGvKPVVCKd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtZYVk%2FbtsGh6kfLBo%2FXRsAkXkwI1IgGvKPVVCKd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;355&quot; height=&quot;693&quot; data-filename=&quot;스크린샷 2024-04-01 12.21.06.png&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 &amp;lt;런 잇&amp;gt; 앱에서 지도뷰의 우측 편의시설 탐색 버튼은 MKLocalSearch.Request를 통해 query를 요청하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1711949733228&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func getAnnotations(forQuery query: String, category: String) {
        closeModal()
        //TODO: 기존의 같은 카테고리의 어노테이션 제거
        let allAnnotations = self.mapView.annotations
        for annotation in allAnnotations {
            if let customAnnotation = annotation as? CustomAnnotation, customAnnotation.category != category {
                self.mapView.removeAnnotation(annotation)
            }
        }
        // mapView.overlays 배열에서 currentCircle를 제외하고 모두 제거
        let overlaysToRemove = mapView.overlays.filter { $0 !== currentCircle }
        mapView.removeOverlays(overlaysToRemove)

        //TODO: 사용자의 현재 위치를 가져오기
        guard let currentLocation = self.mapView.userLocation.location else {
            print(&quot;Failed to get user location&quot;)
            return
        }
        
        //TODO: MKLocalSearch.Request() 활용하여 자연어로 파라미터로 주입된 query를 검색을 정의하고, 지역을 사용자 현재 위치 기준 500m로 설정
        let searchRequest = MKLocalSearch.Request()
        searchRequest.naturalLanguageQuery = query
        searchRequest.region = MKCoordinateRegion(center: currentLocation.coordinate, latitudinalMeters: 500, longitudinalMeters: 500)
        
        //MKLocalSearch를 활용, 검색하여 mapItems을 응답값으로 호출
        let search = MKLocalSearch(request: searchRequest)
        search.start { [weak self] (response, error) in
            guard let response = response else {
                print(&quot;Search error: \(error?.localizedDescription ?? &quot;Unknown error&quot;)&quot;)
                return
            }
            
            // 50미터 이내의 결과만 필터링
            let filteredMapItems = response.mapItems.filter { mapItem in
                let distance = currentLocation.distance(from: mapItem.placemark.location!)
                return distance &amp;lt;= 150
            }
            
            //검색 결과를 캐시에 저장
            self?.searchResultsCache[query] = filteredMapItems
            
            // 검색된 mapItems의 어노테이션을 추가
            self?.addAnnotationsToMap(mapItems: response.mapItems, category: category)
        }

    }
    
    private func addAnnotationsToMap(mapItems: [MKMapItem], category: String) {
        DispatchQueue.main.async {
            // 현재 맵에 있는 모든 어노테이션을 호출
            let existingAnnotations = self.mapView.annotations.compactMap { $0 as? CustomAnnotation }
            
            // 새로운 어노테이션을 추가하기 전에, 이미 존재하는 어노테이션인지 확인
            for item in mapItems {
                let newItemCoordinate = item.placemark.coordinate
                
                // 현재 맵에 동일한 위치의 어노테이션이 있는지 확인
                let isExisting = existingAnnotations.contains(where: { existingAnnotation in
                    existingAnnotation.coordinate.latitude == newItemCoordinate.latitude &amp;amp;&amp;amp; existingAnnotation.coordinate.longitude == newItemCoordinate.longitude
                })
                
                // 동일한 위치의 어노테이션이 없을 경우에만 새 어노테이션을 추가
                if !isExisting {
                    let annotation = CustomAnnotation()
                    annotation.coordinate = newItemCoordinate
                    annotation.title = item.name
                    annotation.mapItem = item
                    annotation.category = category
                    self.mapView.addAnnotation(annotation)
                }
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getAnnotation 매소드를 통해 편의시설 버튼에서 편의시설을 검색하고 서버에서 요청한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711949821263&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @objc func presenthealthyEatingOptionsAnnotations() {
        generator.impactOccurred()
        
        annotationsDebounceTimer?.cancel() // 이전에 예약된 작업이 있다면 취소
        annotationsDebounceTimer = DispatchWorkItem { [weak self] in
            let healthyEatingOptions = [&quot;샐러디&quot;, &quot;subway&quot;]
            let category = &quot;건강식&quot;
            
            for healthyEatingOption in healthyEatingOptions {
                self?.getAnnotations(forQuery: healthyEatingOption, category: category)
            }
        }
        
        // 디바운스 타이머 예약. 여기서는 버튼을 누른 후 1초가 지나면 실행
        if let annotationsDebounceTimer = annotationsDebounceTimer {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: annotationsDebounceTimer)
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatchWorkItem 타입으로 선언하여 &amp;nbsp;현재는 버튼 실행 시 debounce를 통해서 현재 시간 기준 1초가 지나면 실행되게 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1초 안으로 연속 탭한 경우는 실행되지 않도록 구현했다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711949851984&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 디바운스를 위한 변수 선언
    var annotationsDebounceTimer: DispatchWorkItem?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/dispatch/dispatchworkitem&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/dispatch/dispatchworkitem&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711949928788&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;DispatchWorkItem | Apple Developer Documentation&quot; data-og-description=&quot;The work you want to perform, encapsulated in a way that lets you attach a completion handle or execution dependencies.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/dispatch/dispatchworkitem&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/dispatch/dispatchworkitem&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bE1jC5/hyVGGSH1nq/Kvm0R9y9xclu90VK8EIK3K/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ingod/hyVGJ2YhvH/3Kn9W4fGBpqYxokGnOA5ek/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/dispatch/dispatchworkitem&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/dispatch/dispatchworkitem&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bE1jC5/hyVGGSH1nq/Kvm0R9y9xclu90VK8EIK3K/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ingod/hyVGJ2YhvH/3Kn9W4fGBpqYxokGnOA5ek/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;DispatchWorkItem | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The work you want to perform, encapsulated in a way that lets you attach a completion handle or execution dependencies.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 id=&quot;1-디바운싱-debounce&quot; style=&quot;color: #ececec; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Combine 프레임워크를 사용하여 버튼 클릭 이벤트에 대해 쓰로틀(throttle)을 적용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰로틀은 지정된 시간 동안에 첫 번째 이벤트만을 전달하고, 그 시간 동안에 발생하는 나머지 이벤트들을 무시하는 방식으로 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 빠른 연속 클릭과 같은 상황에서 유용하게 사용될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1711950930556&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Combine
// 이벤트 발생을 위한 PassthroughSubject 선언
var convenienceStoreButtonPressed = PassthroughSubject&amp;lt;Void, Never&amp;gt;()

// 구독을 관리할 AnyCancellable 객체
var cancellables = Set&amp;lt;AnyCancellable&amp;gt;()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이벤트를 발생시키기 위한 `PassthroughSubject`와 구독을 관리할 `AnyCancellable` 객체를 선언&lt;/p&gt;
&lt;pre id=&quot;code_1711950943103&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@objc func presentConvenienceStoreAnnotations() {
    // PassthroughSubject에 이벤트를 발생시킵니다.
    convenienceStoreButtonPressed.send()
}

func setupConvenienceStoreButtonThrottle() {
    convenienceStoreButtonPressed
        // 0.5초 동안의 쓰로틀 적용
        .throttle(for: .seconds(0.5), scheduler: RunLoop.main, latest: false)
        // 받은 이벤트에 대한 처리
        .sink { [weak self] _ in
            let convenienceStores = [&quot;GS25&quot;, &quot;CU&quot;, &quot;세븐일레븐&quot;, &quot;이마트24&quot;, &quot;미니스톱&quot;]
            let category = &quot;편의점&quot;

            for convenienceStore in convenienceStores {
                self?.getAnnotations(forQuery: convenienceStore, category: category)
            }
        }
        // 구독을 관리할 cancellables에 추가
        .store(in: &amp;amp;cancellables)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로, `presentConvenienceStoreAnnotations` 함수가 호출될 때마다 `convenienceStoreButtonPressed`에 이벤트를 발생시키도록 수정하고, 쓰로틀을 적용하여 이벤트를 처리&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;`setupConvenienceStoreButtonThrottle` 함수는 앱이 시작할 때나 적절한 초기화 지점에서 호출되어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, setupConvenienceStoreButtonThrottle() &lt;span&gt;함수는&lt;/span&gt; &lt;span&gt;컴포넌트의&lt;/span&gt; &lt;span&gt;초기화&lt;/span&gt; &lt;span&gt;단계&lt;/span&gt;(&lt;span&gt;예&lt;/span&gt;: viewDidLoad&lt;span&gt;에서&lt;/span&gt;)&lt;span&gt;에서&lt;/span&gt; &lt;span&gt;한&lt;/span&gt; &lt;span&gt;번만&lt;/span&gt; &lt;span&gt;호출되어야&lt;/span&gt; &lt;span&gt;한다&lt;/span&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;뷰가 시작될 때, setupConvenienceStoreButtonThrottle() 매소드로 이벤트를 발생시키고 나서,&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711979795237&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @objc func presentConvenienceStoreAnnotations() {
        // PassthroughSubject에 이벤트를 발생시킵니다.
        print(&quot;Button pressed&quot;)
        convenienceStoreButtonPressed.send()
        
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;presentConvenienceStoreAnnotations&lt;span style=&quot;color: #000000;&quot;&gt;() 버튼을 누르면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 발생시켜놨던 이벤트인 convenienceStoreButtonPressed를 .send()해 놓으면, setupConvenienceStoreButtonThrottle&lt;span style=&quot;color: #000000;&quot;&gt;()매소드에 있던 convenienceStoreButtonPressed의 &lt;span style=&quot;color: #000000;&quot;&gt;.&lt;/span&gt;throttle -&amp;gt; &lt;span style=&quot;color: #000000;&quot;&gt;.&lt;/span&gt;sink -&amp;gt; &lt;span style=&quot;color: #000000;&quot;&gt;.&lt;/span&gt;store 단계로 실행된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711979967639&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;             // 1초 동안의 쓰로틀 적용
            .throttle(for: .seconds(1), scheduler: RunLoop.main, latest: false)
            // 받은 이벤트에 대한 처리
            .sink { [weak self] _ in
                print(&quot;Throttle event received&quot;)
                let convenienceStores = [&quot;GS25&quot;, &quot;CU&quot;, &quot;세븐일레븐&quot;, &quot;이마트24&quot;, &quot;미니스톱&quot;]
                let category = &quot;편의점&quot;

                for convenienceStore in convenienceStores {
                    self?.getAnnotations(forQuery: convenienceStore, category: category)
                }
            }
            // 구독을 관리할 cancellables에 추가
            .store(in: &amp;amp;cancellables)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 `convenienceStoreButtonPressed`에 의해 발생되는 이벤트를 쓰로틀링하며, 1초 동안 첫 번째 이벤트만을 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식으로, 사용자가 버튼을 빠르게 여러 번 클릭하더라도, 지정된 시간 동안에 첫 번째 클릭만이 처리되어 불필요한 작업을 방지할 수 있다.&lt;br /&gt;&lt;br /&gt;Combine을 사용한 쓰로틀링은 특히 비동기 이벤트 처리에 있어 강력한 도구가 될 수 있으며, UI 이벤트 처리를 더욱 효율적으로 만들어 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰로틀과 디바운스의 차이&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;쓰로틀(Throttle)과 디바운스(Debounce)는 비슷한 문제를 해결하기 위한 두 가지 다른 접근 방식입니다. 둘 다 빠른 연속 이벤트 발생 시 이를 제어하기 위해 사용되지만, 작동 방식에는 차이가 있다.&lt;br /&gt;&lt;br /&gt;- 쓰로틀(Throttle)은 지정된 시간 동안 첫 번째 이벤트를 전달하고, 그 시간 동안 발생하는 나머지 이벤트들을 무시한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 지정된 시간 간격으로 이벤트를 전달한다. 이 방식은 일정 시간 간격으로 이벤트를 처리해야 할 때 유용하다.&lt;br /&gt;&lt;br /&gt;- 디바운스(Debounce)는 마지막 이벤트가 발생하고 지정된 시간이 지난 후에만 이벤트를 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 지정된 시간 동안 새로운 이벤트가 발생하면 타이머가 리셋된다. 이 방식은 이벤트의 마지막 발생을 기준으로 처리를 하고 싶을 때 사용된다.&lt;br /&gt;&lt;br /&gt;상기 코드에서 쓰로틀을 적용했다면, 이는 지정된 시간(0.5초) 동안 첫 번째 이벤트만 처리하고 나머지는 무시하기 때문에, 디바운스를 별도로 적용할 필요는 없다. 쓰로틀은 이미 연속적인 이벤트 발생을 효과적으로 제어하여, 함수가 너무 자주 호출되는 것을 방지한다.&lt;br /&gt;&lt;br /&gt;따라서,&amp;nbsp;사용자의&amp;nbsp;경우처럼&amp;nbsp;특정&amp;nbsp;기능(예:&amp;nbsp;버튼&amp;nbsp;클릭&amp;nbsp;이벤트&amp;nbsp;처리)을&amp;nbsp;일정&amp;nbsp;시간&amp;nbsp;간격으로&amp;nbsp;제한하고&amp;nbsp;싶다면&amp;nbsp;쓰로틀을&amp;nbsp;사용하는&amp;nbsp;것이&amp;nbsp;적절하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 불필요한 함수 호출을 줄이는 데 도움이 되어 애플리케이션의 성능을 개선할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/combine/passthroughsubject&quot;&gt;https://developer.apple.com/documentation/combine/passthroughsubject&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712037496551&quot; style=&quot;color: #333333; text-align: start;&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/QDQA7/hyVJRyhDW1/BipIzjwUJOWs0Bxt5r9pnk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/uJfjc/hyVGK1UbqG/9mYxT7mZE2ZzHulGND9ha0/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/combine/passthroughsubject&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/combine/passthroughsubject&quot; data-og-host=&quot;developer.apple.com&quot; data-og-description=&quot;A subject that broadcasts elements to downstream subscribers.&quot; data-og-title=&quot;PassthroughSubject | Apple Developer Documentation&quot; data-og-type=&quot;website&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://developer.apple.com/documentation/combine/passthroughsubject&quot; data-source-url=&quot;https://developer.apple.com/documentation/combine/passthroughsubject&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/QDQA7/hyVJRyhDW1/BipIzjwUJOWs0Bxt5r9pnk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/uJfjc/hyVGK1UbqG/9mYxT7mZE2ZzHulGND9ha0/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;PassthroughSubject | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;A subject that broadcasts elements to downstream subscribers.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 캐싱 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캐싱의 이점&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;속도&lt;span&gt; &lt;/span&gt;향상&lt;span&gt;: &lt;/span&gt;자주&lt;span&gt; &lt;/span&gt;사용되는&lt;span&gt; &lt;/span&gt;데이터를&lt;span&gt; &lt;/span&gt;메모리에&lt;span&gt; &lt;/span&gt;저장해&lt;span&gt; &lt;/span&gt;두면&lt;span&gt;, &lt;/span&gt;데이터를&lt;span&gt; &lt;/span&gt;빠르게&lt;span&gt; &lt;/span&gt;접근할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있어&lt;span&gt; &lt;/span&gt;애플리케이션의&lt;span&gt; &lt;/span&gt;반응&lt;span&gt; &lt;/span&gt;속도가&lt;span&gt; &lt;/span&gt;향상&lt;/li&gt;
&lt;li&gt;네트워크&lt;span&gt; &lt;/span&gt;트래픽&lt;span&gt; &lt;/span&gt;감소&lt;span&gt;: &lt;/span&gt;네트워크를&lt;span&gt; &lt;/span&gt;통한&lt;span&gt; &lt;/span&gt;데이터&lt;span&gt; &lt;/span&gt;요청이&lt;span&gt; &lt;/span&gt;줄어들어&lt;span&gt;, &lt;/span&gt;네트워크&lt;span&gt; &lt;/span&gt;부하가&lt;span&gt; &lt;/span&gt;감소하고&lt;span&gt; &lt;/span&gt;데이터&lt;span&gt; &lt;/span&gt;사용량이&lt;span&gt; &lt;/span&gt;줄어든다&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1712037507925&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func getAnnotations(forQuery query: String, category: String) {
        closeModal()
        if let cachedResults = searchResultsCache[query] {
            // 캐시된 결과가 있으면 해당 결과를 사용하여 어노테이션을 추가
//            print(&quot;캐시된 결과: \(cachedResults)&quot;)
            addAnnotationsToMap(mapItems: cachedResults, category: category)
            return
        }
        
        //TODO: 기존의 같은 카테고리의 어노테이션 제거
        let allAnnotations = self.mapView.annotations
        for annotation in allAnnotations {
            if let customAnnotation = annotation as? CustomAnnotation, customAnnotation.category != category {
                self.mapView.removeAnnotation(annotation)
            }
        }
        // mapView.overlays 배열에서 currentCircle를 제외하고 모두 제거
        let overlaysToRemove = mapView.overlays.filter { $0 !== currentCircle }
        mapView.removeOverlays(overlaysToRemove)

        //TODO: 사용자의 현재 위치를 가져오기
        guard let currentLocation = self.mapView.userLocation.location else {
            print(&quot;Failed to get user location&quot;)
            return
        }
        
        //TODO: MKLocalSearch.Request() 활용하여 자연어로 파라미터로 주입된 query를 검색을 정의하고, 지역을 사용자 현재 위치 기준 500m로 설정
        let searchRequest = MKLocalSearch.Request()
        searchRequest.naturalLanguageQuery = query
        searchRequest.region = MKCoordinateRegion(center: currentLocation.coordinate, latitudinalMeters: 5000, longitudinalMeters: 5000)
        
        //MKLocalSearch를 활용, 검색하여 mapItems을 응답값으로 호출
        let search = MKLocalSearch(request: searchRequest)
        search.start { [weak self] (response, error) in
            guard let response = response else {
                print(&quot;Search error: \(error?.localizedDescription ?? &quot;Unknown error&quot;)&quot;)
                return
            }
            
            // 50미터 이내의 결과만 필터링
            let filteredMapItems = response.mapItems.filter { mapItem in
                let distance = currentLocation.distance(from: mapItem.placemark.location!)
                return distance &amp;lt;= 5000
            }
            
            //검색 결과를 캐시에 저장
            self?.searchResultsCache[query] = filteredMapItems
            
            // 검색된 mapItems의 어노테이션을 추가
            self?.addAnnotationsToMap(mapItems: response.mapItems, category: category)
        }

    }
    
    private func addAnnotationsToMap(mapItems: [MKMapItem], category: String) {
        DispatchQueue.main.async {
            // 현재 맵에 있는 모든 어노테이션을 호출
            let existingAnnotations = self.mapView.annotations.compactMap { $0 as? CustomAnnotation }
            
            // 새로운 어노테이션을 추가하기 전에, 이미 존재하는 어노테이션인지 확인
            for item in mapItems {
                let newItemCoordinate = item.placemark.coordinate
                
                // 현재 맵에 동일한 위치의 어노테이션이 있는지 확인
                let isExisting = existingAnnotations.contains(where: { existingAnnotation in
                    existingAnnotation.coordinate.latitude == newItemCoordinate.latitude &amp;amp;&amp;amp; existingAnnotation.coordinate.longitude == newItemCoordinate.longitude
                })
                
                // 동일한 위치의 어노테이션이 없을 경우에만 새 어노테이션을 추가
                if !isExisting {
                    let annotation = CustomAnnotation()
                    annotation.coordinate = newItemCoordinate
                    annotation.title = item.name
                    annotation.mapItem = item
                    annotation.category = category
                    self.mapView.addAnnotation(annotation)
                }
            }
        }
    }
    
    func closeModal() {
        if let currentModal = self.presentedViewController {
            currentModal.dismiss(animated: true)
        }
    }
    
    private func removeAnnotationsFromMap() {
        // mapView.annotations 배열에서 MKUserLocation 인스턴스를 제외하고 모두 제거
        let annotationsToRemove = mapView.annotations.filter { $0 !== mapView.userLocation }
        mapView.removeAnnotations(annotationsToRemove)
        
        // mapView.overlays 배열에서 currentCircle를 제외하고 모두 제거
        let overlaysToRemove = mapView.overlays.filter { $0 !== currentCircle }
        mapView.removeOverlays(overlaysToRemove)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;힙 영역과 스택 영역에 끼치는 영향&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스택 영역(Stack): 함수의 호출과 함께 할당되고, 지역 변수가 저장되는 영역입니다. 스택은 LIFO(Last In First Out) 방식으로 동작하며, 함수 호출이 끝나면 자동으로 메모리가 해제됩니다. 쓰로틀, 디바운스, 캐싱 기법 자체가 스택 영역에 직접적인 영향을 주는 것은 아닙니다.&lt;/li&gt;
&lt;li&gt;힙 영역(Heap): 동적으로 할당되는 메모리가 저장되는 영역입니다. 객체나 배열 같은 구조체는 힙에 저장됩니다. 캐싱에서는 주로 힙 영역을 사용하여 데이터를 저장합니다. 대량의 데이터를 캐싱할 경우 힙 메모리 사용량이 증가하게 됩니다. 메모리 관리가 중요한 이유는, 힙 메모리가 과도하게 사용되면 시스템의 성능 저하나 메모리 누수로 이어질 수 있기 때문입니다.&lt;/li&gt;
&lt;li&gt;쓰로틀과&lt;span&gt; &lt;/span&gt;디바운스는&lt;span&gt; &lt;/span&gt;주로&lt;span&gt; &lt;/span&gt;함수&lt;span&gt; &lt;/span&gt;호출&lt;span&gt; &lt;/span&gt;빈도를&lt;span&gt; &lt;/span&gt;조절하는&lt;span&gt; &lt;/span&gt;기법으로&lt;span&gt;, &lt;/span&gt;직접적으로&lt;span&gt; &lt;/span&gt;메모리&lt;span&gt; &lt;/span&gt;할당에&lt;span&gt; &lt;/span&gt;영향을&lt;span&gt; &lt;/span&gt;주지는&lt;span&gt; &lt;/span&gt;않지만&lt;span&gt;, &lt;/span&gt;결과적으로&lt;span&gt; &lt;/span&gt;리소스&lt;span&gt; &lt;/span&gt;사용량을&lt;span&gt; &lt;/span&gt;줄여&lt;span&gt; &lt;/span&gt;성능을&lt;span&gt; &lt;/span&gt;향상시키는&lt;span&gt; &lt;/span&gt;데&lt;span&gt; &lt;/span&gt;기여합니다&lt;span&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-02 15.12.24.png&quot; data-origin-width=&quot;3014&quot; data-origin-height=&quot;1840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FgQyJ/btsGhVxIoSn/7kK96m7bxwsa24g2YZd85K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FgQyJ/btsGhVxIoSn/7kK96m7bxwsa24g2YZd85K/img.png&quot; data-alt=&quot;러닝타이머를 실행할 때 메모리 누수 발생&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FgQyJ/btsGhVxIoSn/7kK96m7bxwsa24g2YZd85K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFgQyJ%2FbtsGhVxIoSn%2F7kK96m7bxwsa24g2YZd85K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3014&quot; height=&quot;1840&quot; data-filename=&quot;스크린샷 2024-04-02 15.12.24.png&quot; data-origin-width=&quot;3014&quot; data-origin-height=&quot;1840&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;러닝타이머를 실행할 때 메모리 누수 발생&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-02 15.18.06.png&quot; data-origin-width=&quot;3020&quot; data-origin-height=&quot;1832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQclSF/btsGjCcTg9U/KC4mXe4B1pos2OgPGIztO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQclSF/btsGjCcTg9U/KC4mXe4B1pos2OgPGIztO1/img.png&quot; data-alt=&quot;Leak Checks&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQclSF/btsGjCcTg9U/KC4mXe4B1pos2OgPGIztO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQclSF%2FbtsGjCcTg9U%2FKC4mXe4B1pos2OgPGIztO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3020&quot; height=&quot;1832&quot; data-filename=&quot;스크린샷 2024-04-02 15.18.06.png&quot; data-origin-width=&quot;3020&quot; data-origin-height=&quot;1832&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Leak Checks&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://opendoorlife.tistory.com/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://opendoorlife.tistory.com/19&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711954042382&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;RxSwift :: 중복 클릭 방지를 위한 Throttle vs Debounce 차이와 개념, 사용법 알아보기 (iOS 개발)&quot; data-og-description=&quot;보통 앱 화면에서 버튼을 누르면 API 호출이 되는 경우가 잦은데, 종종 다양한 이유로 API 통신이 느려져 유저가 버튼을 연타하는 경우가 생긴다. 마치... 긴박한 티켓팅 같은 상황일 때...   ??? : &quot; data-og-host=&quot;opendoorlife.tistory.com&quot; data-og-source-url=&quot;https://opendoorlife.tistory.com/19&quot; data-og-url=&quot;https://opendoorlife.tistory.com/19&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lGpvb/hyVGGrC0mD/LKJyaMGpJlxMB68TpIgq90/img.png?width=800&amp;amp;height=302&amp;amp;face=0_0_800_302,https://scrap.kakaocdn.net/dn/cnVSs9/hyVGQgGok7/qJSfyE3CrUFFSgr01hQKxk/img.png?width=800&amp;amp;height=302&amp;amp;face=0_0_800_302,https://scrap.kakaocdn.net/dn/xq7cX/hyVJUoePUx/kaH99grBh2JfuSnW3Kv6Gk/img.png?width=2824&amp;amp;height=894&amp;amp;face=0_0_2824_894&quot;&gt;&lt;a href=&quot;https://opendoorlife.tistory.com/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://opendoorlife.tistory.com/19&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lGpvb/hyVGGrC0mD/LKJyaMGpJlxMB68TpIgq90/img.png?width=800&amp;amp;height=302&amp;amp;face=0_0_800_302,https://scrap.kakaocdn.net/dn/cnVSs9/hyVGQgGok7/qJSfyE3CrUFFSgr01hQKxk/img.png?width=800&amp;amp;height=302&amp;amp;face=0_0_800_302,https://scrap.kakaocdn.net/dn/xq7cX/hyVJUoePUx/kaH99grBh2JfuSnW3Kv6Gk/img.png?width=2824&amp;amp;height=894&amp;amp;face=0_0_2824_894');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RxSwift :: 중복 클릭 방지를 위한 Throttle vs Debounce 차이와 개념, 사용법 알아보기 (iOS 개발)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;보통 앱 화면에서 버튼을 누르면 API 호출이 되는 경우가 잦은데, 종종 다양한 이유로 API 통신이 느려져 유저가 버튼을 연타하는 경우가 생긴다. 마치... 긴박한 티켓팅 같은 상황일 때...   ??? :&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;opendoorlife.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RxSwift에서의 쓰로틀 참고자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰로틀을 적용하면서 디버깅 하기&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://meetup.nhncloud.com/posts/315&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://meetup.nhncloud.com/posts/315&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712034074982&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;디버깅을 위한 Xcode 활용 방법 : NHN Cloud Meetup&quot; data-og-description=&quot;디버깅을 위한 Xcode 활용 방법&quot; data-og-host=&quot;meetup.nhncloud.com&quot; data-og-source-url=&quot;https://meetup.nhncloud.com/posts/315&quot; data-og-url=&quot;https://meetup.nhncloud.com/posts/315&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jaepo/hyVGHEjaAQ/NHoZpU9GaMdJsElDEzRCX1/img.png?width=270&amp;amp;height=288&amp;amp;face=0_0_270_288,https://scrap.kakaocdn.net/dn/xP9R2/hyVJ0Cceed/FuVPyHX8xok05C4ClrIcq0/img.png?width=270&amp;amp;height=288&amp;amp;face=0_0_270_288&quot;&gt;&lt;a href=&quot;https://meetup.nhncloud.com/posts/315&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://meetup.nhncloud.com/posts/315&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jaepo/hyVGHEjaAQ/NHoZpU9GaMdJsElDEzRCX1/img.png?width=270&amp;amp;height=288&amp;amp;face=0_0_270_288,https://scrap.kakaocdn.net/dn/xP9R2/hyVJ0Cceed/FuVPyHX8xok05C4ClrIcq0/img.png?width=270&amp;amp;height=288&amp;amp;face=0_0_270_288');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;디버깅을 위한 Xcode 활용 방법 : NHN Cloud Meetup&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;디버깅을 위한 Xcode 활용 방법&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;meetup.nhncloud.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://help.apple.com/instruments/mac/current/#//apple_ref/doc/uid/TP40004652-CH3-SW1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://help.apple.com/instruments/mac/current/#//apple_ref/doc/uid/TP40004652-CH3-SW1&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712036887192&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;https://help.apple.com/instruments/mac/current/#//apple_ref/doc/uid/TP40004652-CH3-SW1&quot; data-og-description=&quot;To see this page, you must enable JavaScript. Pour afficher cette page, vous devez activer JavaScript. Zur Anzeige dieser Seite m&amp;uuml;ssen Sie JavaScript aktivieren. このページを表示するには、JavaScript を有効にする必要があります。 &quot; data-og-host=&quot;help.apple.com&quot; data-og-source-url=&quot;https://help.apple.com/instruments/mac/current/#//apple_ref/doc/uid/TP40004652-CH3-SW1&quot; data-og-url=&quot;https://help.apple.com/instruments/mac/current/#//apple_ref/doc/uid/TP40004652-CH3-SW1&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://help.apple.com/instruments/mac/current/#//apple_ref/doc/uid/TP40004652-CH3-SW1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://help.apple.com/instruments/mac/current/#//apple_ref/doc/uid/TP40004652-CH3-SW1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;https://help.apple.com/instruments/mac/current/#//apple_ref/doc/uid/TP40004652-CH3-SW1&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;To see this page, you must enable JavaScript. Pour afficher cette page, vous devez activer JavaScript. Zur Anzeige dieser Seite m&amp;uuml;ssen Sie JavaScript aktivieren. このページを表示するには、JavaScript を有効にする必要があります。&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;help.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://help.apple.com/instruments/mac/current/#/dev022f987b&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://help.apple.com/instruments/mac/current/#/dev022f987b&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712036900277&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;https://help.apple.com/instruments/mac/current/#/dev022f987b&quot; data-og-description=&quot;To see this page, you must enable JavaScript. Pour afficher cette page, vous devez activer JavaScript. Zur Anzeige dieser Seite m&amp;uuml;ssen Sie JavaScript aktivieren. このページを表示するには、JavaScript を有効にする必要があります。 &quot; data-og-host=&quot;help.apple.com&quot; data-og-source-url=&quot;https://help.apple.com/instruments/mac/current/#/dev022f987b&quot; data-og-url=&quot;https://help.apple.com/instruments/mac/current/#/dev022f987b&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://help.apple.com/instruments/mac/current/#/dev022f987b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://help.apple.com/instruments/mac/current/#/dev022f987b&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;https://help.apple.com/instruments/mac/current/#/dev022f987b&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;To see this page, you must enable JavaScript. Pour afficher cette page, vous devez activer JavaScript. Zur Anzeige dieser Seite m&amp;uuml;ssen Sie JavaScript aktivieren. このページを表示するには、JavaScript を有効にする必要があります。&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;help.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Xcode Study</category>
      <category>combine</category>
      <category>debounce</category>
      <category>query</category>
      <category>Throttle</category>
      <category>Xcode</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/231</guid>
      <comments>https://vesselwheel.tistory.com/231#entry231comment</comments>
      <pubDate>Mon, 1 Apr 2024 14:39:17 +0900</pubDate>
    </item>
    <item>
      <title>ActivityKit &amp;amp; WidgetKit</title>
      <link>https://vesselwheel.tistory.com/230</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10184&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10184&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711768450101&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Meet ActivityKit - WWDC23 - Videos - Apple Developer&quot; data-og-description=&quot;Live Activities are a glanceable way for someone to keep track of the progress of a task within your app. We'll teach you how you can...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10184&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10184&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dzEWWW/hyVGGqYHnQ/ksnK0zMGf1OkW1P0jKxcL1/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10184&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dzEWWW/hyVGGqYHnQ/ksnK0zMGf1OkW1P0jKxcL1/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Meet ActivityKit - WWDC23 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Live Activities are a glanceable way for someone to keep track of the progress of a task within your app. We'll teach you how you can...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/widgetkit/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711768478852&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;WidgetKit | Apple Developer Documentation&quot; data-og-description=&quot;Extend the reach of your app by creating widgets, watch complications, and Live Activities.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/widgetkit/&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/widgetkit/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Mol8p/hyVGR66wvH/nhQKz8hud3gj1CDlEYp8mk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/d0eMt9/hyVGEzV90k/zGK1rEFcP0SAabgtLkEgvk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/widgetkit/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Mol8p/hyVGR66wvH/nhQKz8hud3gj1CDlEYp8mk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/d0eMt9/hyVGEzV90k/zGK1rEFcP0SAabgtLkEgvk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;WidgetKit | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Extend the reach of your app by creating widgets, watch complications, and Live Activities.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/wwdc22/10050&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/wwdc22/10050&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712147727407&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Complications and widgets: Reloaded - WWDC22 - Videos - Apple Developer&quot; data-og-description=&quot;Our widgets code-along returns as we adventure onto the watchOS and iOS Lock Screen. Learn about the latest improvements to WidgetKit...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/wwdc22/10050&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2022/10050/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bYZRgZ/hyVJTwz5Yp/ZhlP5cPbKo1wDymLsaDgF0/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/wwdc22/10050&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/wwdc22/10050&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bYZRgZ/hyVJTwz5Yp/ZhlP5cPbKo1wDymLsaDgF0/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Complications and widgets: Reloaded - WWDC22 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Our widgets code-along returns as we adventure onto the watchOS and iOS Lock Screen. Learn about the latest improvements to WidgetKit...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gyuios.tistory.com/226&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://gyuios.tistory.com/226&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712147957898&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;WWDC22) Complications and widgets: Reloaded&quot; data-og-description=&quot;Complications and widgets: Reloaded - WWDC22 - Videos - Apple Developer 본 글은 WWDC 를 보고, 번역 및 요약 그리고 실행해보는 스터디 프로젝트의 일환입니다. 내용 watchOS Lock Screen 과 complications 로 액세서리 위젯&quot; data-og-host=&quot;gyuios.tistory.com&quot; data-og-source-url=&quot;https://gyuios.tistory.com/226&quot; data-og-url=&quot;https://gyuios.tistory.com/226&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hhj2k/hyVJSxHmYq/SEOasIlSK9wQcv50B8SZpK/img.png?width=800&amp;amp;height=444&amp;amp;face=0_0_800_444,https://scrap.kakaocdn.net/dn/ZdobZ/hyVJ6Jvqqx/dHQb19n6yzcgr6AqQfAte1/img.png?width=800&amp;amp;height=444&amp;amp;face=0_0_800_444,https://scrap.kakaocdn.net/dn/XdZ40/hyVJSYL4n7/vqQPYvr4ZFVw9yO93a9OWk/img.png?width=1628&amp;amp;height=904&amp;amp;face=0_0_1628_904&quot;&gt;&lt;a href=&quot;https://gyuios.tistory.com/226&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gyuios.tistory.com/226&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hhj2k/hyVJSxHmYq/SEOasIlSK9wQcv50B8SZpK/img.png?width=800&amp;amp;height=444&amp;amp;face=0_0_800_444,https://scrap.kakaocdn.net/dn/ZdobZ/hyVJ6Jvqqx/dHQb19n6yzcgr6AqQfAte1/img.png?width=800&amp;amp;height=444&amp;amp;face=0_0_800_444,https://scrap.kakaocdn.net/dn/XdZ40/hyVJSYL4n7/vqQPYvr4ZFVw9yO93a9OWk/img.png?width=1628&amp;amp;height=904&amp;amp;face=0_0_1628_904');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;WWDC22) Complications and widgets: Reloaded&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Complications and widgets: Reloaded - WWDC22 - Videos - Apple Developer 본 글은 WWDC 를 보고, 번역 및 요약 그리고 실행해보는 스터디 프로젝트의 일환입니다. 내용 watchOS Lock Screen 과 complications 로 액세서리 위젯&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gyuios.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;widgetkit에 대한 설명 블로그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;widgetkit의 공식문서 예시&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/emoji-rangers-supporting-live-activities-interactivity-and-animations&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/widgetkit/emoji-rangers-supporting-live-activities-interactivity-and-animations&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712191099817&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Emoji Rangers: Supporting Live Activities, interactivity, and animations | Apple Developer Documentation&quot; data-og-description=&quot;Offer Live Activities, animate data updates, and add interactivity to widgets.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/widgetkit/emoji-rangers-supporting-live-activities-interactivity-and-animations&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/widgetkit/emoji-rangers-supporting-live-activities-interactivity-and-animations&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cHx5yt/hyVJ3F7ynl/LZMUoUMGmHJcEIkKO9XjL0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/Kqmrp/hyVJ543uAd/XpKHnpxXal7PY89c8DGDp0/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/emoji-rangers-supporting-live-activities-interactivity-and-animations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/widgetkit/emoji-rangers-supporting-live-activities-interactivity-and-animations&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cHx5yt/hyVJ3F7ynl/LZMUoUMGmHJcEIkKO9XjL0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/Kqmrp/hyVJ543uAd/XpKHnpxXal7PY89c8DGDp0/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Emoji Rangers: Supporting Live Activities, interactivity, and animations | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Offer Live Activities, animate data updates, and add interactivity to widgets.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1712791328807&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  RunItWidget.swift
//  RunItWidget
//
//  Created by Jason Yang on 4/11/24.
//

import WidgetKit
import SwiftUI
// struct Provider의 정체
// 위젯을 업데이트 할 시기를 WidgetKit에 알리는 역할
// SimpleEntry를 포함하여, 본격적으로 위젯에 표시될 placeholder, 데이터를 가져와서 표출해주는 getSnapshot, 타임라인 설정 관련된 getTimeLine이 존재
struct Provider: AppIntentTimelineProvider {
    // 데이터를 불러오기 전(getSnapshot)에 보여줄 placeholder
    func placeholder(in context: Context) -&amp;gt; SimpleEntry {
        SimpleEntry(date: Date(),  // *TimelineEntry를 준수하는 구조체, *TimelineEntry: 위젯을 표시할 Date를 정하고, 그 Data에 표시할 데이터를 나타냄
                    configuration: ConfigurationAppIntent())
    }

    // 위젯 갤러리에서 위젯을 고를 때 보이는 샘플 데이터를 보여줄때 해당 메소드 호출
    // API를 통해서 데이터를 fetch하여 보여줄때 딜레이가 있는 경우 여기서 샘플 데이터를 하드코딩해서 보여주는 작업도 가능
    // context.isPreview가 true인 경우 위젯 갤러리에 위젯이 표출되는 상태
    
    func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -&amp;gt; SimpleEntry {
        SimpleEntry(date: Date(), configuration: configuration)
    }
    
    // 홈화면에 있는 위젯을 언제 업데이트 시킬것인지 구현하는 부분
    func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -&amp;gt; Timeline&amp;lt;SimpleEntry&amp;gt; {
        var entries: [SimpleEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for hourOffset in 0 ..&amp;lt; 5 {
            // 1시간뒤, 2시간뒤, ... 4시간뒤 entry 값으로 업데이트 하라는 코드
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        // (4시간뒤에 다시 타임라인을 새로 다시 불러옴)
        return Timeline(entries: entries, policy: .atEnd)
        // .atEnd:  마지막 date가 끝난 후 타임라인 reloading
        // .after: 다음 data가 지난 후 타임라인 reloading
        // .never: 즉시 타임라인 reloading
    }

    func recommendations() -&amp;gt; [AppIntentRecommendation&amp;lt;ConfigurationAppIntent&amp;gt;] {
        // Create an array with all the preconfigured widgets to show.
        [AppIntentRecommendation(intent: ConfigurationAppIntent(), description: &quot;Example Widget&quot;)]
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationAppIntent
}

struct RunItWidgetEntryView : View {
    @Environment(\.widgetFamily) var family: WidgetFamily
    var entry: Provider.Entry

    var body: some View {
        VStack {
            HStack {
                Text(&quot;Time:&quot;)
                Text(entry.date, style: .time)
            }
        
            Text(&quot;Favorite Emoji:&quot;)
            Text(entry.configuration.favoriteEmoji)
        }
    }
}

@main
struct RunItWidget: Widget {
    let kind: String = &quot;RunItWidget&quot;
    
    // body 안에 사용하는 Configuration
    // IntentConfiguration: 사용자가 위젯에서 Edit을 통해 위젯에 보여지는 내용 변경이 가능
    // StaticConfiguration: 사용자가 변경 불가능한 정적 데이터 표출

    var body: some WidgetConfiguration {
        AppIntentConfiguration(
            kind: kind,
            intent: ConfigurationAppIntent.self, // 사용자가 설정하는 컨피그
            provider: Provider()  // 위젯 생성자 (타이밍 설정도 가능)
        ) { entry in  // 위젯에 표출될 뷰
            RunItWidgetEntryView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
    }
}

extension ConfigurationAppIntent {
    fileprivate static var smiley: ConfigurationAppIntent {
        let intent = ConfigurationAppIntent()
        intent.favoriteEmoji = &quot; &quot;
        return intent
    }
    
    fileprivate static var starEyes: ConfigurationAppIntent {
        let intent = ConfigurationAppIntent()
        intent.favoriteEmoji = &quot; &quot;
        return intent
    }
}

#Preview(as: .accessoryRectangular) {
    RunItWidget()
} timeline: {
    SimpleEntry(date: .now, configuration: .smiley)
    SimpleEntry(date: .now, configuration: .starEyes)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 설명&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**RunItWidget&amp;nbsp;코드&amp;nbsp;설명**&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;코드는&amp;nbsp;SwiftUI를&amp;nbsp;사용하여&amp;nbsp;iOS&amp;nbsp;위젯을&amp;nbsp;만드는&amp;nbsp;방법을&amp;nbsp;보여줍니다.&amp;nbsp;위젯은&amp;nbsp;사용자의&amp;nbsp;홈&amp;nbsp;화면에&amp;nbsp;작은&amp;nbsp;정보&amp;nbsp;조각을&amp;nbsp;제공하며,&amp;nbsp;이&amp;nbsp;코드는&amp;nbsp;위젯의&amp;nbsp;기본&amp;nbsp;구조와&amp;nbsp;기능을&amp;nbsp;설명합니다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;---&lt;br /&gt;&lt;br /&gt;###&amp;nbsp;Provider&amp;nbsp;구조체&lt;br /&gt;-&amp;nbsp;**역할**:&amp;nbsp;위젯의&amp;nbsp;데이터를&amp;nbsp;관리하고,&amp;nbsp;위젯이&amp;nbsp;언제&amp;nbsp;업데이트될지&amp;nbsp;WidgetKit에&amp;nbsp;알립니다.&lt;br /&gt;-&amp;nbsp;**주요&amp;nbsp;메소드**:&lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`placeholder`:&amp;nbsp;데이터&amp;nbsp;로딩&amp;nbsp;전에&amp;nbsp;임시로&amp;nbsp;보여줄&amp;nbsp;내용을&amp;nbsp;정의합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`snapshot`:&amp;nbsp;위젯&amp;nbsp;갤러리에서&amp;nbsp;샘플&amp;nbsp;데이터를&amp;nbsp;보여줄&amp;nbsp;때&amp;nbsp;사용합니다.&amp;nbsp;실제&amp;nbsp;데이터&amp;nbsp;로딩에&amp;nbsp;딜레이가&amp;nbsp;있을&amp;nbsp;때&amp;nbsp;샘플&amp;nbsp;데이터를&amp;nbsp;표시할&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`timeline`:&amp;nbsp;위젯의&amp;nbsp;업데이트&amp;nbsp;타이밍을&amp;nbsp;결정합니다.&amp;nbsp;여기서는&amp;nbsp;현재&amp;nbsp;시간부터&amp;nbsp;시작하여&amp;nbsp;1시간&amp;nbsp;간격으로&amp;nbsp;5개의&amp;nbsp;엔트리를&amp;nbsp;생성하고,&amp;nbsp;마지막&amp;nbsp;엔트리&amp;nbsp;이후에&amp;nbsp;타임라인을&amp;nbsp;다시&amp;nbsp;로딩합니다.&lt;br /&gt;&lt;br /&gt;###&amp;nbsp;SimpleEntry&amp;nbsp;구조체&lt;br /&gt;-&amp;nbsp;**목적**:&amp;nbsp;위젯에&amp;nbsp;표시될&amp;nbsp;데이터를&amp;nbsp;정의합니다.&lt;br /&gt;-&amp;nbsp;**특징**:&amp;nbsp;`TimelineEntry`&amp;nbsp;프로토콜을&amp;nbsp;준수하며,&amp;nbsp;위젯에&amp;nbsp;표시될&amp;nbsp;날짜와&amp;nbsp;설정을&amp;nbsp;포함합니다.&lt;br /&gt;&lt;br /&gt;###&amp;nbsp;RunItWidgetEntryView&amp;nbsp;구조체&lt;br /&gt;-&amp;nbsp;**기능**:&amp;nbsp;위젯의&amp;nbsp;UI를&amp;nbsp;정의합니다.&lt;br /&gt;-&amp;nbsp;**디자인**:&amp;nbsp;사용자가&amp;nbsp;선택한&amp;nbsp;이모지와&amp;nbsp;현재&amp;nbsp;시간을&amp;nbsp;표시합니다.&lt;br /&gt;&lt;br /&gt;###&amp;nbsp;RunItWidget&amp;nbsp;구조체&lt;br /&gt;-&amp;nbsp;**설정**:&amp;nbsp;위젯의&amp;nbsp;메인&amp;nbsp;구조를&amp;nbsp;정의합니다.&lt;br /&gt;-&amp;nbsp;**Configuration&amp;nbsp;타입**:&lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`AppIntentConfiguration`:&amp;nbsp;사용자가&amp;nbsp;위젯의&amp;nbsp;내용을&amp;nbsp;변경할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;해주는&amp;nbsp;설정입니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`Provider`:&amp;nbsp;위젯의&amp;nbsp;데이터와&amp;nbsp;업데이트&amp;nbsp;타이밍을&amp;nbsp;관리합니다.&lt;br /&gt;&lt;br /&gt;###&amp;nbsp;ConfigurationAppIntent&amp;nbsp;확장&lt;br /&gt;-&amp;nbsp;**목적**:&amp;nbsp;사용자가&amp;nbsp;위젯에서&amp;nbsp;선택할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;이모지를&amp;nbsp;미리&amp;nbsp;설정합니다.&lt;br /&gt;-&amp;nbsp;**예시**:&amp;nbsp;&quot; &quot;와&amp;nbsp;&quot; &quot;&amp;nbsp;이모지를&amp;nbsp;선택할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;해줍니다.&lt;br /&gt;&lt;br /&gt;---&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;코드는&amp;nbsp;SwiftUI와&amp;nbsp;WidgetKit을&amp;nbsp;사용하여&amp;nbsp;사용자&amp;nbsp;정의&amp;nbsp;가능한&amp;nbsp;iOS&amp;nbsp;위젯을&amp;nbsp;만드는&amp;nbsp;방법을&amp;nbsp;보여줍니다.&amp;nbsp;위젯을&amp;nbsp;통해&amp;nbsp;사용자는&amp;nbsp;자신의&amp;nbsp;홈&amp;nbsp;화면에서&amp;nbsp;유용한&amp;nbsp;정보를&amp;nbsp;빠르게&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;더&amp;nbsp;자세한&amp;nbsp;정보는&amp;nbsp;[Apple&amp;nbsp;Developer&amp;nbsp;문서](&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui-concepts/exploring-the-structure-of-a-swiftui-app)에서&quot;&gt;https://developer.apple.com/tutorials/swiftui-concepts/exploring-the-structure-of-a-swiftui-app)에서&lt;/a&gt;&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;위젯&amp;nbsp;개발에&amp;nbsp;관한&amp;nbsp;더&amp;nbsp;궁금한&amp;nbsp;점이&amp;nbsp;있다면&amp;nbsp;언제든지&amp;nbsp;물어보세요!&amp;nbsp; &amp;nbsp;&lt;br /&gt;&lt;br /&gt;이런&amp;nbsp;자료를&amp;nbsp;참고했어요.&lt;br /&gt;[1]&amp;nbsp;Apple&amp;nbsp;Developer&amp;nbsp;-&amp;nbsp;Exploring&amp;nbsp;the&amp;nbsp;structure&amp;nbsp;of&amp;nbsp;a&amp;nbsp;SwiftUI&amp;nbsp;app&amp;nbsp;(&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui-concepts/exploring-the-structure-of-a-swiftui-app)&quot;&gt;https://developer.apple.com/tutorials/swiftui-concepts/exploring-the-structure-of-a-swiftui-app)&lt;/a&gt;&lt;br /&gt;[2]&amp;nbsp;Apple&amp;nbsp;Developer&amp;nbsp;-&amp;nbsp;Creating&amp;nbsp;and&amp;nbsp;combining&amp;nbsp;views&amp;nbsp;&amp;mdash;&amp;nbsp;SwiftUI&amp;nbsp;Tutorials&amp;nbsp;(&lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/creating-and-combining-views)&quot;&gt;https://developer.apple.com/tutorials/swiftui/creating-and-combining-views)&lt;/a&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Xcode Study</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/230</guid>
      <comments>https://vesselwheel.tistory.com/230#entry230comment</comments>
      <pubDate>Sat, 30 Mar 2024 12:14:46 +0900</pubDate>
    </item>
    <item>
      <title>테이블 뷰에서 데이터를 표시하고 데이터 원본과 델리게이트 메서드를 사용하는 방법을 설명해주세요.</title>
      <link>https://vesselwheel.tistory.com/229</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 데이터 원본(Data Source) 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UITableViewDataSource 프로토콜은 테이블 뷰에 데이터를 제공하는 메서드를 정의합니다. 주요 메서드는 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;numberOfSectionsInTableView: 테이블 뷰의 섹션 수를 반환합니다. 섹션이 하나라면 1을 반환하면 됩니다.&lt;/li&gt;
&lt;li&gt;tableView(_:numberOfRowsInSection:): 각 섹션에 속한 행의 수를 반환합니다.&lt;/li&gt;
&lt;li&gt;tableView(_:cellForRowAt:): 각 행에 대한 셀 객체를 반환합니다. 여기서 셀의 내용을 구성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 델리게이트(Delegate) 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UITableViewDelegate 프로토콜은 테이블 뷰의 시각적인 부분과 사용자 인터랙션을 관리합니다. 주요 메서드는 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;tableView(_:didSelectRowAt:): 사용자가 특정 행을 선택했을 때 호출됩니다.&lt;/li&gt;
&lt;li&gt;tableView(_:heightForRowAt:): &lt;span&gt;각&lt;/span&gt; &lt;span&gt;행의&lt;/span&gt; &lt;span&gt;높이를&lt;/span&gt; &lt;span&gt;설정할&lt;/span&gt; &lt;span&gt;수&lt;/span&gt; &lt;span&gt;있습니다&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Grammary in Swift</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/229</guid>
      <comments>https://vesselwheel.tistory.com/229#entry229comment</comments>
      <pubDate>Thu, 28 Mar 2024 09:37:48 +0900</pubDate>
    </item>
    <item>
      <title>Required Reason API(from App Store Connect)</title>
      <link>https://vesselwheel.tistory.com/228</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jochen-holzer.medium.com/embrace-the-evolution-preparing-your-ios-app-for-the-required-reason-api-38f2d12bbce5&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jochen-holzer.medium.com/embrace-the-evolution-preparing-your-ios-app-for-the-required-reason-api-38f2d12bbce5&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711414307505&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Embrace the Evolution: Preparing Your iOS App for the &amp;ldquo;Required Reason API&amp;rdquo;&quot; data-og-description=&quot;Learn how to prepare your iOS app for the required reason API. Step-by-Step guide that helps to prepare the privacy manifest of your app in Xcode.&quot; data-og-host=&quot;jochen-holzer.medium.com&quot; data-og-source-url=&quot;https://jochen-holzer.medium.com/embrace-the-evolution-preparing-your-ios-app-for-the-required-reason-api-38f2d12bbce5&quot; data-og-url=&quot;https://jochen-holzer.medium.com/embrace-the-evolution-preparing-your-ios-app-for-the-required-reason-api-38f2d12bbce5&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b1lI7H/hyVGLZaqvG/LrDLskLEcJkS7NvknCCn7k/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512,https://scrap.kakaocdn.net/dn/bIYlu4/hyVDzsxRJf/qGSdK4eELDgDqWEYyPqBBK/img.png?width=1358&amp;amp;height=679&amp;amp;face=0_0_1358_679,https://scrap.kakaocdn.net/dn/d2Uhbo/hyVGSRvQyY/CKIuzAx7OvdULmktvnVlgK/img.jpg?width=850&amp;amp;height=478&amp;amp;face=0_0_850_478&quot;&gt;&lt;a href=&quot;https://jochen-holzer.medium.com/embrace-the-evolution-preparing-your-ios-app-for-the-required-reason-api-38f2d12bbce5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jochen-holzer.medium.com/embrace-the-evolution-preparing-your-ios-app-for-the-required-reason-api-38f2d12bbce5&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b1lI7H/hyVGLZaqvG/LrDLskLEcJkS7NvknCCn7k/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512,https://scrap.kakaocdn.net/dn/bIYlu4/hyVDzsxRJf/qGSdK4eELDgDqWEYyPqBBK/img.png?width=1358&amp;amp;height=679&amp;amp;face=0_0_1358_679,https://scrap.kakaocdn.net/dn/d2Uhbo/hyVGSRvQyY/CKIuzAx7OvdULmktvnVlgK/img.jpg?width=850&amp;amp;height=478&amp;amp;face=0_0_850_478');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Embrace the Evolution: Preparing Your iOS App for the &amp;ldquo;Required Reason API&amp;rdquo;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to prepare your iOS app for the required reason API. Step-by-Step guide that helps to prepare the privacy manifest of your app in Xcode.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jochen-holzer.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api#4278393&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api#4278393&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711414326798&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Describing use of required reason API | Apple Developer Documentation&quot; data-og-description=&quot;Ensure your use of covered API is consistent with policy.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api#4278393&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/v8tbP/hyVGN3KtKD/IrMT8CXCfZMzFOACWrTZdK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/YFGSU/hyVDxhb4r7/Uy6kQRC24gnp0okklMBHY1/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api#4278393&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api#4278393&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/v8tbP/hyVGN3KtKD/IrMT8CXCfZMzFOACWrTZdK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/YFGSU/hyVDxhb4r7/Uy6kQRC24gnp0okklMBHY1/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Describing use of required reason API | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Ensure your use of covered API is consistent with policy.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;앱이 핵심 기능을 제공하기 위해 사용하는 일부 API(귀하가 작성하거나 타사 SDK에 포함된 코드)는 기기나 사용자를 식별하기 위해 기기 신호에 액세스하는 데 오용될 가능성이 있습니다(지문 채취라고도 함). &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;사용자가 앱에 추적 권한을 부여했는지 여부에 관계없이 지문 채취는 허용되지 않습니다. iOS, iPadOS, tvOS, VisionOS 또는 watchOS의 앱 또는 타사 SDK가 이러한 API를 사용하는 이유를 설명하고 앱 또는 타사 SDK가 예상된 이유로만 API를 사용하는지 확인하세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start; color: #000000;&quot;&gt;개인정보 보호 매니페스트 파일에 이유를 설명하지 않고 필수 이유 API를 사용하는 앱을 App Store Connect에 업로드하는 경우, Apple은 앱의 개인정보 매니페스트에 이유를 추가하라고 알리는 이메일을 사용자에게 보냅니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start; color: #000000;&quot;&gt;2024년 5월 1일부터 개인 정보 매니페스트 파일에 필수 이유 API 사용을 설명하지 않는 앱은 App Store Connect에서 허용되지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-26 09.54.53.png&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;1754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Urq4I/btsF3edhEoM/cNY2JIPIVLeWgiByw3kOg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Urq4I/btsF3edhEoM/cNY2JIPIVLeWgiByw3kOg0/img.png&quot; data-alt=&quot;앱 심사 후 받은 API 관련 이메일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Urq4I/btsF3edhEoM/cNY2JIPIVLeWgiByw3kOg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUrq4I%2FbtsF3edhEoM%2FcNY2JIPIVLeWgiByw3kOg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2046&quot; height=&quot;1754&quot; data-filename=&quot;스크린샷 2024-03-26 09.54.53.png&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;1754&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;앱 심사 후 받은 API 관련 이메일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start; color: #000000;&quot;&gt;설정방법&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start; color: #000000;&quot;&gt;&lt;a href=&quot;https://developers.smaato.com/publishers/ios17-privacy-manifest-file/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.smaato.com/publishers/ios17-privacy-manifest-file/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711414437265&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;iOS17 Privacy Manifest File | Smaato&quot; data-og-description=&quot;Privacy Manifests allow app developers to document the types of data their app collects on its own and through third-party tools like SDKs.&quot; data-og-host=&quot;developers.smaato.com&quot; data-og-source-url=&quot;https://developers.smaato.com/publishers/ios17-privacy-manifest-file/&quot; data-og-url=&quot;https://developers.smaato.com/publishers/ios17-privacy-manifest-file/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bGE1OX/hyVGO2Ed3E/sNk1UBu7mSOk6IWUxgaUrK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/byVHmf/hyVDyUJeEj/KDU09TxxWOnXNPVPz0lUjK/img.png?width=488&amp;amp;height=600&amp;amp;face=0_0_488_600,https://scrap.kakaocdn.net/dn/ezhND/hyVDzzluZo/O8pbL3zvkMXthNskLR1lF0/img.png?width=600&amp;amp;height=275&amp;amp;face=0_0_600_275&quot;&gt;&lt;a href=&quot;https://developers.smaato.com/publishers/ios17-privacy-manifest-file/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.smaato.com/publishers/ios17-privacy-manifest-file/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bGE1OX/hyVGO2Ed3E/sNk1UBu7mSOk6IWUxgaUrK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/byVHmf/hyVDyUJeEj/KDU09TxxWOnXNPVPz0lUjK/img.png?width=488&amp;amp;height=600&amp;amp;face=0_0_488_600,https://scrap.kakaocdn.net/dn/ezhND/hyVDzzluZo/O8pbL3zvkMXthNskLR1lF0/img.png?width=600&amp;amp;height=275&amp;amp;face=0_0_600_275');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;iOS17 Privacy Manifest File | Smaato&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Privacy Manifests allow app developers to document the types of data their app collects on its own and through third-party tools like SDKs.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.smaato.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.naver.com/syunsyun-/223392321839&quot;&gt;https://blog.naver.com/syunsyun-/223392321839&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711414535549&quot; style=&quot;color: #333333; text-align: start;&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/emmYMu/hyVDxuJu0h/wWwBTjmbWqLs85apFtIdGK/img.png?width=743&amp;amp;height=529&amp;amp;face=622_260_663_306&quot; data-og-url=&quot;https://blog.naver.com/syunsyun-/223392321839&quot; data-og-source-url=&quot;https://blog.naver.com/syunsyun-/223392321839&quot; data-og-host=&quot;blog.naver.com&quot; data-og-description=&quot;오늘은 API 사용 이유에 대하여 알아보았다. https://developer.apple.com/documentation/bundleresourc...&quot; data-og-title=&quot;2024-03-22 TIL&quot; data-og-type=&quot;article&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://blog.naver.com/syunsyun-/223392321839&quot; data-source-url=&quot;https://blog.naver.com/syunsyun-/223392321839&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/emmYMu/hyVDxuJu0h/wWwBTjmbWqLs85apFtIdGK/img.png?width=743&amp;amp;height=529&amp;amp;face=622_260_663_306');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;2024-03-22 TIL&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 API 사용 이유에 대하여 알아보았다. https://developer.apple.com/documentation/bundleresourc...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;blog.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Xcode Study</category>
      <category>App Store Connect</category>
      <category>Required Reason API</category>
      <category>swift</category>
      <category>Xcode</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/228</guid>
      <comments>https://vesselwheel.tistory.com/228#entry228comment</comments>
      <pubDate>Tue, 26 Mar 2024 09:57:21 +0900</pubDate>
    </item>
    <item>
      <title>앱 심사 요청 후 reject</title>
      <link>https://vesselwheel.tistory.com/227</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Review Environment&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Submission ID: 6702533a-06c9-4ddf-b8a6-536391bbbeb8&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Review date: March 21, 2024&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Version reviewed: 1.0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Guideline 2.1 - Performance - App Completeness&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Your app or its metadata does not appear to include complete and final content. Specifically, your app includes placeholder content under Event.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App Store users expect the apps they download to be ready for public use. Apps on the store shouldn't include placeholder or incomplete content or information.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next Steps&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;To resolve this issue, please revise your app and metadata so that all of its content is complete and final.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For example:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Your app binary or metadata should not include &quot;lorem ipsum&quot; text.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- App metadata should not include placeholder screenshots, such as &quot;screenshot coming soon.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Resources&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Learn how to &lt;a href=&quot;https://developer.apple.com/app-store/product-page/&quot;&gt;create an engaging App Store product page&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Guideline 5.1.1 - Legal - Privacy - Data Collection and Storage&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.1.1(ii) Permission&lt;i&gt; Apps that collect user or usage data must secure user consent for the collection, even if such data is considered to be anonymous at the time of or immediately following collection. Paid functionality must not be dependent on or require a user to grant access to this data. Apps must also provide the customer with an easily accessible and understandable way to withdraw consent. Ensure your purpose strings clearly and completely describe your use of the data. Apps that collect data for a legitimate interest without consent by relying on the terms of the European Union&amp;rsquo;s GeneralData Protection Regulation (&quot;GDPR&quot;) or similar statute must comply with all terms of that law.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Issue Description&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;One or more purpose strings in the app do not sufficiently explain the use of protected resources. Purpose strings must clearly and completely describe the app's use of data and, in most cases, provide an example of how the data will be used.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Examples of unclear purpose strings:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &quot;App would like to access your Contacts&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &quot;App needs microphone access&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next Steps&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Update the location purpose string to explain how the app will use the requested information and provide an example of how the data will be used. See the attached screenshot.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Resources&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- See examples of &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/patterns/accessing-private-data&quot;&gt;helpful, informative purpose strings&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Review a list of &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW18&quot;&gt;relevant property list keys&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Support&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Reply to this message in your preferred language if you need assistance. If you need additional support, use the &lt;a href=&quot;https://developer.apple.com/contact/topic/#!/topic/select&quot;&gt;Contact Us module&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Consult with fellow developers and Apple engineers on the &lt;a href=&quot;https://developer.apple.com/forums/&quot;&gt;Apple Developer Forums&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- If you think your submission follows the guidelines or feel the review was unfair, you can &lt;a href=&quot;https://developer.apple.com/contact/request/app-review/appeal/&quot;&gt;request an appeal with the App Review Board&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Help improve the review process or identify a need for clarity in our policies by &lt;a href=&quot;https://developer.apple.com/contact/app-store/?topic=guideline&quot;&gt;suggesting guideline changes&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회신내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Regarding Guideline 2.1 - Performance - App Completion, we will first disable the event button function to complete the functionality. After the functionality is fully implemented, we will apply it in the next version.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Regarding Guideline 5.1.1 - Legal - Privacy - Data Collection and Storage, we will specify the purpose of the user location data collection and storage by saying that the collected user location information is used to provide weather and map information and learning paths based on the current location.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(수집한&amp;nbsp;사용자&amp;nbsp;위치&amp;nbsp;정보는&amp;nbsp;현재&amp;nbsp;위치에&amp;nbsp;기반한&amp;nbsp;날씨&amp;nbsp;및&amp;nbsp;지도&amp;nbsp;정보,&amp;nbsp;러닝&amp;nbsp;경로&amp;nbsp;제공을&amp;nbsp;위해&amp;nbsp;사용됩니다. in Korean)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Xcode Study</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/227</guid>
      <comments>https://vesselwheel.tistory.com/227#entry227comment</comments>
      <pubDate>Fri, 22 Mar 2024 13:03:20 +0900</pubDate>
    </item>
    <item>
      <title>iOS에서 뷰(View)와 레이어(Layer)의 개념과 차이점에 대해 설명해보세요.</title>
      <link>https://vesselwheel.tistory.com/225</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@anfgbwl/Swift-뷰View와-레이어Layer의-개념과-차이&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@anfgbwl/Swift-뷰View와-레이어Layer의-개념과-차이&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711057734543&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Swift] 뷰(View)와 레이어(Layer)의 개념과 차이&quot; data-og-description=&quot;iOS에서 뷰(View)와 레이어(Layer)는 그래픽 요소를 표시하고 관리하는 데 사용되는 중요한 구성 요소이다.뷰(View)는 사용자 인터페이스의 기본 구성 요소이다. 화면에 표시되는 버튼, 레이블, 이미&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@anfgbwl/Swift-뷰View와-레이어Layer의-개념과-차이&quot; data-og-url=&quot;https://velog.io/@anfgbwl/Swift-뷰View와-레이어Layer의-개념과-차이&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/irazS/hyVABj10S2/9z1fjsz76cP7zeoAI9x6X1/img.png?width=1250&amp;amp;height=688&amp;amp;face=605_187_743_337,https://scrap.kakaocdn.net/dn/IZVt9/hyVAMMEL4r/7kHnJXnC0KNWkiiO3g7BLK/img.png?width=1250&amp;amp;height=688&amp;amp;face=605_187_743_337,https://scrap.kakaocdn.net/dn/dYItjB/hyVAOKvlYx/x1JnAfWaU2fUSpRFeLLIy0/img.png?width=1250&amp;amp;height=688&amp;amp;face=605_187_743_337&quot;&gt;&lt;a href=&quot;https://velog.io/@anfgbwl/Swift-뷰View와-레이어Layer의-개념과-차이&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@anfgbwl/Swift-뷰View와-레이어Layer의-개념과-차이&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/irazS/hyVABj10S2/9z1fjsz76cP7zeoAI9x6X1/img.png?width=1250&amp;amp;height=688&amp;amp;face=605_187_743_337,https://scrap.kakaocdn.net/dn/IZVt9/hyVAMMEL4r/7kHnJXnC0KNWkiiO3g7BLK/img.png?width=1250&amp;amp;height=688&amp;amp;face=605_187_743_337,https://scrap.kakaocdn.net/dn/dYItjB/hyVAOKvlYx/x1JnAfWaU2fUSpRFeLLIy0/img.png?width=1250&amp;amp;height=688&amp;amp;face=605_187_743_337');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Swift] 뷰(View)와 레이어(Layer)의 개념과 차이&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;iOS에서 뷰(View)와 레이어(Layer)는 그래픽 요소를 표시하고 관리하는 데 사용되는 중요한 구성 요소이다.뷰(View)는 사용자 인터페이스의 기본 구성 요소이다. 화면에 표시되는 버튼, 레이블, 이미&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Grammary in Swift</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/225</guid>
      <comments>https://vesselwheel.tistory.com/225#entry225comment</comments>
      <pubDate>Fri, 22 Mar 2024 06:52:15 +0900</pubDate>
    </item>
    <item>
      <title>확장(Extension)에 대해서 설명해주세요.</title>
      <link>https://vesselwheel.tistory.com/224</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;기존 타입에 기능을 추가하는 수평 확장하는 개념으로 확장을 이용하여 structure, class, enum, protocol 타입에 새로운 기능을 추가할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;외부에서 가져온 타입에 내가 원하는 기능을 추가하고자 할 때 확장을 사용할 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;확장으로 구현 가능한 것은 다음과 같습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot; data-token-index=&quot;0&quot;&gt;Extension으로 구현 가능한 것들:&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;새로운 계산된 속성(Computed Property) 추가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;새로운 인스턴스/타입 메서드 추가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;새로운 초기화(Initializer) 추가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로토콜 채택(Protocol Conformance)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서브스크립트 추가(Subscripting)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중첩 타입(Nested Type) 추가&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Extension으로 구현 불가능한 것들:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;저장 프로퍼티(Stored Property) 추가&lt;/b&gt;: Extension으로는 저장 프로퍼티를 추가할 수 없습니다. 오직 계산된 프로퍼티만 추가할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기존 기능의 재정의(Override)&lt;/b&gt;: 이미 존재하는 기능을 Extension에서 재정의(Override)할 수 없습니다. 상속과 재정의는 클래스에서만 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;초기화 메서드(Initializer)의 재정의&lt;/b&gt;: Extension으로는 새로운 편의 초기화 메서드를 추가할 수 있지만, 기본 초기화 메서드 또는 지정 초기화 메서드를 재정의할 수는 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기존 타입의 저장된 프로퍼티에 기본값 설정&lt;/b&gt;: Extension에서는 기존 타입에 저장된 프로퍼티에 기본값을 설정할 수 없습니다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Grammary in Swift</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/224</guid>
      <comments>https://vesselwheel.tistory.com/224#entry224comment</comments>
      <pubDate>Fri, 22 Mar 2024 06:30:04 +0900</pubDate>
    </item>
    <item>
      <title>Swift Font 사용하기</title>
      <link>https://vesselwheel.tistory.com/221</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/fonts/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/fonts/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710849894460&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Fonts - Apple Developer&quot; data-og-description=&quot;Get the details, frameworks, and tools you need to use system fonts for Apple platforms in your apps.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/fonts/&quot; data-og-url=&quot;https://developer.apple.com/fonts/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nVk1l/hyVANEm7Sh/Rl4hde6kOvxia52b3aTt90/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cztO23/hyVDxmlDmQ/pJ398KI06Qc2Anq4spL681/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512,https://scrap.kakaocdn.net/dn/cyo9Vu/hyVABKGQNH/pZFR1ksNDmYWXxsuybQNu0/img.png?width=1480&amp;amp;height=832&amp;amp;face=0_0_1480_832&quot;&gt;&lt;a href=&quot;https://developer.apple.com/fonts/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/fonts/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nVk1l/hyVANEm7Sh/Rl4hde6kOvxia52b3aTt90/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cztO23/hyVDxmlDmQ/pJ398KI06Qc2Anq4spL681/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512,https://scrap.kakaocdn.net/dn/cyo9Vu/hyVABKGQNH/pZFR1ksNDmYWXxsuybQNu0/img.png?width=1480&amp;amp;height=832&amp;amp;face=0_0_1480_832');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Fonts - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Get the details, frameworks, and tools you need to use system fonts for Apple platforms in your apps.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apple에서 지원하는다양한 폰트가 맥북의 서체관리자(fontbook)에 내장되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-19 21.05.31.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LAdVG/btsFV411KoU/BGUjBloOnr9RYmEbYeJhmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LAdVG/btsFV411KoU/BGUjBloOnr9RYmEbYeJhmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LAdVG/btsFV411KoU/BGUjBloOnr9RYmEbYeJhmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLAdVG%2FbtsFV411KoU%2FBGUjBloOnr9RYmEbYeJhmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;944&quot; data-filename=&quot;스크린샷 2024-03-19 21.05.31.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xcode 내에서 맥북에 내장된 폰트를 사용하기 위해서는&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710850015092&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    lazy var timerCounterView: UILabel = {
        let label = UILabel()
        label.font = UIFont(name: &quot;Arial Rounded MT Bold&quot;, size: 200)
        label.textColor = .systemYellow
        label.textAlignment = .center
        label.isHidden = true
        return label
    }()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIFont의 name으로 폰트 이름을 적으면 손쉽게 적용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, San Francisco = SF 체는 WWDC2022에서 소개되었는데, 다양한 폰트를 제공한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2022/110381/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2022/110381/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710850383497&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Meet the expanded San Francisco font family - WWDC22 - Videos - Apple Developer&quot; data-og-description=&quot;Discover the latest additions to San Francisco &amp;ndash; the system font for Apple platforms &amp;ndash; and find out how they can provide more control and...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2022/110381/&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2022/110381/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cDed5c/hyVAOJ5pz3/Pxu9bjC7N1qMKL3a9uJrN1/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2022/110381/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2022/110381/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cDed5c/hyVAOJ5pz3/Pxu9bjC7N1qMKL3a9uJrN1/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Meet the expanded San Francisco font family - WWDC22 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Discover the latest additions to San Francisco &amp;ndash; the system font for Apple platforms &amp;ndash; and find out how they can provide more control and...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-19 20.39.59.png&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dL1kl1/btsFU7dEUrP/Cw0xDvnrF1rkKnPjAks6D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dL1kl1/btsFU7dEUrP/Cw0xDvnrF1rkKnPjAks6D1/img.png&quot; data-alt=&quot;&amp;quot;Arial Rounded MT Bold&amp;quot;를 적용한 숫자 카운트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dL1kl1/btsFU7dEUrP/Cw0xDvnrF1rkKnPjAks6D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdL1kl1%2FbtsFU7dEUrP%2FCw0xDvnrF1rkKnPjAks6D1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;739&quot; data-filename=&quot;스크린샷 2024-03-19 20.39.59.png&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;739&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&quot;Arial Rounded MT Bold&quot;를 적용한 숫자 카운트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Xcode Study</category>
      <category>Font</category>
      <category>font book</category>
      <category>Xcode</category>
      <category>서체관리자</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/221</guid>
      <comments>https://vesselwheel.tistory.com/221#entry221comment</comments>
      <pubDate>Tue, 19 Mar 2024 21:14:37 +0900</pubDate>
    </item>
    <item>
      <title>옵셔널(Optional)에 대해서 설명해주세요.</title>
      <link>https://vesselwheel.tistory.com/220</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;옵셔널은 값이 없을 수 있는 상황에서 사용됩니다.&amp;nbsp; ?를 활용해서 옵셔널로 래핑하고 !로 언래핑합니다.&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵셔널 값이 빈값인지 검사하기 위해서 옵셔널 바인딩을 사용하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if let, if var, guard let, guard var를 사용해서 옵셔널값을 추출해 새로운 변수에 바인딩합니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵셔널 값이 nil 아닌 경우가 확실할 경우, 옵셔널값 뒤에 !를 써서 강제로 옵셔널을 추출합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강제언래핑 대안으로 옵셔널 체이닝을 활용하여 옵셔널인 프로퍼티, 매서드, 그리고 서브스크립트를 조회하고 호출할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강제언래핑과 옵셔널 체이닝의 차이점은 옵셔널값이 nil일 때, 옵셔널 체이닝은 실패하는 반면, 강제 언래핑은 런타입 에러가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/optional-chaining&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://bbiguduk.gitbook.io/swift/language-guide-1/optional-chaining&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710809256071&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;옵셔널 체이닝 (Optional Chaining) | Swift&quot; data-og-description=&quot;언래핑 없이 옵셔널 값의 멤버에 접근합니다. 옵셔널 체이닝 (Optional chaining) 은 현재 nil 일 수 있는 옵셔널 인 프로퍼티, 메서드, 그리고 서브 스크립트를 조회하고 호출하기 위한 프로세스 입니&quot; data-og-host=&quot;bbiguduk.gitbook.io&quot; data-og-source-url=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/optional-chaining&quot; data-og-url=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/optional-chaining&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/optional-chaining&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/optional-chaining&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;옵셔널 체이닝 (Optional Chaining) | Swift&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;언래핑 없이 옵셔널 값의 멤버에 접근합니다. 옵셔널 체이닝 (Optional chaining) 은 현재 nil 일 수 있는 옵셔널 인 프로퍼티, 메서드, 그리고 서브 스크립트를 조회하고 호출하기 위한 프로세스 입니&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bbiguduk.gitbook.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Grammary in Swift</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/220</guid>
      <comments>https://vesselwheel.tistory.com/220#entry220comment</comments>
      <pubDate>Tue, 19 Mar 2024 09:46:18 +0900</pubDate>
    </item>
    <item>
      <title>[Trouble Shooting] 러닝레코드 프로퍼티 무한값일 때 충돌</title>
      <link>https://vesselwheel.tistory.com/219</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 움직이지 않거나, 실내일 때에는 거리값이 증가하지 않고, 따라서 페이스 또한 증가하지 않아 Nil인 상태이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해, 코어데이터에서 러닝기록을 호출하는 프로필뷰를 탭했을 때, 오류로 인해 기기에서는 앱이 멈추거나 자동종료 되는 문제가 발생했다.&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/445368612&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/oqZxK/hyVALzrjP8/ZwmvfIH9bn1U0eLarjEuJ1/img.jpg?width=720&amp;amp;height=1556&amp;amp;face=0_0_720_1556,https://scrap.kakaocdn.net/dn/sklin/hyVACbsEsb/ku5M6arcofK1BhDbbfoqd1/img.jpg?width=720&amp;amp;height=1556&amp;amp;face=0_0_720_1556&quot; data-video-width=&quot;740&quot; data-video-height=&quot;1599&quot; data-video-origin-width=&quot;720&quot; data-video-origin-height=&quot;1556&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/445368612?service=daum_tistory&quot; width=&quot;740&quot; height=&quot;1599&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- runningRecord.pace 값은 NaN 상태로 값이 없다. 상기 상황에서 정지버튼을 누르면 runningRecord.pace는 NaN 상태로 코어데이터에도 저장된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- runningRecord.pace 값이 NaN으로 null 상태일 때, Int값으로 형변환을 할 수 없음.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-18 15.38.18.png&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;927&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XIh8Y/btsFP7eUOQu/j9zskl35kN7yeROTwkp4Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XIh8Y/btsFP7eUOQu/j9zskl35kN7yeROTwkp4Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XIh8Y/btsFP7eUOQu/j9zskl35kN7yeROTwkp4Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXIh8Y%2FbtsFP7eUOQu%2Fj9zskl35kN7yeROTwkp4Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1901&quot; height=&quot;927&quot; data-filename=&quot;스크린샷 2024-03-18 15.38.18.png&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;927&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 xcode 무한값 유한값을 검색했다. 한글로 검색하니 상관있는 검색결과가 나오지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어로 무한값 infinite, 유한값 finite를 검색하니 공식홈페이지에 관련자료가 나온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/double/isfinite&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/swift/double/isfinite&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710746656790&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;isFinite | Apple Developer Documentation&quot; data-og-description=&quot;A Boolean value indicating whether this instance is finite.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/swift/double/isfinite&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/swift/double/isfinite&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bs7ljV/hyVAD2uAfP/CjLcgp1D8uNpTZ9WtiJM50/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bFACO9/hyVAMrBxGd/PKBoK8vd2eR7Zb7EMUeY0K/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/double/isfinite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/swift/double/isfinite&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bs7ljV/hyVAD2uAfP/CjLcgp1D8uNpTZ9WtiJM50/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bFACO9/hyVAMrBxGd/PKBoK8vd2eR7Zb7EMUeY0K/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;isFinite | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Boolean value indicating whether this instance is finite.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-18 16.08.56.png&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLXhDI/btsFUJiGo3g/KFz1TTLINWd59wjCX1lBTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLXhDI/btsFUJiGo3g/KFz1TTLINWd59wjCX1lBTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLXhDI/btsFUJiGo3g/KFz1TTLINWd59wjCX1lBTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLXhDI%2FbtsFUJiGo3g%2FKFz1TTLINWd59wjCX1lBTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;816&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2024-03-18 16.08.56.png&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;runningRecord.pace&lt;span&gt; 값이 유한값인지 확인되면, &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;페이스의 분과 초는 속력 대비 시간이기 때문에 runningRecord.pace를 60(분, 초)으로 나누거나 나머지를 구해야하고, 정수로 전환해야한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-18 16.29.07.png&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxGGUT/btsFP9qhdmD/hHKvukoLZ1pv0hNCLqMU20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxGGUT/btsFP9qhdmD/hHKvukoLZ1pv0hNCLqMU20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxGGUT/btsFP9qhdmD/hHKvukoLZ1pv0hNCLqMU20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxGGUT%2FbtsFP9qhdmD%2FhHKvukoLZ1pv0hNCLqMU20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;909&quot; height=&quot;492&quot; data-filename=&quot;스크린샷 2024-03-18 16.29.07.png&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 Int값으로 형변환하기 보다, &amp;nbsp;소수점 아래의 수는 Int값으로는 담을 수 없다. 따라서 round 함수로 반올림해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-18 16.29.57.png&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brVojf/btsFT0ecpKH/iEKjmYFUu7Ax4uwr2Zefn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brVojf/btsFT0ecpKH/iEKjmYFUu7Ax4uwr2Zefn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brVojf/btsFT0ecpKH/iEKjmYFUu7Ax4uwr2Zefn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrVojf%2FbtsFT0ecpKH%2FiEKjmYFUu7Ax4uwr2Zefn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;974&quot; height=&quot;808&quot; data-filename=&quot;스크린샷 2024-03-18 16.29.57.png&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 예외처리로, 유한값이 아닐때 는, N/A로 사용자에게 텍스트로 보여준다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710747182442&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;} else {
self.paceText = &quot;N/A&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 값을 확인하고자할 때 사용되는 매소드로 아래와 같이 있으니, 비슷한 상황이 발생할 수 있을 때 사용할 수 있겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-18 16.26.49.png&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;747&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7d21w/btsFUDJyIuu/4XeJjWUc9Z6lIvW2NQihu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7d21w/btsFUDJyIuu/4XeJjWUc9Z6lIvW2NQihu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7d21w/btsFUDJyIuu/4XeJjWUc9Z6lIvW2NQihu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7d21w%2FbtsFUDJyIuu%2F4XeJjWUc9Z6lIvW2NQihu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;827&quot; height=&quot;747&quot; data-filename=&quot;스크린샷 2024-03-18 16.26.49.png&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;747&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Xcode Study</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/219</guid>
      <comments>https://vesselwheel.tistory.com/219#entry219comment</comments>
      <pubDate>Mon, 18 Mar 2024 16:33:23 +0900</pubDate>
    </item>
    <item>
      <title>클래스와 프로토콜</title>
      <link>https://vesselwheel.tistory.com/218</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;클래스란?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 코드의 구성요소가 되는 범용의 유연한 구조로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조체와 달리 참조타입으로 같은 인스턴스를 사용하며, 값을 저장하는 프로퍼티와 기능을 제공하는 메서드로 구성되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스의 프로퍼티에 기본값이 없는 경우, 초기화를 해주어야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 클래스 정의&lt;/p&gt;
&lt;pre id=&quot;code_1710721822826&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 인스턴스 생성&lt;/p&gt;
&lt;pre id=&quot;code_1710721888820&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let someVideoMode = VideoMode()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 프로퍼티에 접근하기 위해서는 점 구문을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710721937400&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;someVideoMode.resolution.width = 1280&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 즉, 동일한 인스턴스를 활용하거나 동일한 기능을 활용할 때 클래스가 사용된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710722041138&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = &quot;1080i&quot;
tenEighty.frameRate = 25.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. weatherKit이나 healthKit 처럼 apple에서 제공하는 Kit들은 파이널 클래스로 정의하였기 때문에, 해당 Kit에 포함된 인스턴스나 매서드를 사용할 수 있는 이유이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/structures-and-classes&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://bbiguduk.gitbook.io/swift/language-guide-1/structures-and-classes&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710721731683&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;구조체와 클래스 (Structures and Classes) | Swift&quot; data-og-description=&quot;데이터를 캡슐화하는 사용자 정의 타입을 모델링합니다. 구조체 (Structures) 와 클래스 (classes) 는 프로그램 코드의 구성 요소가 되는 범용의 유연한 구조입니다. 상수, 변수, 그리고 함수를 정의하&quot; data-og-host=&quot;bbiguduk.gitbook.io&quot; data-og-source-url=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/structures-and-classes&quot; data-og-url=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/structures-and-classes&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d0HqWG/hyVAJapIOU/LIaGI8jBPH8VRnIE0N32RK/img.png?width=1294&amp;amp;height=460&amp;amp;face=0_0_1294_460&quot;&gt;&lt;a href=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/structures-and-classes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/structures-and-classes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d0HqWG/hyVAJapIOU/LIaGI8jBPH8VRnIE0N32RK/img.png?width=1294&amp;amp;height=460&amp;amp;face=0_0_1294_460');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;구조체와 클래스 (Structures and Classes) | Swift&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 캡슐화하는 사용자 정의 타입을 모델링합니다. 구조체 (Structures) 와 클래스 (classes) 는 프로그램 코드의 구성 요소가 되는 범용의 유연한 구조입니다. 상수, 변수, 그리고 함수를 정의하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bbiguduk.gitbook.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜이란?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준수해야하는 타입이 구현해야하는 요구사항을 정의한 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 언급했던, 클래스, 구조체를 포함하여, 그리고 열거형은 프로토콜로 정의된 규약을 따라야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 프로토콜 정의&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710722314764&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol SomeProtocol {
    // protocol definition goes here
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 프로토콜 채택&lt;/p&gt;
&lt;pre id=&quot;code_1710722358380&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct SomeStructure: SomeProtocol, AnotherProtocol {
    // structure definition goes here
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 프로토콜의 사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 델리게이트 패턴을 사용해서 데이터를 주고 받을 때, 프로토콜에서 정의한 매소드를 활용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 전송하는 곳에서 프로토콜과 위임자을 선언하고&lt;/p&gt;
&lt;pre id=&quot;code_1710807702379&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol PauseRunningHalfModalViewControllerDelegate: AnyObject {
    func didDismissPauseRunningHalfModalViewController()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710807776663&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;weak var delegate: PauseRunningHalfModalViewControllerDelegate?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 전송받고자 하는 곳에서 프로토콜을 채택한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710807857218&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class RunningTimerViewController: UIViewController, PauseRunningHalfModalViewControllerDelegate {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 전송받고자 하는 곳에서는 프로토콜에서 선언된 매소드나 프로퍼티를 따라야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1710807907663&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func didDismissPauseRunningHalfModalViewController() {
        runningTimer.restart()
        
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/protocols&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://bbiguduk.gitbook.io/swift/language-guide-1/protocols&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710807553860&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로토콜 (Protocols) | Swift&quot; data-og-description=&quot;준수하는 타입이 구현해야 하는 요구사항을 정의합니다. 프로토콜 (protocol) 은 메서드, 프로퍼티, 그리고 특정 작업이나 기능의 부분이 적합한 다른 요구사항의 청사진을 정의합니다. 프로토콜&quot; data-og-host=&quot;bbiguduk.gitbook.io&quot; data-og-source-url=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/protocols&quot; data-og-url=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/protocols&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/protocols&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bbiguduk.gitbook.io/swift/language-guide-1/protocols&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로토콜 (Protocols) | Swift&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;준수하는 타입이 구현해야 하는 요구사항을 정의합니다. 프로토콜 (protocol) 은 메서드, 프로퍼티, 그리고 특정 작업이나 기능의 부분이 적합한 다른 요구사항의 청사진을 정의합니다. 프로토콜&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bbiguduk.gitbook.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Grammary in Swift</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/218</guid>
      <comments>https://vesselwheel.tistory.com/218#entry218comment</comments>
      <pubDate>Mon, 18 Mar 2024 09:35:30 +0900</pubDate>
    </item>
    <item>
      <title>[Trouble Shooting] 러닝맵 업체 정보 버튼 확장 간 RequestQuery 제한(feat. cache)</title>
      <link>https://vesselwheel.tistory.com/217</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;오늘도 트러블 메이커, query 호출제한과 완료되지 않은 쓰레드의 표시 접근 제한&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-16 10.01.08.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BjZBh/btsFPe5VDAH/aVKubkbnPJq5ECVkTrggXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BjZBh/btsFPe5VDAH/aVKubkbnPJq5ECVkTrggXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BjZBh/btsFPe5VDAH/aVKubkbnPJq5ECVkTrggXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBjZBh%2FbtsFPe5VDAH%2FaVKubkbnPJq5ECVkTrggXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1204&quot; height=&quot;472&quot; data-filename=&quot;스크린샷 2024-03-16 10.01.08.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 통해서, query와 category로 annotation과 업체 정보를 호출하고자 했다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1710550969891&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func getAnnotations(forQuery query: String, category: String) {
        // 모든 어노테이션 제거
        let allAnnotations = self.mapView.annotations
        self.mapView.removeAnnotations(allAnnotations)
        
        guard let currentLocation = self.mapView.userLocation.location else {
            print(&quot;Failed to get user location&quot;)
            return
        }
        
        let searchRequest = MKLocalSearch.Request()
        searchRequest.naturalLanguageQuery = query
        searchRequest.region = MKCoordinateRegion(center: currentLocation.coordinate, latitudinalMeters: 500, longitudinalMeters: 500)
        
        let search = MKLocalSearch(request: searchRequest)
        search.start { [weak self] (response, error) in
            guard let response = response else {
                print(&quot;Search error: \(error?.localizedDescription ?? &quot;Unknown error&quot;)&quot;)
                return
            }
            
            search.start { [weak self] (response, error) in
                guard let response = response else {
                    print(&quot;Search error: \(error?.localizedDescription ?? &quot;Unknown error&quot;)&quot;)
                    return
                }
                
                for item in response.mapItems {
                    let annotation = CustomAnnotation()
                    annotation.coordinate = item.placemark.coordinate
                    annotation.title = item.name
                    annotation.mapItem = item
                    annotation.category = category
                    DispatchQueue.main.async {
                        self?.mapView.addAnnotation(annotation)
                    }
                }
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원께서 개선 해준 getAnnotations()매소드,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색하고자 하는 자연어(naturalLanguageQuery)를 query 객체화해서 파라미터로 받고, catergoy 또한 파라미터로 객체화했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 편의점 업체 정보가 GS25 만 있기때문에, 사용자가 사용하기에 정보가 제한적이라 판단되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 추가적인 업체를 함께 검색하게금하면 어떠할까하는 생각에 업체 정보를 추가하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-16 10.06.20.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ujLCj/btsFPb9im5n/7cO3jQvkX9yH9XqgYMsv30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ujLCj/btsFPb9im5n/7cO3jQvkX9yH9XqgYMsv30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ujLCj/btsFPb9im5n/7cO3jQvkX9yH9XqgYMsv30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FujLCj%2FbtsFPb9im5n%2F7cO3jQvkX9yH9XqgYMsv30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1290&quot; height=&quot;566&quot; data-filename=&quot;스크린샷 2024-03-16 10.06.20.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Simulator Screenshot - iPhone 15 Pro - 2024-03-16 at 10.07.59.png&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbZIAA/btsFOTAYNp3/qaw1kHXKiolpxv0QJRUft0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbZIAA/btsFOTAYNp3/qaw1kHXKiolpxv0QJRUft0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbZIAA/btsFOTAYNp3/qaw1kHXKiolpxv0QJRUft0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbZIAA%2FbtsFOTAYNp3%2Fqaw1kHXKiolpxv0QJRUft0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;683&quot; data-filename=&quot;Simulator Screenshot - iPhone 15 Pro - 2024-03-16 at 10.07.59.png&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그랬더니, 업체정보는 어노테이션에 잘 나온다. 하지만 업체 정보를 노출하기 위해 업체정보가 나오지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-16 10.10.00.png&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;1488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eGlAZN/btsFQxQZ04z/C0dd1uLl1GkMy47hpqpfp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eGlAZN/btsFQxQZ04z/C0dd1uLl1GkMy47hpqpfp0/img.png&quot; data-alt=&quot;Attempt to present~로 시작하는 부분&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eGlAZN/btsFQxQZ04z/C0dd1uLl1GkMy47hpqpfp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeGlAZN%2FbtsFQxQZ04z%2FC0dd1uLl1GkMy47hpqpfp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2224&quot; height=&quot;1488&quot; data-filename=&quot;스크린샷 2024-03-16 10.10.00.png&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;1488&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Attempt to present~로 시작하는 부분&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;Attempt to present &amp;lt;Run_It.StoreViewController: 0x104584910&amp;gt; on &amp;lt;Run_It.MainTabBarViewController: 0x129820600&amp;gt; (from &amp;lt;Run_It.RunningMapViewController: 0x10780ec00&amp;gt;) while a presentation is in progress.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 문제는 모달뷰에 업체정보를 표시하기 전에 모댤뷰가 러닝맵뷰에 호출되려고 하니, 완료되지 않은 쓰레드가 사용되어 뷰전환이 거부되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 query와 category가 다른 3종류의 버튼을 연달아 탭했더니 아래와 같은 메세지가 콘솔창에 나왔다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710551635914&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Throttled &quot;PlaceRequest.REQUEST_TYPE_SEARCH&quot; request: Tried to make more than 50 requests in 60 seconds, will reset in 11 seconds - Error Domain=GEOErrorDomain Code=-3 &quot;(null)&quot; UserInfo={details=(
    {
    intervalType = short;
    maxRequests = 50;
    &quot;throttler.keyPath&quot; = &quot;app:com.team5.Run-It/0x20301/short(default/any)&quot;;
    timeUntilReset = 11;
    windowSize = 60;
}
), requestKindString=PlaceRequest.REQUEST_TYPE_SEARCH, timeUntilReset=11, requestKind=769}
Search error: The operation couldn&amp;rsquo;t be completed. (MKErrorDomain error 3.)
Throttled &quot;PlaceRequest.REQUEST_TYPE_SEARCH&quot; request: Tried to make more than 50 requests in 60 seconds, will reset in 11 seconds - Error Domain=GEOErrorDomain Code=-3 &quot;(null)&quot; UserInfo={details=(
{
intervalType = short;
maxRequests = 50;
&quot;throttler.keyPath&quot; = &quot;app:com.team5.Run-It/0x20301/short(default/any)&quot;;
timeUntilReset = 11;
windowSize = 60;
}
), requestKindString=PlaceRequest.REQUEST_TYPE_SEARCH, timeUntilReset=11, requestKind=769}
Error: The operation couldn&amp;rsquo;t be completed. (MKErrorDomain error 3.).&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련된 내용을 검색해보니,&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #3b3f4e; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;해당 콘솔 메시지는 API 요청 제한으로 인한 오류 메시지입니다. 여기서 발생한 문제는 여러분이 사용하고 있는 지도 서비스의 API가 일정 시간 동안에 허용하는 요청 횟수를 초과했다는 것을 의미합니다. 구체적으로, &quot;Tried to make more than 50 requests in 60 seconds&quot;라는 부분에서 볼 수 있듯이, 여러분의 애플리케이션이 60초 동안 50번 이상의 요청을 시도했고, 이로 인해 일시적으로 요청이 제한되었다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #3b3f4e; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;해결 방법으로는 다음과 같은 몇 가지 방법이 있습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #3b3f4e; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;요청 횟수 최적화&lt;/b&gt;: 필요한 정보만을 요청하도록 알고리즘을 조정하여 요청 횟수를 줄이거나, 한 번의 요청으로 여러 데이터를 받아올 수 있는 방법을 모색해보세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지연 시간 추가&lt;/b&gt;: 각 요청 사이에 일정 시간 동안 지연시간을 두어 API 요청 횟수를 조절하세요. 예를 들어, 각 요청 후 1~2초의 지연 시간을 추가할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐싱 사용&lt;/b&gt;: 이전에 요청했던 데이터는 로컬에 캐싱하여 같은 정보를 다시 요청할 필요가 없도록 합니다. 이 방법은 특히 자주 사용되거나 변하지 않는 데이터에 효과적입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;API 요청 제한 확장&lt;/b&gt;: 사용 중인 지도 서비스의 API 요청 제한이 너무 낮다고 판단된다면, 서비스 제공업체에 요청하여 요청 제한을 늘려달라고 요청하거나, 유료 플랜으로 전환하는 것을 고려해볼 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 요청 횟수 최적화는 검색되는 업체 정보를 줄이거나, 지역을 한정하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 지연 시간 추가는 한 업체의 query 검색이 완료될 때까지 지연시간을 적용하고 완료 후에 다음 검색을 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 캐시 사용 : 스택에 저장되는 배열을 활용하여 검색이 완료되면 배열에 넣어준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 카카오나, 네이버와 같이 검색 횟수에 따라 유료로 전환되어 API 요청 제한이 있다. Mapkit의 경우 추가적인 요금은 발생하지 않지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1초 내에 검색가능 횟수가 50회로 제한되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 시도한 방법은 캐시를 활용한 배열에 저장 후 재호출하고, 지연시간을 주어 검색과 호출이 완료되면 다음 query를 실행하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 우선, 캐싱할 배열을 생성하고, 요청 완료 여부를 설정한 뒤에, 진행되는 동안 사용자에게 진행중 인디케이터를 표시하려고 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710553195808&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum PresentView {
    case inProgress
    case completed
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1710553122071&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var searchResultsCache: [String: [MKMapItem]] = [:]
    
var presentationState = PresentView.completed
    
var loadingIndicator: UIActivityIndicatorView?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1710553281932&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func getAnnotations(forQuery query: String, category: String) {
        if let cachedResults = searchResultsCache[query] {
            // 캐시된 결과가 있으면 해당 결과를 사용하여 어노테이션을 추가
            addAnnotationsToMap(mapItems: cachedResults, category: category)
            return
        }
        
        // 캐시에 저장된 결과가 없으면 네트워크 요청을 시작
        guard let currentLocation = self.mapView.userLocation.location else {
            print(&quot;Failed to get user location&quot;)
            return
        }
        
        // 모든 어노테이션 제거
        let allAnnotations = self.mapView.annotations
        self.mapView.removeAnnotations(allAnnotations)
        
        guard let currentLocation = self.mapView.userLocation.location else {
            print(&quot;Failed to get user location&quot;)
            return
        }
        
        let searchRequest = MKLocalSearch.Request()
        searchRequest.naturalLanguageQuery = query
        searchRequest.region = MKCoordinateRegion(center: currentLocation.coordinate, latitudinalMeters: 500, longitudinalMeters: 500)
        
        let search = MKLocalSearch(request: searchRequest)
        search.start { [weak self] (response, error) in
            guard let response = response else {
                print(&quot;Search error: \(error?.localizedDescription ?? &quot;Unknown error&quot;)&quot;)
                return
            }
            
            search.start { [weak self] (response, error) in
                guard let response = response else {
                    print(&quot;Search error: \(error?.localizedDescription ?? &quot;Unknown error&quot;)&quot;)
                    return
                }
                
                // 50미터 이내의 결과만 필터링
                let filteredMapItems = response.mapItems.filter { mapItem in
                    let distance = currentLocation.distance(from: mapItem.placemark.location!)
                    return distance &amp;lt;= 25
                }
                
                self?.searchResultsCache[query] = response.mapItems
                
                // 캐시된 결과를 사용하여 어노테이션을 추가
                self?.addAnnotationsToMap(mapItems: response.mapItems, category: category)
            }
        }
    }
    
    private func addAnnotationsToMap(mapItems: [MKMapItem], category: String) {
        for item in mapItems {
            let annotation = CustomAnnotation()
            annotation.coordinate = item.placemark.coordinate
            annotation.title = item.name
            annotation.mapItem = item
            annotation.category = category
            DispatchQueue.main.async {
                self.mapView.addAnnotation(annotation)
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1710554351990&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private func removeAnnotationsFromMap() {
        // mapView.annotations 배열에서 MKUserLocation 인스턴스를 제외하고 모두 제거
        let annotationsToRemove = mapView.annotations.filter { $0 !== mapView.userLocation }
        mapView.removeAnnotations(annotationsToRemove)
        
        // mapView.overlays 배열에서 currentCircle를 제외하고 모두 제거
//        let overlaysToRemove = mapView.overlays.filter { $0 !== currentCircle }
//        mapView.removeOverlays(overlaysToRemove)
    }
    
    func calculateDistance(to location: CLLocation) -&amp;gt; Int {
        let userLocation = CLLocation(latitude: mapView.userLocation.coordinate.latitude, longitude: mapView.userLocation.coordinate.longitude)
        let distanceInMeters = userLocation.distance(from: location)
        return Int(distanceInMeters)
    }
    
    func isStoreFavorite(name: String, latitude: Double, longitude: Double) -&amp;gt; Bool {
        let viewModel = FavoritesViewModel()
        return viewModel.isFavorite(storeName: name, latitude: latitude, longitude: longitude)
    }
    
    
    func searchAndPresentStores(with query: String, location: CLLocation) {
        let searchRequest = MKLocalSearch.Request()
        searchRequest.naturalLanguageQuery = query
        // 사용자의 현재 위치를 중심으로 하는 지역 설정
        let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 500, longitudinalMeters: 500)
        searchRequest.region = region
        
        let search = MKLocalSearch(request: searchRequest)
        search.start { (response, error) in
            guard let response = response else {
                print(&quot;Error: \(error?.localizedDescription ?? &quot;Unknown error&quot;).&quot;)
                return
            }
            
            var places: [AnnotationInfo] = []
            for item in response.mapItems {
                // 필요한 데이터를 places 배열에 추가
                let place = AnnotationInfo(
                    name: item.name ?? &quot;Unknown&quot;,
                    category: query, // 카테고리를 검색어로 설정
                    address: item.placemark.title ?? &quot;No address&quot;,
                    url: item.url?.absoluteString ?? &quot;No URL&quot;,
                    latitude: item.placemark.coordinate.latitude,
                    longitude: item.placemark.coordinate.longitude,
                    isOpenNow: false, // 이 값을 설정하기 위한 로직이 필요
                    distance: self.calculateDistance(to: CLLocation(latitude: item.placemark.coordinate.latitude, longitude: item.placemark.coordinate.longitude)),
                    isFavorite: self.isStoreFavorite(name: item.name ?? &quot;&quot;, latitude: item.placemark.coordinate.latitude, longitude: item.placemark.coordinate.longitude)
                )
                
                places.append(place)
            }
            
            DispatchQueue.main.async {
                self.presentStoreViewController(with: places)
            }
        }
    }
    
    private func presentStoreViewController(with places: [AnnotationInfo]) {
        // 모달을 표시하기 전 상태 확인
        if presentationState == .completed {
            // 상태를 inProgress로 변경
            presentationState = .inProgress
            loadingIndicator?.startAnimating()
            
            // 기존에 표시된 모달이 있다면 닫기
            if let currentModal = self.presentedViewController {
                currentModal.dismiss(animated: true) {
                    self.showStoreVC(with: places)
                }
            } else {
                showStoreVC(with: places)
            }
        } else {
            // 프레젠테이션 상태가 inProgress인 경우, 완료될 때까지 기다렸다가 모달 표시
            DispatchQueue.global(qos: .background).async {
                while self.presentationState != .completed {
                    // 상태가 completed로 변경될 때까지 대기
                    Thread.sleep(forTimeInterval: 0.1) // CPU를 과도하게 사용하지 않도록 적당한 대기 시간 설정
                }
                DispatchQueue.main.async {
                    self.presentStoreViewController(with: places)
                }
            }
        }
        loadingIndicator?.stopAnimating()
    }
    
    private func showStoreVC(with places: [AnnotationInfo]) {
        let storeVC = StoreViewController()
//        storeVC.delegate = self
        storeVC.stores = places // 데이터 전달
        storeVC.modalPresentationStyle = .formSheet
        storeVC.modalTransitionStyle = .coverVertical
        
        // 모달을 표시하기 전에 sheetPresentationController 설정을 추가
        if let sheet = storeVC.presentationController as? UISheetPresentationController {
            let customDetentIdentifier = UISheetPresentationController.Detent.Identifier(&quot;customBottomBarHeight&quot;)
            let customDetent = UISheetPresentationController.Detent.custom(identifier: customDetentIdentifier) { _ in
                return 250
            }
            
            sheet.detents = [customDetent] // 모달의 높이 설정
            sheet.prefersGrabberVisible = true
            sheet.largestUndimmedDetentIdentifier = customDetentIdentifier // 음영 처리 없이 표시
            sheet.prefersScrollingExpandsWhenScrolledToEdge = false // 모달 내부 스크롤 시 확장되지 않도록 설정
            sheet.prefersEdgeAttachedInCompactHeight = true // 컴팩트 높이에서 화면 가장자리에 붙도록 설정
            sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true // 너비가 preferredContentSize를 따르도록 설정
        }
        
        self.present(storeVC, animated: true) {
            // 프레젠테이션이 완료되면 상태를 completed로 변경
            self.presentationState = .completed
        }
    }
    
    private func setupLoadingIndicator() {
        loadingIndicator = UIActivityIndicatorView(style: .large)
        loadingIndicator?.center = self.view.center
        loadingIndicator?.hidesWhenStopped = true
        if let indicator = loadingIndicator {
            self.view.addSubview(indicator)
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1710554074318&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @objc func presentConvenienceStoreAnnotations() {
        let convenienceStores = [&quot;GS25&quot;, &quot;CU&quot;, &quot;세븐일레븐&quot;, &quot;이마트24&quot;, &quot;미니스톱&quot;]
        let category = &quot;편의점&quot;
        
        // currentLocation이 nil인 경우를 처리
        guard let currentLocation = currentLocation else {
            print(&quot;현재 위치 정보가 없습니다.&quot;)
            return
        }
        
        for convenienceStore in convenienceStores {
            getAnnotations(forQuery: convenienceStore, category: category)
            searchAndPresentStores(with: convenienceStore, location : currentLocation)
        }
    }
    // 유저에게 편의전 옵션을 주고, 편의점 옵션을 선택해서 하프모달로 노출

    @objc func presentcoffeeAndBakeryFranchisesAnnotations() {
        
        let coffeeAndBakeryFranchises = [&quot;Cafe&quot;, &quot;coffee&quot;, &quot;투썸플레이스&quot;, &quot;컴포즈커피&quot;,
                                         &quot;스타벅스&quot;, &quot;파리바게뜨&quot;, &quot;뚜레쥬르&quot;, &quot;할리스커피&quot;,
                                         &quot;이디야커피&quot;, &quot;메가커피&quot;, &quot;브레드톡&quot;]
        let category = &quot;카페/베이커리&quot;
        
        // currentLocation이 nil인 경우를 처리
        guard let currentLocation = currentLocation else {
            print(&quot;현재 위치 정보가 없습니다.&quot;)
            return
        }
        
        for coffeeAndBakeryFranchise in coffeeAndBakeryFranchises {
            getAnnotations(forQuery: coffeeAndBakeryFranchise, category: category)
            searchAndPresentStores(with: coffeeAndBakeryFranchise, location : currentLocation)
        }
        
    }
    
    @objc func presenthealthyEatingOptionsAnnotations() {
        
        let healthyEatingOptions = [&quot;샐러디&quot;, &quot;subway&quot;]
        let category = &quot;건강식&quot;
        
        // currentLocation이 nil인 경우를 처리
        guard let currentLocation = currentLocation else {
            print(&quot;현재 위치 정보가 없습니다.&quot;)
            return
        }
        for healthyEatingOption in healthyEatingOptions {
            getAnnotations(forQuery: healthyEatingOption, category: category)
            DispatchQueue.main.async {
                self.searchAndPresentStores(with: healthyEatingOption, location : currentLocation)
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Xcode Study</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/217</guid>
      <comments>https://vesselwheel.tistory.com/217#entry217comment</comments>
      <pubDate>Sat, 16 Mar 2024 10:17:11 +0900</pubDate>
    </item>
    <item>
      <title>[Trobule Shooting] 러닝타이머 거리값이 pause 버튼을 누렀다가, 다시 재개를 누르면 계속 기록되지 않고 멈춤</title>
      <link>https://vesselwheel.tistory.com/216</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;현재 문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/445294589&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cWCSEQ/hyVyhlaF17/ZOfs15weLPBUyEzQUZyRmK/img.jpg?width=350&amp;amp;height=732&amp;amp;face=0_0_350_732,https://scrap.kakaocdn.net/dn/Glhfl/hyVyqbmXAm/yCv2U8uAo8f7yKLBZD5KC1/img.jpg?width=350&amp;amp;height=732&amp;amp;face=0_0_350_732&quot; data-video-width=&quot;350&quot; data-video-height=&quot;732&quot; data-video-origin-width=&quot;350&quot; data-video-origin-height=&quot;732&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;'VesselWheel'에서 업로드한 동영상&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/445294589?service=daum_tistory&quot; width=&quot;350&quot; height=&quot;732&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 러닝타이머 거리값이 pause 버튼을 누렀다가, 다시 재개를 누르면 계속 기록되지 않고 멈춤&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 버튼의 target 매소드인 restartRunning()에는 시간값을 책임하는 restart() 매소드가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-15 10.31.46.png&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXHiFj/btsFO1qolig/W9GEBEObZOBtUTKPgs2QB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXHiFj/btsFO1qolig/W9GEBEObZOBtUTKPgs2QB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXHiFj/btsFO1qolig/W9GEBEObZOBtUTKPgs2QB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXHiFj%2FbtsFO1qolig%2FW9GEBEObZOBtUTKPgs2QB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;232&quot; data-filename=&quot;스크린샷 2024-03-15 10.31.46.png&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상기 코드에서 보면&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710466751678&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        self.runningTimer.restart()
        self.dismiss(animated: true) {
            self.delegate?.didDismissPauseRunningHalfModalViewController()
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runningTimer 클래스를 델리게이트 패턴 선언하고 PauseRunningHalfModalViewController에서 객체화하고 나서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;restart()매소드를 호출하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710466867157&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class PauseRunningHalfModalViewController: UIViewController {
    
    let runningTimer = RunningTimer()
    
    weak var delegate: PauseRunningHalfModalViewControllerDelegate?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나, runningTimer&lt;span style=&quot;color: #000000;&quot;&gt;.&lt;/span&gt;restart&lt;span style=&quot;color: #000000;&quot;&gt;() 는 싱글톤 패턴으로 restart&lt;span style=&quot;color: #000000;&quot;&gt;() 매소드를 &lt;/span&gt;호출하고 있고,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마찬가지로, &lt;span style=&quot;color: #fc5fa3;&quot;&gt;&lt;b&gt;self&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.&lt;/span&gt;delegate&lt;span style=&quot;color: #000000;&quot;&gt;?.&lt;/span&gt;didDismissPauseRunningHalfModalViewController&lt;span style=&quot;color: #000000;&quot;&gt;()도 델리게이트 패턴으로 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;restart&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;() 매소드를 호출하고 있어 중복되는 코드이며, 거리값은 재개를 하지 않는 문제점이 발생했다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710466959218&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol PauseRunningHalfModalViewControllerDelegate: AnyObject {
    func didDismissPauseRunningHalfModalViewController()
}

import UIKit
import CoreLocation

class PauseRunningHalfModalViewController: UIViewController {&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1710466993170&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func didDismissPauseRunningHalfModalViewController() {
        runningTimer.restart()
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Xcode Study</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/216</guid>
      <comments>https://vesselwheel.tistory.com/216#entry216comment</comments>
      <pubDate>Fri, 15 Mar 2024 10:43:48 +0900</pubDate>
    </item>
    <item>
      <title>구조체(Struct)의 mutating 키워드에 대해서 설명해주세요.</title>
      <link>https://vesselwheel.tistory.com/215</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://skytitan.tistory.com/551&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://skytitan.tistory.com/551&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710463408927&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Swift] 왜 struct에선 mutating을 사용해야하는가?&quot; data-og-description=&quot;How is struct(immutability) related to thread safety? Posted in r/swift by u/vingrish &amp;bull; 1 point and 14 comments www.reddit.com Swift and mutating struct There is something that I don't entirely understand when it comes to mutating value types in Swift. A&quot; data-og-host=&quot;skytitan.tistory.com&quot; data-og-source-url=&quot;https://skytitan.tistory.com/551&quot; data-og-url=&quot;https://skytitan.tistory.com/551&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Tfce1/hyVyl8WKF1/tkrGiwcksfk55fkAXKhtP0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bK1KAb/hyVAAKxGTh/zBmIpeJjgRqPfhMtCBjmXk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://skytitan.tistory.com/551&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://skytitan.tistory.com/551&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Tfce1/hyVyl8WKF1/tkrGiwcksfk55fkAXKhtP0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bK1KAb/hyVAAKxGTh/zBmIpeJjgRqPfhMtCBjmXk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Swift] 왜 struct에선 mutating을 사용해야하는가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How is struct(immutability) related to thread safety? Posted in r/swift by u/vingrish &amp;bull; 1 point and 14 comments www.reddit.com Swift and mutating struct There is something that I don't entirely understand when it comes to mutating value types in Swift. A&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;skytitan.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/8T5vQnMspko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/8T5vQnMspko&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=8T5vQnMspko&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/whm1J/hyVynMu2yl/VfhA1veCdCDvFnFp1vPHW0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/8T5vQnMspko&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;If a struct has a variable property but the instance of the struct was created as a constant, that property can&amp;rsquo;t be changed &amp;ndash;&amp;nbsp;the struct is constant, so all its properties are also constant regardless of how they were created.&lt;/p&gt;
&lt;p style=&quot;background-color: #1c1c1e; color: #dcdcdc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;The problem is that when you create the struct Swift has no idea whether you will use it with constants or variables, so by default it takes the safe approach: Swift won&amp;rsquo;t let you write methods that change properties unless you specifically request it.&lt;/p&gt;
&lt;p style=&quot;background-color: #1c1c1e; color: #dcdcdc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;When you&amp;nbsp;want&amp;nbsp;to change a property inside a method, you need to mark it using the&amp;nbsp;mutating&amp;nbsp;keyword, like this:&lt;/p&gt;
&lt;pre id=&quot;code_1710465313100&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Person {
    var name: String

    mutating func makeAnonymous() {
        name = &quot;Anonymous&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Because it changes the property, Swift will only allow that method to be called on&lt;span&gt;&amp;nbsp;&lt;/span&gt;Person&lt;span&gt;&amp;nbsp;&lt;/span&gt;instances that are variables:&lt;/p&gt;
&lt;pre id=&quot;code_1710465327509&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var person = Person(name: &quot;Ed&quot;)
person.makeAnonymous()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mutate 변경가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Struct 구조체에서 프로퍼티는 값을 할당하고나면 값이 정해져 있다. var 든 let이든 처음 값은 있으니까,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 구조체를 변경가능하게 사용하기 위해 정의한다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;struct는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;mutable, immutable 2가지 모드&lt;/b&gt;&lt;/span&gt;를 가지고 있다고 가정할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;let으로 할당하면 immutable, var로 할당하면 mutable이다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;추가로, class는 '참조 가능한' entity를 표현하기 때문에 이러한 설정의 개념이 없고 항상 mutable하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;struct는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;plain, mutating 이렇게 2가지 종류의 method&lt;/b&gt;&lt;/span&gt;를 가진다고 할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;plain메서드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;immutable&lt;/b&gt;&lt;/span&gt;하다는 의미를 내포하고 있으며 method에 앞에 아무것도 선언하지 않았을 때의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;default 상태&lt;/b&gt;&lt;/span&gt;이다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;plain메서드는 immutable한 규칙을 지키기 위해 어떠한 내부의 property 값도 변경하지 않는다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;plain과 반대로 mutating 메서드는 값을 변경할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;immutable한 메서드가 default인 이유
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;mutating value들은 미래의 상태를 예측하기가 매우 어렵고 이 때문에 여러 bug를 야기&lt;/b&gt;&lt;/span&gt;할 수 있다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;그렇기 때문에 해당 상황에 대한 솔루션으로 C/C++ family 언어들에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;mutable한 것들을 피하고 immutable한 모드를&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;default로 하는 것&lt;/b&gt;&lt;/span&gt;을&amp;nbsp;&lt;/span&gt;&lt;/span&gt;최상위의 wishlist에 올렸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;let (immutatable)으로 선언된 값의 mutating function을 호출하면 컴파일시 에러가 발생한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1710464744884&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

struct Point {
    var x: Int
    var y: Int

    mutating func update() {
        x = 2
        y = 2
    }
}

let point = Point(x: 1, y: 1)
point.update()

print(point)

/*
/tmp/D3730DC1-4D40-4C9B-9732-919A6846A49A.1XZJDC/main.swift:16:1: error: cannot use mutating member on immutable value: 'point' is a 'let' constant
point.update()
^~~~~
/tmp/D3730DC1-4D40-4C9B-9732-919A6846A49A.1XZJDC/main.swift:15:1: note: change 'let' to 'var' to make it mutable
let point = Point(x: 1, y: 1)
^~~
var
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Grammary in Swift</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/215</guid>
      <comments>https://vesselwheel.tistory.com/215#entry215comment</comments>
      <pubDate>Fri, 15 Mar 2024 10:01:06 +0900</pubDate>
    </item>
    <item>
      <title>구조체(Struct)에 대해서 설명해주세요. 어떤 경우 사용하나요?</title>
      <link>https://vesselwheel.tistory.com/214</link>
      <description>&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구조체 (Structures)&lt;span style=&quot;text-align: start;&quot;&gt;는 프로그램 코드의 구성 요소가 되는 범용의 유연한 구조입니다. &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;상수, 변수, 그리고 함수를 정의하는 것과 같은 구문을 사용하여 구조체와 클래스에 프로퍼티와 메서드를 기능적으로 추가할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;구조체는프로퍼티에 값을 저장하거나 메서드를 통해 기능을 제공하고 이걸 하나로 캡슐화할 수 있는 사용자 정의 타입입니다.&lt;/li&gt;
&lt;li&gt;생성자(initializer)를 정의하지 않으면 구조체가 자동으로 생성자(Memberwise Initializer.)를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 값타입 이란?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값 타입은 변수나 상수에 할당될 때 값의 복사본이 생성되는 타입입니다. 주로 구조체(Structures), 열거형(Enumerations), 기본 데이터 타입(Int, Double, Bool, 등)이 값 타입에 해당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;날씨 API 호출할 때, DTO를 정의하게 되는데, 서버에서 데이터를 호출할 때 정의되는 프로퍼티를 구조체로 선언하여 값타입으로 서버에 있는 데이터를 복사하여 호출할 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 날씨 앱에서 API를 호출할 때 Struct WeatherData&lt;/p&gt;
&lt;div style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot; data-text-less=&quot;닫기&quot; data-text-more=&quot;더보기&quot; data-ke-type=&quot;moreLess&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710464959890&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;//
//  WeatherModel.swift
//  Weather777
//
//  Created by Jason Yang on 2/5/24.
//

import Foundation

// MARK: - WeatherData
struct WeatherData: Codable {
    let cod: String
    let message, cnt: Int
    let list: [List]
    let city: City
}

// MARK: - City
struct City: Codable {
    let id: Int
    let name: String
    let coord: Coord
    let country: String
    let population, timezone, sunrise, sunset: Int
}

// MARK: - Coord
struct Coord: Codable {
    var id: Int?
    var lat, lon: Double
}
extension Coord {
    init(lat: Double, lon: Double) {
        self.id = UUID().hashValue
        self.lat = lat
        self.lon = lon
    }
}

// MARK: - List
struct List: Codable {
    let dt: Int
    let main: MainClass
    let weather: [Weather]
    let clouds: Clouds
    let wind: Wind
    let visibility: Int
    let pop: Double
    let sys: Sys
    let dtTxt: String
    let rain: Rain?

    enum CodingKeys: String, CodingKey {
        case dt, main, weather, clouds, wind, visibility, pop, sys
        case dtTxt = &quot;dt_txt&quot;
        case rain
    }
}

// MARK: - Clouds
struct Clouds: Codable {
    let all: Int
}

// MARK: - MainClass
struct MainClass: Codable {
    let temp, feelsLike, tempMin, tempMax: Double
    let pressure, seaLevel, grndLevel, humidity: Int
    let tempKf: Double

    enum CodingKeys: String, CodingKey {
        case temp
        case feelsLike = &quot;feels_like&quot;
        case tempMin = &quot;temp_min&quot;
        case tempMax = &quot;temp_max&quot;
        case pressure
        case seaLevel = &quot;sea_level&quot;
        case grndLevel = &quot;grnd_level&quot;
        case humidity
        case tempKf = &quot;temp_kf&quot;
    }
}

// MARK: - Rain
struct Rain: Codable {
    let the3H: Double

    enum CodingKeys: String, CodingKey {
        case the3H = &quot;3h&quot;
    }
}

// MARK: - Sys
struct Sys: Codable {
    let pod: Pod
}

enum Pod: String, Codable {
    case d = &quot;d&quot;
    case n = &quot;n&quot;
}

// MARK: - Weather
struct Weather: Codable {
    let id: Int
    let main: MainEnum
    let description: String
    let icon: String
}


enum MainEnum: String, Codable {
    case clear = &quot;Clear&quot;
    case clouds = &quot;Clouds&quot;
    case rain = &quot;Rain&quot;
    case snow = &quot;Snow&quot;
}

// MARK: - Wind
struct Wind: Codable {
    let speed: Double
    let deg: Int
    let gust: Double
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Grammary in Swift</category>
      <author>JasonYang</author>
      <guid isPermaLink="true">https://vesselwheel.tistory.com/214</guid>
      <comments>https://vesselwheel.tistory.com/214#entry214comment</comments>
      <pubDate>Thu, 14 Mar 2024 10:00:20 +0900</pubDate>
    </item>
  </channel>
</rss>