<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>aloe 기술블로그</title>
    <link>https://aloe-study.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 15 Apr 2026 02:00:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>개발자 aloe</managingEditor>
    <image>
      <title>aloe 기술블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/4926891/attach/ce3c3d7b7cf841c2a00ba134d179cee5</url>
      <link>https://aloe-study.tistory.com</link>
    </image>
    <item>
      <title>Delegate Pattern, Closure 데이터 전달 방식 정리 (&amp;quot;Any&amp;quot;와AnyObject 까지)</title>
      <link>https://aloe-study.tistory.com/236</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;델리게이트 패턴(Delegate Pattern)을 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠깐 그전에!!!!!!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Any와 AnyObject란?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Any&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;모든 타입&quot; 을 저장할 수 있는 타입&lt;/li&gt;
&lt;li&gt;값 타입(Struct, Enum), 참조 타입(Class, Closure) 모두 저장 가능&lt;/li&gt;
&lt;li&gt;ex) Int, Double, String, Bool, Struct, Class, Closure 다 담을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AnyObject&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;모든 클래스 타입&quot; 만 저장할 수 있는 타입&lt;/li&gt;
&lt;li&gt;오직 &lt;b&gt;클래스 인스턴스&lt;/b&gt;만 가능 (구조체, 열거형, 클로저 ❌)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Any &amp;amp; AnyObject의 타입 캐스팅&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;switch-case 패턴 매칭 (as)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;as를 이용해 switch 문 안에서 타입별로 분기 처리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;for thing in things {
    switch thing {
    case _ as Int:
        print(&quot;Int Type!&quot;)
    case _ as String:
        print(&quot;String Type!&quot;)
    case _ as Human:
        print(&quot;Human Class Type!&quot;)
    case _ as () -&amp;gt; ():
        print(&quot;Closure Type!&quot;)
    default:
        print(&quot;Unknown&quot;)
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다운캐스팅 (as?, as!)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;as?: 타입 변환 시도, 실패하면 nil&lt;/li&gt;
&lt;li&gt;as!: 타입 변환 강제, 실패 시 런타임 에러&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;var name: Any = &quot;Sodeul&quot;
if let str = name as? String {
    print(str.count) // 성공하면 사용 가능
}

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  [심화] Any, AnyObject가 &quot;런타임 시점에 타입이 결정된다&quot;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift는 &lt;b&gt;컴파일 시점&lt;/b&gt;에 타입을 엄격히 체크하는 언어야.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;Any&lt;/b&gt;나 &lt;b&gt;AnyObject&lt;/b&gt;로 선언하면, 어떤 타입이 들어올지 컴파일러가 미리 알 수 없어.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &quot;컴파일할 때 타입을 정확히 모른다&quot; &amp;rarr; &quot;런타임(앱 실행 중) 때 실제 타입을 확인하고 처리한다&quot; 는 뜻&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;ceylon&quot;&gt;&lt;code&gt;let value: Any = &quot;Hello&quot;

value.count // 컴파일 에러! (Any 타입은 count를 모름)

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 value는 컴파일할 때 그냥 &lt;b&gt;Any&lt;/b&gt;야.&lt;/li&gt;
&lt;li&gt;그런데 실제 앱이 실행될 때, &lt;b&gt;String&lt;/b&gt;이란 걸 알게 돼.&lt;/li&gt;
&lt;li&gt;그래서 as?나 as!로 &lt;b&gt;런타임&lt;/b&gt;에 타입을 확인해서 사용해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;if let stringValue = value as? String {
    print(stringValue.count) // 런타임에 String임을 확인하고 count 사용
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;Any나 AnyObject를 쓸 때는 런타임 타입 확인이 필수&lt;/b&gt;야.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(안 하면 크래시가 터지거나, 타입 추론이 안 돼서 메서드 호출도 못함.)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;Any&lt;/b&gt; = &quot;진짜 모든 타입 가능&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AnyObject&lt;/b&gt; = &quot;오직 클래스 인스턴스만 가능&quot;&lt;/li&gt;
&lt;li&gt;둘 다 컴파일 시점에는 정확한 타입을 몰라서 &lt;b&gt;런타임 타입 확인&lt;/b&gt;이 필요하다!&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h1&gt;  1. 델리게이트 패턴(Delegate Pattern) 이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Delegate&lt;/b&gt;는 어떤 객체가 &quot;자신의 일을 다른 객체에 위임&quot;하는 디자인 패턴이야.&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;A 객체&lt;/b&gt;가 어떤 행동을 하고 싶지만, &quot;직접&quot; 하지 않고 &lt;b&gt;B 객체&lt;/b&gt;에게 &quot;너 대신 해줘&quot;라고 맡기는 구조&lt;/li&gt;
&lt;li&gt;대신, B 객체가 어떤 메서드를 구현했는지는 보장해야 하니까 protocol(프로토콜)을 이용해서 &quot;너는 이 함수를 꼭 구현해야 해&quot;라고 규칙을 정해.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A&lt;/b&gt;는 &quot;Delegate 프로토콜&quot;만 알고 있고, &lt;b&gt;B&lt;/b&gt;는 그 프로토콜을 준수하면서 대신 행동해주는 거야.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;너가 대한민국 변호사라면 해당 자격증을 채택해야해&lt;/li&gt;
&lt;li&gt;자격증 안에는 판례 파악하기, 소송 절차 이해하기, 민사소송, 형사소송관련 법률적 근거 알고있기 등등&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;자격증 채택하기 (나는 변호사야 자격증을 채택했고 너가 말한 기능을 다 할줄 알아 언제든지 요청해줘!)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;// 프로토콜 : 자격증(이 자격증을 가진애들은 다 올 수 있음) - 의존성 주입이라는 개념

protocol DataBindDelegate : AnyObject {
    func dataBind(id : String) // 위임할 일을 정의
}
&lt;/code&gt;&lt;/pre&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;h1&gt;  2. AnyObject를 왜 protocol에 붙이는가?&lt;/h1&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol DataBindDelegate: AnyObject {
    func dataBind(id: String)
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;➡️ 이유는 &quot;weak&quot; 참조를 위해서야!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Delegate&lt;/b&gt;는 주로 weak으로 선언해.&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;weak var delegate: DataBindDelegate?

&lt;/code&gt;&lt;/pre&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;delegate는 순환 참조(Retain Cycle)를 방지하기 위해 약한 참조(weak)로 선언하는 게 &quot;관례&quot;야.&lt;/li&gt;
&lt;li&gt;그런데 &lt;b&gt;Swift 규칙&lt;/b&gt;상 weak은 &lt;b&gt;클래스 타입&lt;/b&gt;(Reference Type)한테만 걸 수 있어.&lt;/li&gt;
&lt;li&gt;(Struct나 Enum은 Value Type이라 weak 적용이 불가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &quot;Delegate는 클래스만 할 수 있어야 한다&quot; 라고 제한을 걸기 위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;protocol 정의할 때 : AnyObject를 붙이는 거야.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 즉, AnyObject를 붙이면, &quot;이 프로토콜을 따르는 타입은 클래스만 가능하다&quot;는 뜻&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순환참조 관점에서 나의 위임자가 없을수 있음 + 만약 weak이 아니라면 서로가 서로를 가리키는 상황 발생&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드로 알아보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발급자 관점 (대리자에게 위임하는 쪽)&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;// 프로토콜 : 자격증(이 자격증을 가진애들은 다 올 수 있음) 발급 

protocol DataBindDelegate : AnyObject {
    func dataBind(id : String) // 위임할 일을 정의
}

class WelcomeViewController_DelegatePattern: UIViewController {
    
    // MARK: - Property
    
    var id: String?
    weak var delegate : DataBindDelegate? // 프로토콜 타입(자격증) 채택한 애들은 다 올수가 있어
    
    // MARK: - UIComponent
    
    let mainImage: UIImageView = {
        let Image = UIImageView(frame: CGRect(x: 120, y: 60, width: 150, height: 150))
        Image.image = ImageLiterals.login
        return Image
    }()
		.
		.
		.
    
    //MARK: Life Cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        addViews()
    }
}
extension WelcomeViewController_DelegatePattern{
    
    @objc
    private func backToLoginButtonDidTap() {
        
        if let id = id {
            delegate?.dataBind(id: id) // delegate 야야 너 델리게이트 이것좀 해봐라~
        }
        
        if self.navigationController == nil {
            self.dismiss(animated: true)
        } else {
            self.navigationController?.popViewController(animated: true)
        }
    }
 
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대리자 관점 (자격증을 채택해서 관련 기능(업무)를 처리)&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;//
//  LoginViewController_DelegatePattern.swift
//  Week_02
//
//  Created by 정정욱 on 4/12/25.
//

import UIKit

class LoginViewController_DelegatePattern: UIViewController {
    
    // MARK: - UIComponent
    
    let titleLabel: UILabel = {
        let label = UILabel(frame: CGRect(x: 69, y: 161, width: 236, height: 44))
        label.text = &quot;동네라서 가능한 모든것\\n당근에서 가까운 이웃과 함께해요.&quot;
        label.font = UIFont.Pretendard.subhead1()
        label.textColor = .black
        label.textAlignment = .center
        label.numberOfLines = 2
        return label
    }()
    
    let idTextField: UITextField = {
        let textField = UITextField(frame: CGRect(x: 20, y: 276, width: 335, height: 52))
        textField.placeholder = &quot;아이디&quot;
        textField.font = UIFont.Pretendard.subhead4()
        textField.backgroundColor = UIColor.Gray200
        return textField
    }()
    
    let passwordTextField: UITextField = {
        let textField = UITextField(frame: CGRect(x: 20, y: 335, width: 335, height: 52))
        textField.placeholder = &quot;비밀번호&quot;
        textField.font = UIFont.Pretendard.subhead4()
        textField.backgroundColor = UIColor.Gray200
        return textField
    }()
}

//MARK: Delegate

extension LoginViewController_DelegatePattern {
    @objc
    private func loginButtonDidTap() {
        //presentToWelcomeVC()
        pushToWelcomeVC()
    }

    private func pushToWelcomeVC() {
        let welcomeViewController = WelcomeViewController_DelegatePattern()
        welcomeViewController.delegate = self // 1. 어어 너의 대리자는 나야
        welcomeViewController.id = idTextField.text
    
        self.navigationController?.pushViewController(welcomeViewController, animated: true)
    }
}

// 2. 너가 말한 자격증을 채택했고 너가 하라는 기능을 나는 할 수 있어 
extension LoginViewController_DelegatePattern: DataBindDelegate {
    func dataBind(id: String) {
        passwordTextField.text = &quot;\\(id) + 1234 &quot;
    }
}

#Preview{
    LoginViewController_DelegatePattern()
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  3. 의존성 주입(Dependency Injection, DI)과의 관계?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Delegate 패턴은 의존성 주입의 일종&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;A 객체가 &quot;내 일을 대신할 B 객체&quot;를 &lt;b&gt;외부에서 주입&lt;/b&gt;받잖아?&lt;/li&gt;
&lt;li&gt;직접 생성하거나 의존하지 않고, &lt;b&gt;외부에서 주입해서 결합도를 낮춘다&lt;/b&gt;는 점에서 &lt;b&gt;DI(Dependency Injection)&lt;/b&gt; 개념이 들어가는 거야.&lt;/li&gt;
&lt;/ul&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;&lt;b&gt;의존성 주입&lt;/b&gt;은 &quot;필요한 객체를 외부에서 주입받는 것&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;델리게이트 패턴&lt;/b&gt;은 &quot;필요한 행동을 대신할 객체를 외부에서 주입받는 것&quot;&lt;/li&gt;
&lt;li&gt;델리게이트는 특히 행동(메서드 실행)을 외주화하는 특화된 의존성 주입이야.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  한눈에 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Delegate 패턴&lt;/td&gt;
&lt;td&gt;어떤 행동을 다른 객체에 위임하는 패턴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AnyObject 붙이는 이유&lt;/td&gt;
&lt;td&gt;Delegate를 weak로 갖기 위해 (클래스만 허용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delegate와 DI 관계&lt;/td&gt;
&lt;td&gt;Delegate는 의존성 주입의 한 종류 (외부 주입)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;weak var delegate&lt;/td&gt;
&lt;td&gt;메모리 누수(Retain Cycle) 방지하려고 약한 참조&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 그럼 이거 왜쓰는건데?.&lt;/p&gt;
&lt;h1&gt;  Delegate 패턴을 언제 써야 해?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;객체끼리 소통이 필요한데, 직접 참조해서 결합도를 높이고 싶지 않을 때&quot;&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;어떤 &lt;b&gt;A 객체&lt;/b&gt;가 특정 상황이 되면 &lt;b&gt;B 객체&lt;/b&gt;에게 알려야 하는데&lt;/li&gt;
&lt;li&gt;A가 B를 &lt;b&gt;직접 알아버리면&lt;/b&gt; 둘이 강하게 엮여서(=결합) 유지보수가 어려워져.&lt;/li&gt;
&lt;li&gt;이걸 막으려고 &lt;b&gt;&quot;B가 Delegate 프로토콜을 구현하고&quot;&lt;/b&gt;, A는 그냥 delegate한테 &quot;약하게(weak) 요청&quot;하는것&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  즉, &quot;낮은 결합(Loose Coupling)&quot;을 만들고 싶을 때 Delegate 패턴을 쓴다!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  실전 예시 (iOS)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. &lt;b&gt;UITableView, UICollectionView&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;테이블뷰나 컬렉션뷰를 쓰려면 반드시 UITableViewDelegate, UITableViewDataSource를 구현해야 해.&lt;/li&gt;
&lt;li&gt;테이블뷰가 직접 데이터를 가지는 게 아니라, &lt;b&gt;&quot;DataSource가 셀 몇개 있는지 알려줘&quot;&lt;/b&gt;,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;Delegate가 셀 클릭되면 뭐할지 알려줘&quot;&lt;/b&gt; 이렇게 위임하는 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;tableView.delegate = self
tableView.dataSource = self

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테이블뷰는 데이터, 동작을 직접 몰라도 돼. (Delegate가 대신 처리)&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. &lt;b&gt;Custom View 내부 이벤트를 외부로 전달할 때&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, &lt;b&gt;CustomButton&lt;/b&gt;이 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눌렀을 때 특정 ViewController가 뭘 하게 하고 싶어.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버튼이 뷰컨을 &quot;직접 모르면 좋겠어&quot;&lt;/li&gt;
&lt;li&gt;그래서 &lt;b&gt;Delegate 프로토콜&lt;/b&gt; 만들어서 버튼이 그냥 &quot;나 클릭됐어!&quot;만 알리고&lt;/li&gt;
&lt;li&gt;ViewController가 &lt;b&gt;Delegate를 통해 반응&lt;/b&gt;하는 거야.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol CustomButtonDelegate: AnyObject {
    func didTapButton()
}

class CustomButton: UIButton {
    weak var delegate: CustomButtonDelegate?

    @objc func buttonTapped() {
        delegate?.didTapButton()
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;버튼은 외부가 뭘 하든 신경 안 쓴다! (낮은 결합)&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. &lt;b&gt;A 뷰컨 &amp;rarr; B 뷰컨 데이터 전달&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;A가 B를 present하거나 push 한 다음,&lt;/li&gt;
&lt;li&gt;B에서 사용자 입력을 받아서 다시 A로 &lt;b&gt;&quot;결과를 넘겨줄 때&quot;&lt;/b&gt; Delegate를 쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol BViewControllerDelegate: AnyObject {
    func didEnterName(name: String)
}

class BViewController: UIViewController {
    weak var delegate: BViewControllerDelegate?

    func doneButtonTapped() {
        delegate?.didEnterName(name: &quot;정정욱&quot;)
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;B는 A를 모르고, 그냥 delegate한테 &quot;입력했다&quot;고 알려주는 것뿐&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;왜 Delegate를 써야 함?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;낮은 결합 (Loose Coupling)&lt;/td&gt;
&lt;td&gt;서로 직접 의존하지 않아서 유지보수 편해짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유연성 (Flexibility)&lt;/td&gt;
&lt;td&gt;다른 객체로 쉽게 교체 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;재사용성 (Reusability)&lt;/td&gt;
&lt;td&gt;View나 컴포넌트를 다른 곳에서도 쓸 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 안전 (Memory Safety)&lt;/td&gt;
&lt;td&gt;weak 덕분에 순환 참조(Retain Cycle) 방지 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;개발하다 델리게이트를 써야 하는 순간&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CustomView가 외부 이벤트를 전달해야 할 때&lt;/li&gt;
&lt;li&gt;뷰컨끼리 결과 데이터를 주고받을 때 (특히 모달 dismiss할 때)&lt;/li&gt;
&lt;li&gt;테이블뷰, 컬렉션뷰 같은 UIKit 컴포넌트를 쓸 때&lt;/li&gt;
&lt;li&gt;네트워크 콜백 결과를 외부로 넘기고 싶을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Closure 방식의 값 전달 델리게이트와 비슷함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A -&amp;gt; B&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A &amp;lt;- B&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;b&gt;Closure 같는쪽 (야야 나 너가 주는 이름 없는 함수를 받을 준비가 끝났어)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Closure 보내는쪽 (어어 그래? 그러면 너거 적절하게 함수 실행만 시켜 내가 어떤 코드들을 실행 해야하는지는 내쪽에서 정의 할거야)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Closure 같는쪽&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class WelcomeViewController_Closure: UIViewController {
    
    // MARK: - Property
    
    var id: String?
    var loginDataCompletion : ((String) -&amp;gt; Void)? // 1. 함수를 받을 준비
    
    // MARK: - UIComponent
    
    let mainImage: UIImageView = {
        let Image = UIImageView(frame: CGRect(x: 120, y: 60, width: 150, height: 150))
        Image.image = ImageLiterals.login
        return Image
    }()

   .
   .
}

//MARK: Action

extension WelcomeViewController_Closure{
    
    @objc
    private func backToLoginButtonDidTap() {
        
        guard let loginDataCompletion else { return }
        if let id = id {
            loginDataCompletion(id) //  2. 어어 나 해당기능 끝나면 너가 준거 실행 시킨다! : 받는놈이 실행을 한다
            /*
             print() // ()붙으면 함수 실행. {}만 있으면 함수 정의(보내는 쪽) 
             */
        }
        
        if self.navigationController == nil {
            self.dismiss(animated: true)
        } else {
            self.navigationController?.popViewController(animated: true)
        }
    }
    
    private func bindID() {
        self.welcomeLabel.text = &quot;\\(id ?? &quot;&quot;)님 \\n반가워요!&quot;
    }
    
    func setLabelText(id: String?) {
        //self.id = id // 요놈은 왜 타입 표출 일어남? String? 로 선언 되어 있기 때문임
        self.welcomeLabel.text = &quot;\\(id ?? &quot;&quot;)님 \\n반가워요!&quot;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Closure 보내는쪽&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;//
//  LoginViewController_Closure.swift
//  Week_02
//
//  Created by 정정욱 on 4/12/25.
//

import UIKit

class LoginViewController_Closure: UIViewController {
    
    // MARK: - UIComponent
    
    let passwordTextField: UITextField = {
        let textField = UITextField(frame: CGRect(x: 20, y: 335, width: 335, height: 52))
        textField.placeholder = &quot;비밀번호&quot;
        textField.font = UIFont.Pretendard.subhead4()
        textField.backgroundColor = UIColor.Gray200
        return textField
    }()
    .
    .
    .
    
    //MARK: Life Cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        addViews()
    }
}

//MARK: Action

extension LoginViewController_Closure {
    
    // 함수를 미리 정의를 해서 던진다 그럼 실행은 어디서 할까? 받은 쪽에서 한다
    private func pushToWelcomeVC() {
        let welcomeViewController = WelcomeViewController_Closure() // 함수를 받을놈
        welcomeViewController.id = idTextField.text
        
        // 1. 함수를 미리 정의 =&amp;gt; 던짐 (적절한 곳에서 실행 부탁해)
        // 클로저 값 캡처 방지 [weak self] 이제는 아시죵?
        welcomeViewController.loginDataCompletion = { [weak self] data in
            print(&quot;클로저로 받아온 id가 뭐냐면&quot;, data)
            guard let self else { return }
            self.passwordTextField.text = data
        } // 여기 까지 함수 정의
    
        self.navigationController?.pushViewController(welcomeViewController, animated: true)
    }
}

#Preview{
    LoginViewController_Closure()
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt; &amp;nbsp;Delegate vs Closure 비교&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 Delegate Closure&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;기본 개념&lt;/td&gt;
&lt;td&gt;'대리'에게 맡긴다 (프로토콜 기반)&lt;/td&gt;
&lt;td&gt;코드 블록(함수)을 변수로 넘긴다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;설계 방식&lt;/td&gt;
&lt;td&gt;프로토콜(Interface)을 정의하고, 구현하는 객체를 외부에서 주입&lt;/td&gt;
&lt;td&gt;함수(Closure)를 외부에서 직접 주입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 목적&lt;/td&gt;
&lt;td&gt;&lt;b&gt;여러 이벤트를 다뤄야 할 때&lt;/b&gt; (복합적/다수)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;특정 1개의 작업만 넘길 때&lt;/b&gt; (간단, 직관적)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;관계&lt;/td&gt;
&lt;td&gt;1:N (여러 함수)&lt;/td&gt;
&lt;td&gt;1:1 (한 개 함수)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 길이&lt;/td&gt;
&lt;td&gt;길어짐 (프로토콜 선언 + 구현)&lt;/td&gt;
&lt;td&gt;짧아짐 (inline 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대표 예시&lt;/td&gt;
&lt;td&gt;테이블뷰, 커스텀 뷰 이벤트 전달&lt;/td&gt;
&lt;td&gt;버튼 클릭 시 단순 액션, 네트워크 응답 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Delegate 사용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여러 동작을 넘기고 싶을 때&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol ProfileViewDelegate: AnyObject {
    func didTapFollowButton()
    func didTapMessageButton()
}

class ProfileView: UIView {
    weak var delegate: ProfileViewDelegate?

    @objc func followButtonTapped() {
        delegate?.didTapFollowButton()
    }

    @objc func messageButtonTapped() {
        delegate?.didTapMessageButton()
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;Follow 버튼&quot; / &quot;Message 버튼&quot; 각각 다른 일을 Delegate한테 알림&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Closure 사용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;딱 한 동작만 넘길 때&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class ProfileView: UIView {
    var followButtonAction: (() -&amp;gt; Void)?  // 클로저 변수

    @objc func followButtonTapped() {
        followButtonAction?() // 클로저 실행
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;Follow 버튼 클릭&quot; 하나만 넘기고 끝낼 때&lt;/b&gt; 클로저로 깔끔하게 가능.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;아니 그럼 언제 뭘 써야 해?&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황 쓰는 것 이유&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;버튼 한두개 클릭 처리&lt;/td&gt;
&lt;td&gt;Closure&lt;/td&gt;
&lt;td&gt;간단하고, inline 처리로 코드 짧음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;여러 동작을 하나의 객체에 묶어야 함&lt;/td&gt;
&lt;td&gt;Delegate&lt;/td&gt;
&lt;td&gt;관리하기 편하고 확장성 좋음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;복잡한 라이프사이클 관리 (ex: 화면 이동, 상태 변경)&lt;/td&gt;
&lt;td&gt;Delegate&lt;/td&gt;
&lt;td&gt;명확하게 역할 분리 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;네트워크 요청 결과 받기&lt;/td&gt;
&lt;td&gt;Closure&lt;/td&gt;
&lt;td&gt;결과 하나만 넘기면 되니까 간단하게&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;❤️&amp;nbsp;한 줄 요약&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Delegate는 여러 행동을 한 번에 넘길 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Closure&lt;/b&gt;는 단일 이벤트를 간편하게 넘길 때 쓴다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;마지막 요약&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Delegate&lt;/b&gt; = &quot;A야, 어떤 상황되면 나 대신 알려줘&quot; (Protocol + 구현)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Closure&lt;/b&gt; = &quot;A야, 이 코드 한 조각 받아서 실행해줘&quot; (Function as variable)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>any&amp;quot;와anyobject</category>
      <category>closure 값 전달 이해하기</category>
      <category>delegate 패턴 쉽게 이해하고 적용하기</category>
      <category>ios delegate 패턴</category>
      <author>개발자 aloe</author>
      <guid isPermaLink="true">https://aloe-study.tistory.com/236</guid>
      <comments>https://aloe-study.tistory.com/236#entry236comment</comments>
      <pubDate>Thu, 15 May 2025 16:36:05 +0900</pubDate>
    </item>
    <item>
      <title>API 통신 정리: URLSession, async/await 확실한 이해, 네트워크 셋팅, 및 설계</title>
      <link>https://aloe-study.tistory.com/235</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;&lt;a href=&quot;https://amused-flax-90b.notion.site/API-URLSession-async-await-1ee3310a471480479767e69b7f73f37a?pvs=74&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://amused-flax-90b.notion.site/API-URLSession-async-await-1ee3310a471480479767e69b7f73f37a?pvs=74&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;Swift에서 URLSession은 &lt;b&gt;HTTP 통신을 위한 기본 API&lt;/b&gt;로, 서버와 데이터를 주고받을 때 사용됩니다. 앱에서 REST API와 통신하거나 파일을 다운로드할 때 자주 사용됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. URLSession이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLSession은 Apple에서 제공하는 &lt;b&gt;비동기 네트워킹 API&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;HTTP/HTTPS 요청 및 응답&lt;/li&gt;
&lt;li&gt;파일 업로드/다운로드&lt;/li&gt;
&lt;li&gt;백그라운드 전송&lt;/li&gt;
&lt;/ul&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;Apple에서 제공하는 네트워크 통신 API로, 앱이 서버와 데이터를 주고받을 수 있도록 지원합니다.&lt;/li&gt;
&lt;li&gt;Alamofire, Moya 등 서드파티 라이브러리의 기반이 되는 핵심 API입니다.&lt;/li&gt;
&lt;li&gt;타임아웃, 캐시 정책, 백그라운드 전송 등 다양한 네트워크 설정을 구성할 수 있습니다.(&lt;a href=&quot;https://gwonii.github.io/2020/03/25/URLSession.html?utm_source=chatgpt.com&quot;&gt;gwonii.github.io&lt;/a&gt;, &lt;a href=&quot;https://velog.io/%40juh2/iOSSwift-URL-Session?utm_source=chatgpt.com&quot;&gt;Velog&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 기본 구조&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let url = URL(string: &quot;&amp;lt;https://api.example.com/data&amp;gt;&quot;)!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
    // 응답 처리
}
task.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 플로우&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import Foundation

// 1. URL 생성 (옵셔널 바인딩으로 안전하게 처리)
guard let url = URL(string: &quot;&amp;lt;https://api.example.com/data&amp;gt;&quot;) else {
    print(&quot;❌ 유효하지 않은 URL&quot;)
    return
}

// 2. URLRequest 객체 생성 및 설정
var request = URLRequest(url: url)
request.httpMethod = &quot;GET&quot;  // or &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;
request.setValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)
// 필요 시 헤더 추가 가능
// request.setValue(&quot;Bearer &quot;, forHTTPHeaderField: &quot;Authorization&quot;)

// 3. URLSession으로 dataTask 생성
let task = URLSession.shared.dataTask(with: request) { data, response, error in
    
    // 4. 에러 체크
    if let error = error {
        print(&quot;❌ 네트워크 에러: \\(error.localizedDescription)&quot;)
        return
    }

    // 5. 응답 상태코드 확인
    guard let httpResponse = response as? HTTPURLResponse else {
        print(&quot;❌ 유효하지 않은 응답 객체&quot;)
        return
    }

    print(&quot;  상태코드: \\(httpResponse.statusCode)&quot;)

    guard (200...299).contains(httpResponse.statusCode) else {
        print(&quot;❌ 서버 응답 오류: \\(httpResponse.statusCode)&quot;)
        return
    }

    // 6. 데이터 존재 여부 확인 및 JSON 파싱
    guard let data = data else {
        print(&quot;❌ 데이터가 없습니다.&quot;)
        return
    }

    do {
        // 7. JSON 파싱 (Dictionary 또는 Array 형태)
        if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
            print(&quot;✅ 파싱된 JSON: \\(json)&quot;)
        } else {
            print(&quot;❌ JSON 형식이 Dictionary가 아닙니다.&quot;)
        }
    } catch {
        print(&quot;❌ JSON 디코딩 실패: \\(error)&quot;)
    }
}

// 8. 네트워크 요청 시작
task.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&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;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;URLSession&lt;/td&gt;
&lt;td&gt;네트워크 작업을 관리하는 객체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URLSessionTask&lt;/td&gt;
&lt;td&gt;네트워크 요청을 나타냄 (dataTask, uploadTask, downloadTask)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URLRequest&lt;/td&gt;
&lt;td&gt;요청 정보 설정 (URL, HTTP 메서드, 헤더, 바디 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URLResponse, HTTPURLResponse&lt;/td&gt;
&lt;td&gt;응답 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  URLSessionConfiguration 종류 (네트워크 작업 관리 객테의 종류)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLSession은 네 가지 세션 구성을 제공합니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Shared Session&lt;/b&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;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let url = URL(string: &quot;&amp;lt;https://example.com/data&amp;gt;&quot;)!
URLSession.shared.dataTask(with: url) { data, response, error in
    // 응답 처리
}.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Default Session&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Shared Session과 유사하지만, 커스터마이징이 가능합니다.&lt;/li&gt;
&lt;li&gt;Delegate를 통해 데이터를 점진적으로 받아올 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let url = URL(string: &quot;&amp;lt;https://example.com/data&amp;gt;&quot;)!
let defaultSession = URLSession(configuration: .default)
defaultSession.dataTask(with: url) { data, response, error in
    // 응답 처리
}.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ephemeral Session&lt;/b&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;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let url = URL(string: &quot;&amp;lt;https://example.com/data&amp;gt;&quot;)!
let ephemeralSession = URLSession(configuration: .ephemeral)
ephemeralSession.dataTask(with: url) { data, response, error in
    // 응답 처리
}.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Background Session&lt;/b&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;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let url = URL(string: &quot;&amp;lt;https://example.com/data&amp;gt;&quot;)!
let config = URLSessionConfiguration.background(withIdentifier: &quot;com.example.app.background&quot;)
let backgroundSession = URLSession(configuration: config)
backgroundSession.dataTask(with: url) { data, response, error in
    // 응답 처리
}.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  URLSessionTask 종류 (어떤 요청을 보낼지 Task에 대한 종류)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLSession은 다양한 작업을 처리하기 위해 네 가지 주요 Task를 제공합니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;DataTask&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NSData 객체를 사용하여 데이터를 전송하고 받습니다.&lt;/li&gt;
&lt;li&gt;짧고 빈번한 요청에 적합하며, 주로 HTTP GET 요청을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UploadTask&lt;/b&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;/li&gt;
&lt;li&gt;주로 HTTP POST 요청을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DownloadTask&lt;/b&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;/li&gt;
&lt;li&gt;주로 HTTP GET 요청을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;StreamTask&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCP/IP 연결을 생성할 때 사용합니다.(&lt;a href=&quot;https://wnsgur9137.github.io/swift_basic_10_URLSession/?utm_source=chatgpt.com&quot;&gt;202044021 이준혁&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 사용 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  A. GET 요청&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let url = URL(string: &quot;&amp;lt;https://jsonplaceholder.typicode.com/posts&amp;gt;&quot;)!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        let responseString = String(data: data, encoding: .utf8)
        print(responseString ?? &quot;No Data&quot;)
    }
}
task.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  B. POST 요청&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;var request = URLRequest(url: URL(string: &quot;&amp;lt;https://api.example.com/login&amp;gt;&quot;)!)
request.httpMethod = &quot;POST&quot;
request.setValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)

let parameters = [&quot;email&quot;: &quot;test@example.com&quot;, &quot;password&quot;: &quot;1234&quot;]
request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    if let data = data {
        let result = String(data: data, encoding: .utf8)
        print(result ?? &quot;&quot;)
    }
}
task.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 주의할 점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.resume() 호출 안 하면 요청이 시작되지 않음&lt;/li&gt;
&lt;li&gt;비동기 코드이므로 UI 업데이트는 DispatchQueue.main.async에서 해야 함&lt;/li&gt;
&lt;li&gt;서버 응답은 statusCode를 통해 반드시 확인해야 함&lt;/li&gt;
&lt;li&gt;JSON 디코딩은 Codable을 활용하는 것이 일반적&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. JSON 디코딩 예시&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;struct Post: Codable {
    let id: Int
    let title: String
}

let url = URL(string: &quot;&amp;lt;https://jsonplaceholder.typicode.com/posts/1&amp;gt;&quot;)!
URLSession.shared.dataTask(with: url) { data, _, _ in
    if let data = data {
        let post = try? JSONDecoder().decode(Post.self, from: data)
        print(post?.title ?? &quot;No title&quot;)
    }
}.resume()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  URLRequest 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLRequest를 사용하여 요청의 세부 사항을 설정할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let url = URL(string: &quot;&amp;lt;https://example.com/data&amp;gt;&quot;)!
var request = URLRequest(url: url)
request.httpMethod = &quot;GET&quot;
request.setValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  URLResponse 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt;Completion Handler&lt;/b&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;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let url = URL(string: &quot;&amp;lt;https://example.com/data&amp;gt;&quot;)!
let defaultSession = URLSession(configuration: .default)
defaultSession.dataTask(with: url) { data, response, error in
    // 응답 처리
}.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;URLSessionDelegate&lt;/b&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;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let url = URL(string: &quot;&amp;lt;https://example.com/data&amp;gt;&quot;)!
let config = URLSessionConfiguration.default
let defaultSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)

defaultSession.dataTask(with: url).resume()

extension ViewController: URLSessionDataDelegate {
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        // 데이터 수신 처리
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        // 작업 완료 처리
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이러한 Delegate 패턴을 활용하면, 대용량 데이터 전송이나 다운로드 진행 상황을 실시간으로 처리하는 데 유용합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1747137770304&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ViewController: UIViewController, URLSessionDataDelegate {

    func getData() {
        let url = URL(string: &quot;https://example.com/data&quot;)!
        var request = URLRequest(url: url)
        request.httpMethod = &quot;GET&quot;
        let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
        let task = session.dataTask(with: request)
        task.resume()
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        print(&quot;Received data: \(data)&quot;)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            print(&quot;Error: \(error.localizedDescription)&quot;)
        } else {
            print(&quot;Data received successfully.&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;요게 어떻게 보면 URLSession 을 가장 정석적으로 사용하는 코드라고 볼수 있음&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;h1&gt;async/await 너 누군데?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async/await는 Swift 5.5부터 도입된 &lt;b&gt;비동기 코드 작성을 쉽게 해주는 문법&lt;/b&gt;입니다. 기존의 &lt;b&gt;completion handler 기반 URLSession 코드&lt;/b&gt;를 &lt;b&gt;더 간결하고 읽기 쉬운 형태&lt;/b&gt;로 바꿔줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. async/await란?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;async: 해당 함수가 &lt;b&gt;비동기적으로 실행됨&lt;/b&gt;을 나타냄&lt;/li&gt;
&lt;li&gt;await: 비동기 함수를 &lt;b&gt;기다림&lt;/b&gt; (작업이 끝날 때까지 다음 코드 실행을 멈춤)&lt;/li&gt;
&lt;li&gt;기존 completion handler보다 &lt;b&gt;가독성이 좋고 오류 처리도 간편&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 기존 방식 vs async/await&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  기존 방식&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;URLSession.shared.dataTask(with: url) { data, response, error in
    // callback 안에서 처리
}.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  async/await 방식&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;let (data, response) = try await URLSession.shared.data(from: url)
// 이어서 처리

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. URLSession + async/await 기본 예제&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;func fetchPost() async throws -&amp;gt; Post {
    let url = URL(string: &quot;&amp;lt;https://jsonplaceholder.typicode.com/posts/1&amp;gt;&quot;)!

    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
        throw URLError(.badServerResponse)
    }

    let post = try JSONDecoder().decode(Post.self, from: data)
    return post
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출은 이렇게 합니다:&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Task {
    do {
        let post = try await fetchPost()
        print(post.title)
    } catch {
        print(&quot;에러 발생: \\(error)&quot;)
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. POST 요청 예제&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;func login() async throws -&amp;gt; LoginResponse {
    let url = URL(string: &quot;&amp;lt;https://api.example.com/login&amp;gt;&quot;)!

    var request = URLRequest(url: url)
    request.httpMethod = &quot;POST&quot;
    request.setValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)

    let parameters = [&quot;email&quot;: &quot;test@example.com&quot;, &quot;password&quot;: &quot;1234&quot;]
    request.httpBody = try JSONEncoder().encode(parameters)

    let (data, response) = try await URLSession.shared.data(for: request)

    guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
        throw URLError(.badServerResponse)
    }

    let result = try JSONDecoder().decode(LoginResponse.self, from: data)
    return result
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 주의할 점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;async 함수는 await 없이 호출 불가 &amp;rarr; Task { } 안에서 호출하거나 다른 async 함수 내에서 사용해야 함&lt;/li&gt;
&lt;li&gt;UI 업데이트는 반드시 MainActor 또는 DispatchQueue.main.async에서 실행해야 함&lt;/li&gt;
&lt;li&gt;try await는 에러 처리가 필요 &amp;rarr; do-catch로 감싸야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. SwiftUI에서 사용 예시&lt;/h2&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;@MainActor
func loadData() async {
    do {
        let post = try await fetchPost()
        self.title = post.title
    } catch {
        self.errorMessage = error.localizedDescription
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;.onAppear {
    Task {
        await loadData()
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;다 필요없다 이것만 알고 가자 URLSession, async/await 사용법 정리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;URLSession 사용 순서 정리&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;URL 생성 (통신할 주소)&lt;/li&gt;
&lt;li&gt;URLRequest 객체 생성 및 설정 (httpMethod, 필요 시 헤더 추가)&lt;/li&gt;
&lt;li&gt;URLSession으로 dataTask 생성 (요청 이후 처리할 일을 정의하는 코드 블럭을 정의 &lt;b&gt;completion handler&lt;/b&gt;)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;내부에서 할일 에러체크, 응답 상태코드 확인, 데이터 응답 객체 JSON 파싱 각 case 별 에러 대응&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;task.resume()를 통해 실제 실행 !!!!! 위 1,2,3 과정을 정의 했다면 실행을 시켜야함&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import Foundation

// 1. URL 생성 (옵셔널 바인딩으로 안전하게 처리)
guard let url = URL(string: &quot;&amp;lt;https://api.example.com/data&amp;gt;&quot;) else {
    print(&quot;❌ 유효하지 않은 URL&quot;)
    return
}

// 2. URLRequest 객체 생성 및 설정
var request = URLRequest(url: url)
request.httpMethod = &quot;GET&quot;  // or &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;
request.setValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)
// 필요 시 헤더 추가 가능
// request.setValue(&quot;Bearer &quot;, forHTTPHeaderField: &quot;Authorization&quot;)

// 3. URLSession으로 dataTask 생성
let task = URLSession.shared.dataTask(with: request) { data, response, error in
    
    // 4. 에러 체크
    if let error = error {
        print(&quot;❌ 네트워크 에러: \\(error.localizedDescription)&quot;)
        return
    }

    // 5. 응답 상태코드 확인
    guard let httpResponse = response as? HTTPURLResponse else {
        print(&quot;❌ 유효하지 않은 응답 객체&quot;)
        return
    }

    print(&quot;  상태코드: \\(httpResponse.statusCode)&quot;)

    guard (200...299).contains(httpResponse.statusCode) else {
        print(&quot;❌ 서버 응답 오류: \\(httpResponse.statusCode)&quot;)
        return
    }

    // 6. 데이터 존재 여부 확인 및 JSON 파싱
    guard let data = data else {
        print(&quot;❌ 데이터가 없습니다.&quot;)
        return
    }

    do {
        // 7. JSON 파싱 (Dictionary 또는 Array 형태)
        if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
            print(&quot;✅ 파싱된 JSON: \\(json)&quot;)
        } else {
            print(&quot;❌ JSON 형식이 Dictionary가 아닙니다.&quot;)
        }
    } catch {
        print(&quot;❌ JSON 디코딩 실패: \\(error)&quot;)
    }
}

// 8. 네트워크 요청 시작
task.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;async/await 사용 순서 정리&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;비동기 처리 작업이 들어가는 함수에 async 키워드 붙이기 async 함수로 만들어 주는 것 (비동기 작업을 하는 함수라고 정의)&lt;/li&gt;
&lt;li&gt;반환 타입 = await (비동기 작업 실행) 구조를 꼭 기억하자
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;await 왼쪽은 비동기 작업 실행 이후 받을 값들에 대해서 정의를 하고 await 뒤에 비동기 작업을 실행하는 코드를 위치&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;async 함수는 await 없이 호출 불가 &amp;rarr; Task { } 안에서 호출하거나 다른 async 함수 내에서 사용해야 함&lt;/li&gt;
&lt;li&gt;UI 업데이트는 반드시 MainActor 또는 DispatchQueue.main.async에서 실행해야 함&lt;/li&gt;
&lt;li&gt;try await는 에러 처리가 필요 &amp;rarr; do-catch로 감싸야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;func fetchPost() async throws -&amp;gt; Post {
    let url = URL(string: &quot;&amp;lt;https://jsonplaceholder.typicode.com/posts/1&amp;gt;&quot;)!

// 반환 타입 = await (비동기 작업 실행) 구조를 꼭 기억하자
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
        throw URLError(.badServerResponse)
    }

    let post = try JSONDecoder().decode(Post.self, from: data)
    return post
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출은 이렇게 합니다:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;Task {
    do {
         // 반환 타입 = await (비동기 작업 실행) 구조를 꼭 기억하자
        let post = try await fetchPost()
        print(post.title)
    } catch {
        print(&quot;에러 발생: \\(error)&quot;)
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자자 다 떠먹여줄게&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 헷갈리기 쉬운 async, await, try await, throws 키워드의 의미와 관계를 중심으로&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. async 키워드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미:&lt;/h3&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;/li&gt;
&lt;li&gt;즉, 함수 내에서 &lt;b&gt;비동기 작업을 await로 호출&lt;/b&gt;할 수 있게 해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;async 함수는 &lt;b&gt;동기 함수에서 직접 호출 불가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;반드시 아래 중 하나로 감싸야 함:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Task { ... } 블록&lt;/li&gt;
&lt;li&gt;다른 async 함수 내부&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. await 키워드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미:&lt;/h3&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;/li&gt;
&lt;li&gt;await은 단독으로 사용할 수 없고, 반드시 async 함수 안에서만 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예:&lt;/h3&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;let (data, response) = try await URLSession.shared.data(for: request)

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; data(for:)는 비동기 메서드이기 때문에 await으로 기다려야 함&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. throws 키워드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미:&lt;/h3&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;/li&gt;
&lt;li&gt;Swift의 오류 처리 방식인 &lt;b&gt;do-try-catch 문법&lt;/b&gt;과 함께 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;try 키워드를 통해 오류 가능성이 있는 함수 호출&lt;/li&gt;
&lt;li&gt;오류가 발생하면 catch로 넘어감&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. try await 키워드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;await 대상이 &lt;b&gt;에러를 던질 수 있는 비동기 함수&lt;/b&gt;일 경우 두 키워드를 &lt;b&gt;함께 사용&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;await만 써선 부족하고, 반드시 try까지 붙여야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황 키워드&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;동기 + 오류 발생 가능&lt;/td&gt;
&lt;td&gt;try&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비동기 + 오류 발생 불가&lt;/td&gt;
&lt;td&gt;await&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비동기 + 오류 발생 가능&lt;/td&gt;
&lt;td&gt;try await&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 전체 흐름 구조&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  함수 정의:&lt;/h3&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func fetchPost() async throws -&amp;gt; Post

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소 의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;async&lt;/td&gt;
&lt;td&gt;비동기 작업 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;throws&lt;/td&gt;
&lt;td&gt;에러 던질 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&amp;gt; Post&lt;/td&gt;
&lt;td&gt;최종 결과로 Post 타입을 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이 함수는 &lt;b&gt;비동기 작업을 기다리고&lt;/b&gt;, &lt;b&gt;에러를 던질 수 있고&lt;/b&gt;, &lt;b&gt;Post 타입 결과를 돌려줌&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  함수 내부:&lt;/h3&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;let (data, response) = try await URLSession.shared.data(for: request)

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URLSession.shared.data(for:): 비동기 함수 + throws&lt;/li&gt;
&lt;li&gt;따라서 &amp;rarr; try await 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  함수 호출:&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;Task {
    do {
        let post = try await fetchPost()
    } catch {
        print(&quot;에러 발생: \\(error)&quot;)
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fetchPost()는 async throws 함수&lt;/li&gt;
&lt;li&gt;따라서 호출은 반드시 try await로, 그리고 do-catch로 감싸야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task { }는 Swift에서 &lt;b&gt;비동기 작업을 시작할 수 있는 스레드-safe한 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async 함수는 일반 함수에서 직접 호출할 수 없기 때문에, &lt;b&gt;비동기 함수 호출을 감싸는 컨테이너의 역할을 담당&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 포인트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황 써야 할 키워드 예시&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;비동기 함수 정의&lt;/td&gt;
&lt;td&gt;async&lt;/td&gt;
&lt;td&gt;func foo() async {}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에러 던질 수 있음&lt;/td&gt;
&lt;td&gt;throws&lt;/td&gt;
&lt;td&gt;func foo() throws {}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에러 + 비동기&lt;/td&gt;
&lt;td&gt;async throws&lt;/td&gt;
&lt;td&gt;func foo() async throws {}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에러나는 동기 호출&lt;/td&gt;
&lt;td&gt;try&lt;/td&gt;
&lt;td&gt;try JSONDecoder().decode(...)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에러 안 나는 비동기 호출&lt;/td&gt;
&lt;td&gt;await&lt;/td&gt;
&lt;td&gt;await delay(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에러 나는 비동기 호출&lt;/td&gt;
&lt;td&gt;try await&lt;/td&gt;
&lt;td&gt;try await URLSession.shared.data(for: request)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;async는 &quot;이 함수는 기다려야 해!&quot;&lt;/li&gt;
&lt;li&gt;await는 &quot;이 작업이 끝날 때까지 기다릴게!&quot;&lt;/li&gt;
&lt;li&gt;throws는 &quot;이 함수는 실패할 수 있어!&quot;&lt;/li&gt;
&lt;li&gt;try는 &quot;실패할 수 있으니 조심해서 실행해!&quot;&lt;/li&gt;
&lt;li&gt;try await는 &quot;실패할 수 있는 비동기 작업을 기다릴게!&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SOPT 4주차 실습 코드로 한번 봐보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 실습 코드는 URLSession, async/await 구조가 섞여 있는 구조임&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;//
//  RegisterService.swift
//  Week_04
//
//  Created by 정정욱 on 5/3/25.
//

import Foundation

// MARK: - RegisterService (Singleton 패턴)
class RegisterService {

    static let shared = RegisterService() // 싱글톤 인스턴스
    private init() {} // 외부에서 인스턴스 생성 방지

    //MARK: 회원가입 요청 바디 생성 함수 - JSON 데이터 생성
    // URL Session에 넣어줄 URL 요청 객체만을 만드는 부분
    func makeRequestBody(loginId: String, password: String, nickName: String) -&amp;gt; Data? {
        do {
            let data = RegisterRequest(
                loginId: loginId,
                password: password,
                nickname: nickName
            )
            let jsonEncoder = JSONEncoder()
            let requestBody = try jsonEncoder.encode(data)
            return requestBody
        } catch {
            print(&quot;  Encoding error: \\(error)&quot;)
            return nil
        }
    }

    // MARK: URLRequest 생성 함수 - URL, 메서드, 헤더, 바디
     // URLRequest 객체 생성 및 설정 (httpMethod,  필요 시 헤더 추가)
    func makeRequest(body: Data?) -&amp;gt; URLRequest {
        let url = URL(string: &quot;http://......&quot;)! 
        var request = URLRequest(url: url) 
        request.httpMethod = &quot;POST&quot; 
        let header = [&quot;Content-Type&quot;: &quot;application/json&quot;] 
        header.forEach {
            request.addValue($0.value, forHTTPHeaderField: $0.key)
        }
        if let body = body {
            request.httpBody = body
        }
        // 이후에 아까 받아온 Body를 넣어줌

        return request
    }

    // MARK: 회원가입 요청 함수 - 서버 통신 
    // - try await을 사용함
    func PostRegisterData(loginId: String, password: String, nickName: String) async throws -&amp;gt; RegisterUserInfo {

        // makeRequestBody 함수를 이용해서, 리퀘스트 바디를 만들어줍니다! 실패할 경우, 아까 NetworkError에서 선언한 오류들을 던지게 됩니다
        guard let body = makeRequestBody(
            loginId: loginId,
            password: password,
            nickName: nickName
        ) else {
            throw NetworkError.requestEncodingError
        }

        let request = self.makeRequest(body: body)
        let (data, response) = try await URLSession.shared.data(for: request)
        dump(request)

        // 응답 유효성 검사
        guard let httpResponse = response as? HTTPURLResponse else {
            throw NetworkError.responseError
        }

        dump(response)

        guard (200...299).contains(httpResponse.statusCode) else {
            throw configureHTTPError(errorCode: httpResponse.statusCode)
        }

        // 문제 없으면 디코딩 수행
        do {
            let decoded = try JSONDecoder().decode(RegisterResponse.self, from: data)
            return decoded.data // 결국 정상 반환시 리턴 값
        } catch {
            print(&quot;디코딩 실패:&quot;, error)
            throw NetworkError.responseError
        }
    }

    private func configureHTTPError(errorCode: Int) -&amp;gt; Error {
        return NetworkError(rawValue: errorCode)
        ?? NetworkError.unknownError
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RegisterService는 &lt;b&gt;URLSession을 async/await 방식으로 사용하는 구조&lt;/b&gt;라서 dataTask(with:) { ... }.resume() 방식의 &lt;b&gt;task 객체가 코드에 드러나지 않음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 URLSession 코드:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let task = URLSession.shared.dataTask(with: request) { data, response, error in
    ...
}
task.resume()

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 지금 사용하신 구조는 Swift의 &lt;b&gt;async/await 기반의 URLSession.shared.data(for:)&lt;/b&gt; 를 사용한 방식&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;let (data, response) = try await URLSession.shared.data(for: request)

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 건:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data(for:)는 내부적으로 URLSessionDataTask를 생성하고 resume()까지 자동으로 처리해줍니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 평소에 보던 task는 &lt;b&gt;암묵적으로 생성되고 실행&lt;/b&gt;되기 때문에 코드에서 task 객체가 직접적으로 보이지 않음&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RegisterService 흐름 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 RegisterService의 네트워크 흐름을 요약한 순서입니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;makeRequestBody(...)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Encodable 모델을 JSON 형식으로 Data로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;makeRequest(...)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;POST, Header 설정, body 삽입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;URLSession.shared.data(for: request)
&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;내부에서 DataTask가 자동 생성 및 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;response에서 status code 검사&lt;/li&gt;
&lt;li&gt;성공 시 JSON &amp;rarr; RegisterResponse로 디코딩&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고로 내부에서 무슨 일이 일어나는가?&lt;/h2&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;let (data, response) = try await URLSession.shared.data(for: request)

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한 줄은 다음을 포함합니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;URLSessionDataTask 생성&lt;/li&gt;
&lt;li&gt;resume() 호출&lt;/li&gt;
&lt;li&gt;응답이 올 때까지 suspension&lt;/li&gt;
&lt;li&gt;응답 오면 data, response 리턴 or throw&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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://1000one.tistory.com/58&quot;&gt;https://1000one.tistory.com/58&lt;/a&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;나는 이번 API 통신 과제를 어떻게 했는가&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 PR &lt;a href=&quot;https://github.com/AT-SOPT-iOS/Jeonguk_practice/pull/2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/AT-SOPT-iOS/Jeonguk_practice/pull/2&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1747138264578&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Feat/#1 network learning by jeonguk29 &amp;middot; Pull Request #2 &amp;middot; AT-SOPT-iOS/Jeonguk_practice&quot; data-og-description=&quot;  What is the PR? 세미나때 배운 네트워크 통신 내용을 기반으로 API 네트워크 레이어 추상화 구조 구현   Changes API 네트워크 레이어 추상화 구조 구현 로그인, 회원가입 API 연결 로그인 id KeychainM&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/AT-SOPT-iOS/Jeonguk_practice/pull/2&quot; data-og-url=&quot;https://github.com/AT-SOPT-iOS/Jeonguk_practice/pull/2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cBXzyr/hyYTbOnUEp/jjkEZVN7AjzcUEPwblCV51/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/iEt2D/hyYTjyRTEv/FGUqBbU5ulVK6qBExCBiAK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/AT-SOPT-iOS/Jeonguk_practice/pull/2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/AT-SOPT-iOS/Jeonguk_practice/pull/2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cBXzyr/hyYTbOnUEp/jjkEZVN7AjzcUEPwblCV51/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/iEt2D/hyYTjyRTEv/FGUqBbU5ulVK6qBExCBiAK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;Feat/#1 network learning by jeonguk29 &amp;middot; Pull Request #2 &amp;middot; AT-SOPT-iOS/Jeonguk_practice&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  What is the PR? 세미나때 배운 네트워크 통신 내용을 기반으로 API 네트워크 레이어 추상화 구조 구현   Changes API 네트워크 레이어 추상화 구조 구현 로그인, 회원가입 API 연결 로그인 id KeychainM&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;1444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biVbGN/btsNWr9rU0x/hrbHJpkyBSzkFONJeE6tC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biVbGN/btsNWr9rU0x/hrbHJpkyBSzkFONJeE6tC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biVbGN/btsNWr9rU0x/hrbHJpkyBSzkFONJeE6tC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiVbGN%2FbtsNWr9rU0x%2FhrbHJpkyBSzkFONJeE6tC1%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;467&quot; height=&quot;735&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;1444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqk94i/btsNXoK8PQn/OuFkyq0djg7exNZz819Dd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqk94i/btsNXoK8PQn/OuFkyq0djg7exNZz819Dd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqk94i/btsNXoK8PQn/OuFkyq0djg7exNZz819Dd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqk94i%2FbtsNXoK8PQn%2FOuFkyq0djg7exNZz819Dd1%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;1468&quot; height=&quot;350&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 부분을 좀 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 HTTP 통신을 진행하기 위해 네트워크 관련 작업을 기능별, 책임별로 나눴고 추상화를 통해 네트워크 통신을 하도록 만들었음 URLSession 만을 사용하라는 요구사항이 있었는데 사실 해당 구조는 얼핏 본다면 Moya 라이브러리의 구조와 비슷하다고 볼 수도 있음&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&quot;역할별로 나눴다&quot;는 건 무슨 말?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 관련 작업을 &lt;b&gt;기능별, 책임별로 나눠서 관리&lt;/b&gt;한다는 뜻&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 하나의 파일에 모든 걸 몰아넣는 게 아니라, &lt;b&gt;누가 뭘 할지 명확하게 나눴음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할 파일 하는 일&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;어떤 API를 요청할지 정리&lt;/td&gt;
&lt;td&gt;Endpoint.swift&lt;/td&gt;
&lt;td&gt;URL 경로와 메서드 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 메서드 정의&lt;/td&gt;
&lt;td&gt;HTTPMethod.swift&lt;/td&gt;
&lt;td&gt;GET, POST 등을 enum으로 표현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실제 요청 실행&lt;/td&gt;
&lt;td&gt;APIService.swift&lt;/td&gt;
&lt;td&gt;URLSession 사용해서 요청 보내기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;로그인/회원가입 등 호출&lt;/td&gt;
&lt;td&gt;AuthService.swift&lt;/td&gt;
&lt;td&gt;위 기능들을 모아서 비즈니스로직 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이게 바로 &quot;&lt;b&gt;역할별 분리&lt;/b&gt;&quot;&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;일단 네트워크 에러 정의 코드&lt;/p&gt;
&lt;pre id=&quot;code_1747138003464&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum NetworkError: Int, Error, CustomStringConvertible {
    var description: String { self.errorDescription }
    case requestEncodingError
    case responseDecodingError = 1
    case responseError
    case unknownError
    case loginFailed = 400
    case internalServerError = 500
    case notFoundError = 404
    case invalidURL

    var errorDescription: String {
        switch self {
        case .loginFailed: return &quot;로그인에 실패하였습니다.&quot;
        case .requestEncodingError: return &quot;REQUEST_ENCODING_ERROR&quot;
        case .responseError: return &quot;RESPONSE_ERROR&quot;
        case .responseDecodingError: return &quot;RESPONSE_DECODING_ERROR&quot;
        case .unknownError: return &quot;UNKNOWN_ERROR&quot;
        case .internalServerError: return &quot;500:INTERNAL_SERVER_ERROR&quot;
        case .notFoundError: return &quot;404:NOT_FOUND_ERROR&quot;
        case .invalidURL: return &quot;잘못된 URL 입니다.&quot;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설명&lt;hr data-ke-style=&quot;style1&quot; /&gt;전체 구조이 한 줄에 들어있는 뜻키워드 의미
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;enum&lt;/td&gt;
&lt;td&gt;열거형: 정해진 케이스 집합을 나타냄&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;: Int&lt;/td&gt;
&lt;td&gt;각 케이스에 숫자(Int) 원시값을 부여할 수 있게 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;td&gt;Swift의 표준 에러 처리용 프로토콜 채택 (try/catch용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CustomStringConvertible&lt;/td&gt;
&lt;td&gt;description 문자열을 커스터마이징할 수 있게 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;열거형 케이스 정의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;case requestEncodingError &amp;rarr; rawValue가 0 (기본값)&lt;/li&gt;
&lt;li&gt;case responseDecodingError = 1 &amp;rarr; 명시적으로 1&lt;/li&gt;
&lt;li&gt;이후는 자동 증가됨 (단, 400, 404, 500처럼 직접 지정한 케이스도 있음)&lt;/li&gt;
&lt;/ul&gt;
즉:&lt;hr data-ke-style=&quot;style1&quot; /&gt;Error 프로토콜이 뭐야?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Swift의 try, catch, throw 구문에서 사용할 수 있는 &quot;에러 타입&quot;으로 만들겠다는 뜻입니다.&lt;/li&gt;
&lt;li&gt;즉, 아래처럼 사용할 수 있게 됨:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;throw NetworkError.loginFailed

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;CustomStringConvertible은 뭐야?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;print(error) 또는 print(&quot;\\(error)&quot;)처럼 출력할 때 자동으로 description을 사용하게 해주는 프로토콜이에요.&lt;/li&gt;
&lt;li&gt;description은 String 타입으로 반환되어야 함&lt;/li&gt;
&lt;li&gt;여기서 description은 self.errorDescription을 그대로 가져다 씀&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;그럼 self.errorDescription은 뭐야?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이건 &lt;b&gt;계산 속성(computed property)&lt;/b&gt; 이라고 해요.&lt;/li&gt;
&lt;li&gt;description은 실제로는 errorDescription이라는 다른 커스텀 프로퍼티를 &lt;b&gt;간접 참조&lt;/b&gt;하고 있는 셈이에요.&lt;/li&gt;
&lt;/ul&gt;
즉:이렇게 사용할 수 있음!&lt;hr data-ke-style=&quot;style1&quot; /&gt;열거형 안에 변수/함수를 넣을 수 있나?&amp;rarr; enum은 거의 struct와 비슷하게 행동할 수 있는 강력한 타입이에요.&lt;hr data-ke-style=&quot;style1&quot; /&gt;목적:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서버 상태 코드나 앱 내부 네트워크 오류를 하나의 타입(NetworkError)로 표현&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;throw와 catch에서 오류를 처리할 수 있도록 Error 채택&lt;/li&gt;
&lt;li&gt;로그에 출력하거나 사용자에게 보여줄 메시지를 깔끔하게 만들기 위해 description 정의&lt;/li&gt;
&lt;li&gt;Int 원시값을 주어서 상태코드 대응도 쉽게 함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;enum NetworkError: Int, Error&lt;/td&gt;
&lt;td&gt;에러 처리용 열거형 (상태코드 대응)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CustomStringConvertible&lt;/td&gt;
&lt;td&gt;출력 시 설명 텍스트 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;description: String&lt;/td&gt;
&lt;td&gt;실제로는 errorDescription을 가져옴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;var errorDescription: String&lt;/td&gt;
&lt;td&gt;각 케이스에 대한 사용자 친화 메시지 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rawValue&lt;/td&gt;
&lt;td&gt;HTTP 상태코드와 연동 가능 (예: 400, 404, 500 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;HTTPMethod 정의 코드&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;enum HTTPMethod: String {
    case GET, POST, PATCH, PUT, DELETE
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열거형을 통해 앱에서 사용할 API의 경로(path)와 HTTP 메서드(method)를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하고 구조화한 코드, 서버와 통신할 때 필요한 &amp;ldquo;주소&amp;rdquo;와 &amp;ldquo;방식&amp;rdquo;을 정리해놓은 API 전화번호 부 역할&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;//
//  Endpoint.swift
//  Week_04
//
//  Created by 정정욱 on 5/6/25.
//

import Foundation

enum Endpoint {

    enum Auth {
        case signup
        case signin

        var path: String {
            switch self {
            case .signup: return &quot;/api/v1/auth/signup&quot;
            case .signin: return &quot;/api/v1/auth/signin&quot;
            }
        }

        var method: HTTPMethod {
            switch self {
            case .signup, .signin: return .POST
            }
        }
    }

    enum User {
        case me
        case all(keyword: String?)
        case update

        var path: String {
            switch self {
            case .me: return &quot;/api/v1/users/me&quot;
            case .all(let keyword):
                if let keyword = keyword, !keyword.isEmpty {
                    // 인코딩시 한글 깨짐 방지
                    return &quot;/api/v1/users?keyword=\\(keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? &quot;&quot;)&quot;
                } else {
                    return &quot;/api/v1/users&quot;
                }
            case .update: return &quot;/api/v1/users&quot;
            }
        }

        var method: HTTPMethod {
            switch self {
            case .me, .all: return .GET
            case .update: return .PATCH
            }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 APIService 클래스는 앱의 모든 네트워크 요청을 담당하는 &lt;b&gt;중앙 통신 관리자&lt;/b&gt; 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;모든 API 요청을 하나의 함수로 통합하고, 재사용성을 높임&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;//
//  APIService.swift
//  Week_04
//
//  Created by 정정욱 on 5/6/25.
//

import Foundation

final class APIService {
    static let shared = APIService()
    private init() {}

    private let baseURL = &quot;&amp;lt;http://api.atsopt-4.site&amp;gt;&quot;

    func request(
        path: String,
        method: HTTPMethod,
        headers: [String : String]? = nil,
        body: Data? = nil,
        responseType: T.Type
    ) async throws -&amp;gt; T {
        guard let url = URL(string: baseURL + path) else {
            throw NetworkError.invalidURL
        }

        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        request.setValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)

        if let headers {
            for (key, value) in headers {
                request.setValue(value, forHTTPHeaderField: key)
            }
        }

        request.httpBody = body

        let (data, response) = try await URLSession.shared.data(for: request)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw NetworkError.responseError
        }

        guard (200...299).contains(httpResponse.statusCode) else {
            throw configureHTTPError(errorCode: httpResponse.statusCode)
        }

        do {
            return try JSONDecoder().decode(T.self, from: data)
        } catch {
            throw NetworkError.responseDecodingError
        }
    }

    private func configureHTTPError(errorCode: Int) -&amp;gt; Error {
        return NetworkError(rawValue: errorCode)
        ?? NetworkError.unknownError
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사용할때 AuthService처럼 사용&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 역할 요약&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;final class APIService

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;final: 상속 금지 (다른 클래스가 이 클래스를 상속하지 못하도록)&lt;/li&gt;
&lt;li&gt;shared: &lt;b&gt;싱글톤(Singleton) 패턴&lt;/b&gt; &amp;mdash; 앱 전역에서 하나의 인스턴스를 공유&lt;/li&gt;
&lt;li&gt;핵심 메서드: request(...)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 이 클래스는 &amp;ldquo;&lt;b&gt;모든 HTTP 요청을 수행하고 응답을 디코딩해서 반환해주는 역할&lt;/b&gt;&amp;rdquo;을 해요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1. 싱글톤 정의&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;static let shared = APIService()
private init() {}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부에서 new APIService()처럼 새로 만들지 못하게 private init()&lt;/li&gt;
&lt;li&gt;오직 APIService.shared로만 접근 가능하게 함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  2. 기본 URL 설정&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;private let baseURL = &quot;&amp;lt;http://apsite&amp;gt;&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 API 경로에 앞에 붙는 &lt;b&gt;공통 서버 주소&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;실제 요청 시 baseURL + path로 완성된 URL 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  3. 핵심 메서드: request(...)&lt;/h3&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func request&amp;lt;T: Decodable&amp;gt;(
    path: String,
    method: HTTPMethod,
    headers: [String : String]? = nil,
    body: Data? = nil,
    responseType: T.Type
) async throws -&amp;gt; T

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;매개변수 설명:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수 의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;path&lt;/td&gt;
&lt;td&gt;Endpoint에서 넘겨준 경로 (예: &quot;/api/v1/auth/signup&quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;method&lt;/td&gt;
&lt;td&gt;GET, POST, PATCH 등 (HTTPMethod enum)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;headers&lt;/td&gt;
&lt;td&gt;추가로 붙일 HTTP 헤더&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;body&lt;/td&gt;
&lt;td&gt;HTTP 요청 body (JSON 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;responseType&lt;/td&gt;
&lt;td&gt;디코딩할 모델 타입 (예: RegisterResponse.self)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  4. URL 생성 및 유효성 검사&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;guard let url = URL(string: baseURL + path) else {
    throw NetworkError.invalidURL
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열을 URL 객체로 만들 수 없는 경우 &amp;rarr; 오류 던짐 (invalidURL)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  5. URLRequest 설정&lt;/h3&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;var request = URLRequest(url: url)
request.httpMethod = method.rawValue
request.setValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 메서드 설정 (&quot;GET&quot;, &quot;POST&quot; 등)&lt;/li&gt;
&lt;li&gt;기본 Content-Type 헤더 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  6. 추가 헤더 처리&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;if let headers {
    for (key, value) in headers {
        request.setValue(value, forHTTPHeaderField: key)
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Authorization, Accept-Language 등 다른 헤더를 동적으로 추가할 수 있도록 확장성 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  7. HTTP Body 설정&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;request.httpBody = body

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 POST나 PATCH 같은 요청은 서버에 데이터를 보내야 하므로 body 필요&lt;/li&gt;
&lt;li&gt;미리 JSONEncoder().encode(...) 해서 넘겨줘야 함&lt;/li&gt;
&lt;li&gt;요청 객체 즉 Request 객체를 외부에서 받아 삽입&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  8. URLSession 비동기 요청 실행&lt;/h3&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;let (data, response) = try await URLSession.shared.data(for: request)

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 HTTP 통신 발생&lt;/li&gt;
&lt;li&gt;async/await를 사용하여 비동기 응답 대기&lt;/li&gt;
&lt;li&gt;오류 발생 시 바로 throw&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  9. 응답 검증&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;guard let httpResponse = response as? HTTPURLResponse else {
    throw NetworkError.responseError
}
guard (200...299).contains(httpResponse.statusCode) else {
    throw configureHTTPError(errorCode: httpResponse.statusCode)
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;response가 HTTP 형식이 아닐 경우 오류&lt;/li&gt;
&lt;li&gt;상태 코드가 2xx 범위를 벗어나면 NetworkError로 변환 후 throw&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  10. 디코딩 처리&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;return try JSONDecoder().decode(T.self, from: data)

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버로부터 받은 JSON 데이터를 우리가 원하는 모델 타입 T로 디코딩&lt;/li&gt;
&lt;li&gt;실패 시 .responseDecodingError 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  11. 상태 코드에 따른 오류 매핑&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private func configureHTTPError(errorCode: Int) -&amp;gt; Error {
    return NetworkError(rawValue: errorCode)
    ?? NetworkError.unknownError
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: 404 &amp;rarr; .notFoundError, 500 &amp;rarr; .internalServerError&lt;/li&gt;
&lt;li&gt;정의되지 않은 오류는 .unknownError로 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 예시&lt;/h2&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;let result = try await APIService.shared.request(
    path: Endpoint.Auth.signup.path,
    method: Endpoint.Auth.signup.method,
    body: encodedBody,
    responseType: RegisterResponse.self
)

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 서버에서 받은 응답이 RegisterResponse 타입으로 자동 변환되어 반환됨&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사용할때 AuthService처럼 사용&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;//
//  AuthService.swift
//  Week_04
//
//  Created by 정정욱 on 5/6/25.
//

import Foundation

final class AuthService {
    static let shared = AuthService()

    func signup(
        loginId: String,
        password: String,
        nickName: String
    ) async throws -&amp;gt; RegisterUserInfo {

        // 1. Request body 모델 생성 및 인코딩
        let requestModel = RegisterRequest(
            loginId: loginId,
            password: password,
            nickname: nickName
        )
        let encodedBody = try JSONEncoder().encode(requestModel)

        // 2. APIService 통해 요청
        return try await APIService.shared.request(
            path: Endpoint.Auth.signup.path,
            method: Endpoint.Auth.signup.method,
            body: encodedBody,
            responseType: RegisterResponse.self
        ).data // 라서 반환 타입이 RegisterResponseBody임
    }

    func signin(
        loginId: String,
        password: String
    ) async throws -&amp;gt; LoginUserID {

        let requestModel = LoginRequest(
            loginId: loginId,
            password: password
        )
        let encodedBody = try JSONEncoder().encode(requestModel)

        return try await APIService.shared.request(
            path: Endpoint.Auth.signin.path,
            method: Endpoint.Auth.signin.method,
            body: encodedBody,
            responseType: LoginResponse.self
        ).data
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 실제 앱에서 사용자가 &lt;b&gt;회원가입(signup)&lt;/b&gt; 또는 &lt;b&gt;로그인(signin)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 보낼 때 호출되는 &lt;b&gt;비즈니스 로직 중심의 서비스 클래스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;API를 직접 호출하는 게 아니라 그 과정을 깔끔하게 감싸주는 역할&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View에서 이렇게 사용&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt; @objc private func loginButtonTap() {
        Task {
            do {
                let response = try await AuthService.shared.signin(loginId: self.loginId,
                                                                   password: self.password)

                //  Keychain 자체는 Int를 직접 저장할 수 없고, Data 또는 String 형식만 저장 가능
                let userId = response.userID
                let userIdString = String(userId)
                let saved = KeychainManager.shared.save(key: &quot;userId&quot;, value: userIdString)
                print(&quot;Keychain 저장 성공 여부: \\(saved)&quot;)

                let alert = UIAlertController(
                    title: &quot;로그인 성공&quot;,
                    message: &quot;토큰 대용 ID로 로그인 성공 (ID: \\(userIdString))&quot;,
                    preferredStyle: .alert
                )

                let okAction = UIAlertAction(title: &quot;확인&quot;, style: .default)
                alert.addAction(okAction)
                self.present(alert, animated: true)
            } catch {
                let alert = UIAlertController(
                    title: &quot;로그인 실패&quot;,
                    message: error.localizedDescription,
                    preferredStyle: .alert
                )
                let okAction = UIAlertAction(title: &quot;확인&quot;, style: .default)
                alert.addAction(okAction)
                self.present(alert, animated: true)

                print(&quot;로그인 에러:&quot;, error)
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. &quot;추상화했다&quot;는 건 무슨 말?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 것을 단순하게 &lt;b&gt;겉으로만 보이게 감싼다&lt;/b&gt;는 뜻이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 사용자는 AuthService.shared.signup(...) 이렇게만 쓰면 되지만,&lt;/p&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;URL 경로를 가져오고 (Endpoint)&lt;/li&gt;
&lt;li&gt;HTTPMethod를 설정하고 (POST)&lt;/li&gt;
&lt;li&gt;JSON으로 인코딩하고&lt;/li&gt;
&lt;li&gt;URLRequest를 만들고&lt;/li&gt;
&lt;li&gt;URLSession으로 요청하고&lt;/li&gt;
&lt;li&gt;응답을 디코딩하고&lt;/li&gt;
&lt;li&gt;에러를 구분해서 처리&lt;/li&gt;
&lt;/ol&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;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 APIService가 이걸 &quot;추상화&quot;해서 감춰줬기 때문이에요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. &quot;클린 아키텍처 기반이다&quot;란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처(Clean Architecture)의 핵심은 &quot;관심사의 분리&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;한 파일/모듈이 하나의 책임만 가지게 만드는 구조&lt;/b&gt;죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원칙 네트워크 구조 예시&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;하나의 컴포넌트는 하나의 책임만&lt;/td&gt;
&lt;td&gt;APIService: 요청 전송, Endpoint: URL 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비즈니스 로직은 네트워크와 분리&lt;/td&gt;
&lt;td&gt;AuthService: 비즈니스 로직 (URLSession 직접 몰라도 됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;테스트 가능성 향상&lt;/td&gt;
&lt;td&gt;각 레이어가 분리돼 있어 모킹 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비유로 쉽게 설명해볼게요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;편의점에서 커피 주문하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할 사람/행동 코드에서 대응하는 것&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;손님&lt;/td&gt;
&lt;td&gt;&quot;커피 하나 주세요&quot;&lt;/td&gt;
&lt;td&gt;AuthService.signup() 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;점원&lt;/td&gt;
&lt;td&gt;커피 주문 받아서 기계 작동&lt;/td&gt;
&lt;td&gt;APIService&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기계&lt;/td&gt;
&lt;td&gt;물, 커피, 컵을 조합해서 커피 제조&lt;/td&gt;
&lt;td&gt;URLSession, JSON 인코딩, 디코딩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메뉴판&lt;/td&gt;
&lt;td&gt;어떤 커피가 있는지 정리&lt;/td&gt;
&lt;td&gt;Endpoint, HTTPMethod&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  손님은 &quot;커피 주세요&quot;만 하면 되죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부에서는 복잡한 과정이 일어나지만, &lt;b&gt;깔끔하게 감춰져 있어요&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 바로 &lt;b&gt;추상화&lt;/b&gt;고, &lt;b&gt;역할 분리&lt;/b&gt;, 그리고 &lt;b&gt;클린한 구조&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념 의미 적용된 코드&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;네트워크 레이어&lt;/td&gt;
&lt;td&gt;서버와 통신하는 부분&lt;/td&gt;
&lt;td&gt;APIService, AuthService 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;역할 분리&lt;/td&gt;
&lt;td&gt;각 기능을 나눠서 설계&lt;/td&gt;
&lt;td&gt;Endpoint, HTTPMethod, APIService, AuthService&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;추상화&lt;/td&gt;
&lt;td&gt;복잡한 내부 로직을 감추고 단순하게 사용&lt;/td&gt;
&lt;td&gt;AuthService.signup(...)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;클린 아키텍처&lt;/td&gt;
&lt;td&gt;관심사 분리 + 재사용성 + 테스트 용이성&lt;/td&gt;
&lt;td&gt;전체 구조가 이 철학에 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>async/await 정리</category>
      <category>ios 네트워크 설계</category>
      <category>ios 네트워크 셋팅</category>
      <category>ios 클린아키텍처</category>
      <category>urlsession async/await 차이</category>
      <category>urlsession 정리</category>
      <author>개발자 aloe</author>
      <guid isPermaLink="true">https://aloe-study.tistory.com/235</guid>
      <comments>https://aloe-study.tistory.com/235#entry235comment</comments>
      <pubDate>Tue, 13 May 2025 21:13:00 +0900</pubDate>
    </item>
    <item>
      <title>iOS : SwiftUI + TCA 설명 Architecture와 Design Pattern 차이</title>
      <link>https://aloe-study.tistory.com/234</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;저는 최근 &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #555555; text-align: start;&quot;&gt;Clean architecture&lt;/span&gt;를 프로젝트에 도입하기 위해서 VIP 패턴을 사용하여 개발을 하고 있습니다, 그러다 요즘은 너무나 많이 사용하는 TCA가 궁금해져서 공부를 해보았는데요&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TCA란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 개발에서 언급되는 TCA는 The Composable Architecture의 약자로, Swift 언어를 사용한 iOS 앱 개발에서 점점 더 많이 사용되고 있는 아키텍처 패턴입니다. The Composable Architecture는 Point-Free라는 개발자가 설계한 라이브러리로, 애플리케이션의 상태 관리, 사이드 이펙트 처리, 모듈성, 테스트 가능성을 개선하는 데 중점을 둡니다.&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;TCA를 사용하는 이유:&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;상태 관리의 일관성:&lt;/b&gt; TCA는 앱의 상태를 중앙에서 관리합니다. 이를 통해 여러 UI 컴포넌트가 동일한 상태를 일관되게 공유하고 업데이트할 수 있습니다. Redux와 유사한 패턴으로, 상태 변화가 단방향으로 이루어지며 예측 가능해집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사이드 이펙트 관리:&lt;/b&gt; 네트워크 요청이나 데이터베이스 접근 등 사이드 이펙트를 명확하게 처리할 수 있도록 도와줍니다. 사이드 이펙트는 Effect라는 타입으로 캡슐화되며, 이를 통해 코드의 가독성과 유지보수성을 높일 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모듈화 및 재사용성:&lt;/b&gt; TCA는 애플리케이션의 기능을 작은 모듈로 나눌 수 있게 해줍니다. 이러한 모듈은 독립적으로 개발, 테스트, 유지보수할 수 있으며, 다른 프로젝트나 앱에서도 재사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 가능성:&lt;/b&gt; TCA는 순수 함수와 데이터 기반 상태 관리를 중심으로 설계되어 있어, 테스트가 용이합니다. 애플리케이션의 상태와 액션을 쉽게 시뮬레이션하고, 사이드 이펙트를 테스트할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SwiftUI와의 높은 호환성:&lt;/b&gt; TCA는 SwiftUI의 데이터 흐름과 자연스럽게 어우러집니다. SwiftUI의 선언적 UI 패턴과 TCA의 상태 관리 및 사이드 이펙트 처리 메커니즘이 잘 맞아 떨어집니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCA는 iOS 앱 개발에서 복잡한 상태 관리, 모듈화, 테스트 가능성을 개선하고자 하는 개발자들에게 유용한 도구입니다. 특히 SwiftUI와의 결합에서 강력한 효과를 발휘해, 점점 더 많은 개발자들이 TCA를 선택하고 있습니다.&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-origin-width=&quot;657&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/162MC/btsI0xGLnyb/QSNgHKOIhKcU4sF8Mh90zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/162MC/btsI0xGLnyb/QSNgHKOIhKcU4sF8Mh90zK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/162MC/btsI0xGLnyb/QSNgHKOIhKcU4sF8Mh90zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F162MC%2FbtsI0xGLnyb%2FQSNgHKOIhKcU4sF8Mh90zK%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;657&quot; height=&quot;359&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;359&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;Architecture와 Design Pattern 차이를 아시나요? 해당 개념을 정리하지 않고 공부를 하다보니 머리가 아프고 햇갈리는게 만더라구요 (저도 알고 싶지 않았습니다...)&amp;nbsp;&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;&lt;b&gt;소프트웨어 아키텍처&lt;/b&gt;와 &lt;b&gt;디자인 패턴&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;개념을&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;&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;h3 data-ke-size=&quot;size23&quot;&gt;1. 소프트웨어 아키텍처 (Software Architecture)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;아키텍처&lt;/b&gt;는 소프트웨어 시스템의 전반적인 구조를 정의하는 고수준(한 차원 더 높은 단계)의 설계&lt;/span&gt;입니다. 이는 시스템의 주요 구성 요소와 그 상호 작용 방식을 정의하며, 프로젝트 전반에 걸쳐 사용됩니다. 아키텍처는 시스템의 구조, 의사소통 방식, 모듈화, 의존성, 확장성 등을 고려합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 특징:&lt;/h4&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;span style=&quot;background-color: #ffc1c8;&quot;&gt;아키텍처는 시스템의 전반적인 구조와 큰 그림을 그립니다. 예를 들어, 클린 아키텍처, 레이어드 아키텍처, 마이크로서비스 아키텍처 등이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모듈 간의 관계&lt;/b&gt;: 시스템을 구성하는 주요 모듈들이 어떻게 상호작용하는지 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수성&lt;/b&gt;: 시스템이 변경되거나 확장될 때 아키텍처는 그 영향도를 최소화하도록 설계됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능과 확장성&lt;/b&gt;: 아키텍처는 시스템의 성능 및 확장성을 고려하여 설계됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 이유:&lt;/h4&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;/li&gt;
&lt;li&gt;&lt;b&gt;의사소통의 명확성&lt;/b&gt;: 팀 간의 협업을 촉진하며, 모든 팀원이 시스템의 큰 그림을 이해할 수 있도록 돕습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;품질 향상&lt;/b&gt;: 일관된 아키텍처는 시스템의 안정성과 품질을 높여줍니다.&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;2. 디자인 패턴 (Design Pattern)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;디자인 패턴&lt;/b&gt;은 소프트웨어 설계에서 자주 발생하는 문제를 해결하기 위한 반복 가능한 해결책&lt;/span&gt;입니다. 이는 특정 문제를 해결하기 위한 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;구체적인 설계 방법&lt;/b&gt;을 제공합니다. 디자인 패턴은 아키텍처보다 낮은 수준에서, 코드의 구조와 구현에 집중합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 특징:&lt;/h4&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;/li&gt;
&lt;li&gt;&lt;b&gt;재사용 가능성&lt;/b&gt;: 패턴은 반복적으로 사용 가능한 해결책을 제공하여, 코드의 재사용성을 높입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명확한 용어&lt;/b&gt;: 패턴은 소프트웨어 설계에 대한 공통 언어를 제공하여, 개발자 간의 의사소통을 용이하게 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 이유:&lt;/h4&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;/li&gt;
&lt;li&gt;&lt;b&gt;코드 품질 향상&lt;/b&gt;: 디자인 패턴을 사용하면 코드의 명확성과 유지보수성을 높일 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효율성&lt;/b&gt;: 디자인 패턴은 이미 검증된 해결책이므로, 개발 시간을 절약하고 오류를 줄일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 아키텍처와 패턴의 차이점&lt;/h3&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;아키텍처&lt;/b&gt;는 시스템 전체의 구조를 다루며, 전반적인 설계와 관련된 의사결정을 포함합니다. 이는 시스템의 모든 모듈과 그 관계를 포괄합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;디자인 패턴&lt;/b&gt;은 특정 문제를 해결하기 위한 특정 모듈이나 코드 수준의 방법을 다룹니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수준의 차이&lt;/b&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;/li&gt;
&lt;li&gt;&lt;b&gt;디자인 패턴&lt;/b&gt;은 상대적으로 낮은 수준의 구현 세부 사항에 초점을 맞춥니다. 코딩 과정에서의 문제 해결 방법을 제공하며, 구현에 직접적으로 영향을 미칩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유연성&lt;/b&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;/li&gt;
&lt;li&gt;&lt;b&gt;디자인 패턴&lt;/b&gt;은 다양한 시점에서 필요에 따라 적용될 수 있으며, 특정 문제에 대한 해결책을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. &lt;span style=&quot;background-color: #f6e199;&quot;&gt;요약 및 결론&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아키텍처&lt;/b&gt;는 소프트웨어 시스템의 전반적인 구조와 설계를 다루며, &lt;b&gt;디자인 패턴&lt;/b&gt;은 특정 문제에 대한 반복 가능한 설계 방법을 제공합니다. 아키텍처는 시스템의 큰 그림을 그리고, 패턴은 그 안에서 개별적인 문제를 해결하는 구체적인 방법을 제시합니다.&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;TCA도 앱을 개발하는데 있어 전반적인 구조와 큰 그림을 그리기 위해 설계되었다고 보시면 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처와 TCA를 비교해볼까요?&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;u&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;TCA(The Composable Architecture)와 같은 아키텍처는 명확한 구조와 문서가 제공되지만, 클린 아키텍처(Clean Architecture)는 추상적인 개념을 다루기 때문에 이를 구체화하는 방법으로 VIP(VIPER) 패턴이나 다른 디자인 패턴을 사용합니다. 이를 통해 클린 아키텍처의 원칙을 실제 코드로 구현할 수 있습니다.&lt;/span&gt;&lt;/u&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;하지만 TCA는 공식문서가 명확하게 있기때문에 보고 이에 맞춰 구현을 하면 됩니다. TCA도 단방향 데이터 흐름 (Unidirectional Data Flow)이라는 것을 사용하는데 밑에서 상세하게 설명하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #fafafa; color: #333333;&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;이제 이를 좀 더 구체적으로 설명해드릴게요.&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;a href=&quot;https://github.com/pointfreeco/swift-composable-architecture&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/pointfreeco/swift-composable-architecture&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723185972102&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - pointfreeco/swift-composable-architecture: A library for building applications in a consistent and understandable way, &quot; data-og-description=&quot;A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind. - pointfreeco/swift-composable-architecture&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/pointfreeco/swift-composable-architecture&quot; data-og-url=&quot;https://github.com/pointfreeco/swift-composable-architecture&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bnMeST/hyWKvWOaaK/kt4e9GR5LYPmAT8X5JNdi0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/pointfreeco/swift-composable-architecture&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/pointfreeco/swift-composable-architecture&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bnMeST/hyWKvWOaaK/kt4e9GR5LYPmAT8X5JNdi0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;GitHub - pointfreeco/swift-composable-architecture: A library for building applications in a consistent and understandable way,&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind. - pointfreeco/swift-composable-architecture&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.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;클린아키텍처는 명확한 가이드는 따로 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;1. 클린 아키텍처의 개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처는 소프트웨어 설계 원칙을 기반으로, 애플리케이션을 모듈화하고 책임을 명확하게 나누어 유지보수성과 확장성을 높이는 것을 목표로 합니다. 이 아키텍처는 **의존성 역전 원칙(DIP)**과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;단일 책임 원칙(SRP)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;등을 중심으로 설계되며, 애플리케이션의 각 레이어가 서로 독립적으로 동작할 수 있도록 합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;클린 아키텍처의 주요 레이어:&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;엔티티(Entity)&lt;/b&gt;: 비즈니스 로직과 핵심 규칙을 담고 있습니다. 이들은 애플리케이션의 핵심 기능을 구현하며, 외부 세계와 독립적으로 존재합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유스케이스(Use Case, 또는 인터랙터)&lt;/b&gt;: 애플리케이션의 특정 기능이나 작업 흐름을 담당합니다. 이 레이어는 엔티티와 상호작용하며, 비즈니스 로직을 실행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인터페이스 어댑터(Interface Adapters)&lt;/b&gt;: 유스케이스와 외부 시스템(예: UI, 데이터베이스, 웹 API) 간의 변환을 담당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프레임워크 및 드라이버(Frameworks &amp;amp; Drivers)&lt;/b&gt;: 실제 구현(예: UI, 데이터베이스 등)을 담당하며, 시스템의 외곽에 위치합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 레이어 구조는 모듈화된 코드를 작성할 수 있게 해주며, 각 레이어는 독립적으로 테스트 및 유지보수할 수 있습니다. 그러나 클린 아키텍처 자체는 추상적인 개념이기 때문에, 이를 구체적으로 구현하려면 디자인 패턴이 필요합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;2. 클린 아키텍처는 추상적이다!!! 구현을 위한 디자인 패턴: VIP와 VIPER&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처의 추상적인 개념을 실제 코드로 구현하기 위해, 다양한 디자인 패턴이 사용됩니다. 특히, iOS 개발에서 VIP와 VIPER 패턴이 클린 아키텍처를 구현하는 데 자주 사용됩니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;VIP 패턴 (View-Interactor-Presenter):&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;View&lt;/b&gt;: 사용자 인터페이스(UI)를 담당합니다. 사용자의 입력을 받아 인터랙터에게 전달하고, 프레젠터로부터 전달받은 데이터를 화면에 표시합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Interactor&lt;/b&gt;: 애플리케이션의 비즈니스 로직을 처리합니다. 유스케이스를 구현하며, View와 Presenter 간의 데이터를 중계합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Presenter&lt;/b&gt;: 데이터를 포맷하고, 이를 View에 전달합니다. 또한, 인터랙터로부터 받은 데이터를 View에 알맞게 변환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VIP 패턴은 각 모듈에 명확한 역할을 부여하여, 코드의 책임을 분리하고 테스트 가능성을 높입니다. 이는 클린 아키텍처의 원칙인 모듈화와 책임 분리(SRP)를 잘 반영합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;VIPER 패턴 (View-Interactor-Presenter-Entity-Router):&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VIPER은 VIP 패턴을 확장하여, 더 세분화된 구조를 제공합니다.&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;View&lt;/b&gt;: UI 요소와 사용자 인터랙션을 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Interactor&lt;/b&gt;: 비즈니스 로직을 담당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Presenter&lt;/b&gt;: 데이터를 View에 전달하고, UI 로직을 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Entity&lt;/b&gt;: 비즈니스 엔티티를 나타내며, 데이터 모델을 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Router&lt;/b&gt;: 화면 간의 내비게이션과 모듈 간의 전환을 담당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VIPER 패턴은 클린 아키텍처의 각 레이어를 구체화하여, 모듈 간 의존성을 더 명확히 분리하고, 코드의 구조를 체계적으로 유지할 수 있도록 합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;3. 클린 아키텍처와 디자인 패턴의 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;클린 아키텍처&lt;/b&gt;는 소프트웨어의 전반적인 설계 철학과 구조를 다루는 반면,&amp;nbsp;&lt;b&gt;디자인 패턴&lt;/b&gt;(VIP, VIPER 등)은 이 철학을 실질적으로 구현하는 구체적인 방법론입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;관계 요약:&lt;/h4&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;/li&gt;
&lt;li&gt;&lt;b&gt;디자인 패턴&lt;/b&gt;은 클린 아키텍처의 원칙을 실현하기 위한 도구입니다. VIP, VIPER 패턴은 클린 아키텍처의 모듈화, 책임 분리, 의존성 관리 등의 원칙을 iOS 개발에서 구현할 수 있도록 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;4. 왜 사용하는가?&lt;/h3&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;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 가능성&lt;/b&gt;: 각 모듈이 독립적으로 동작할 수 있도록 설계되어, 유닛 테스트와 같은 테스트가 용이해집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성과 유연성&lt;/b&gt;: 새로운 기능을 추가하거나 기존 기능을 변경할 때, 전체 시스템에 미치는 영향을 최소화할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;책임 분리&lt;/b&gt;: 코드의 각 부분이 명확한 역할을 가지고, 이로 인해 시스템의 복잡성을 줄이고 이해하기 쉽게 만듭니다.&lt;/li&gt;
&lt;/ul&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;p data-ke-size=&quot;size16&quot;&gt;TCA(The Composable Architecture)에서는 주로 &lt;b&gt;단방향 데이터 흐름(Unidirectional Data Flow)&lt;/b&gt; 패턴을 사용합니다. 이 패턴은 앱의 상태 관리와 비즈니스 로직을 명확하게 구조화하는 데 중점을 둡니다. TCA는 이 패턴을 기반으로 상태(state), 액션(action), 리듀서(reducer), 그리고 환경(environment)을 사용하여 앱의 로직을 구성합니다.&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;TCA에서 사용하는 주요 패턴:&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;단방향 데이터 흐름(Unidirectional Data Flow)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;상태(State)&lt;/b&gt;: 애플리케이션의 모든 데이터를 포함하는 구조체입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;액션(Action)&lt;/b&gt;: 상태 변경을 일으키는 이벤트입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리듀서(Reducer)&lt;/b&gt;: 액션에 따라 상태를 변경하는 순수 함수입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;환경(Environment)&lt;/b&gt;: 외부 의존성을 캡슐화하여 애플리케이션 로직과 분리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴은 상태 관리의 복잡성을 줄이고 예측 가능성을 높이는 데 중점을 둡니다. Redux에서 영감을 받은 이 패턴은 TCA의 핵심이 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TCA의 주요 특징 :&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;의존성 주입(Dependency Injection)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCA에서는 Environment를 통해 외부 의존성을 관리합니다. 이는 테스트 가능성과 모듈화를 촉진 : TCA의 특징&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴포지션(Composition)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCA는 리듀서와 상태를 합성하여 더 복잡한 로직을 만들 수 있게 합니다. 이는 시스템을 모듈화하고 재사용성을 높이는 방식 : TCA의 설계 철학&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이펙트 관리(Effect Management)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Effect는 비동기 작업이나 부작용을 처리하는 TCA의 메커니즘입니다. Combine 프레임워크와 함께 사용되며, 비동기 작업을 선언적이고 관리 가능한 방식으로 처리합니다. : TCA의 기능적 특징&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약&lt;/h3&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;: TCA의 핵심 패턴은 &lt;b&gt;단방향 데이터 흐름&lt;/b&gt;입니다. 이 패턴은 앱의 상태와 비즈니스 로직을 예측 가능하고 명확하게 관리할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징&lt;/b&gt;: TCA의 주요 특징으로는 의존성 주입, 컴포지션, 이펙트 관리가 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;자 이제 코드로 이해를 해볼까요?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7lTT7/btsIZdpiF1l/1kj6KOhB9SPON8t2jolEOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7lTT7/btsIZdpiF1l/1kj6KOhB9SPON8t2jolEOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7lTT7/btsIZdpiF1l/1kj6KOhB9SPON8t2jolEOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7lTT7%2FbtsIZdpiF1l%2F1kj6KOhB9SPON8t2jolEOk%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;1019&quot; height=&quot;547&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;547&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;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=fYQ9YnbvasU&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/pPkFE/hyWOoBJJ1G/JGdXXhQ9afUJSlKvK1Jh21/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-video-title=&quot;취준생을 위한 스위프트UI 앱만들기 앱 아키텍쳐 TCA 알아보기 / SwiftUi fundamental Tutorial (2022) - compos&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/fYQ9YnbvasU&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;저도 해당 강의를 보고 대략적인 감을 잡았습니다. 해당 강의를 보시면서 아래 코드 주석을 읽어 주세요&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Counter App&amp;nbsp;&lt;/h3&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;도메인(무언가를 만들때 어떠한 데이터) + 상태&lt;/p&gt;
&lt;pre id=&quot;code_1723188596870&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 도메인(무언가를 만들때 어떠한 데이터) + 상태
// 카운터 화면이라 이러한 구조를 가짐
struct CounterState: Equatable {
    var count = 0
}

// 도메인 + 액션
enum CounterAction: Equatable {
    case addCount // 카운트를 더하는 액션
    case subtractCount // 카운트를 빼는 액션
}

// 도메인 Environment(환경)
struct CounterEnvironment {}

// Reducer : 액션과 상태를 연결 시켜주는 역할 - Effect를 반환함
let counterReducer = Reducer&amp;lt;CounterState, CounterAction, CounterEnvironment&amp;gt; { state, action, environment in
    // 들어온 액션에 따라 상태를 변경
    switch action {
    case .addCount:
        state.count += 1
        return Effect.none // 이펙트를 반환하지는 않지만 상태를 바꿔준것임
    case .subtractCount:
        state.count -= 1
        return Effect.none
    }
}&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;MVVM을&amp;nbsp;사용하셨다면&amp;nbsp;비슷하게&amp;nbsp;생각하시면&amp;nbsp;편합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723188789979&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct CounterView: View {
    
    // 스토어는 상태랑 액션을 가지고 있음 선언만 한거고 넣어주는건 외부 에서
    let store : Store&amp;lt;CounterState, CounterAction&amp;gt;
    
    var body: some View {
        // 스토어를 받는 방법 WithViewStore : 스토어를 받고 할 수 있도록하는 옵져버블이 되는애
        // 스토어랑 스유뷰랑 연결해주는 친구임
        WithViewStore(self.store) { viewStore in
            VStack {
                Text(&quot;count: \(viewStore.state.count)&quot;) // 스토어가 상태도 가지고 있어서 변경이 됨
                    .padding()
                HStack{
                    // 스토어 == 사령관 : 사령관님 저 이제 더하기 할거에요 하고 알려줘야함
                    Button(&quot;더하기&quot;, action: { viewStore.send(.addCount) }) // enum으로 다 정의함 
                    Button(&quot;뺴기&quot;, action: { viewStore.send(.subtractCount) })
                }
            }
        }
    }
}&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;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_1723188921447&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;

import SwiftUI
import ComposableArchitecture

// 도메인(무언가를 만들때 어떠한 데이터) + 상태
// 카운터 화면이라 이러한 구조를 가짐
struct CounterState: Equatable {
    var count = 0
}

// 도메인 + 액션
enum CounterAction: Equatable {
    case addCount // 카운트를 더하는 액션
    case subtractCount // 카운트를 빼는 액션
}

// 도메인 Environment(환경)
struct CounterEnvironment {}

// Reducer : 액션과 상태를 연결 시켜주는 역할 - Effect를 반환함
let counterReducer = Reducer&amp;lt;CounterState, CounterAction, CounterEnvironment&amp;gt; { state, action, environment in
    // 들어온 액션에 따라 상태를 변경
    switch action {
    case .addCount:
        state.count += 1
        return Effect.none // 이펙트를 반환하지는 않지만 상태를 바꿔준것임
    case .subtractCount:
        state.count -= 1
        return Effect.none
    }
}

struct CounterView: View {
    
    // 스토어는 상태랑 액션을 가지고 있음 선언만 한거고 넣어주는건 외부 에서
    let store : Store&amp;lt;CounterState, CounterAction&amp;gt;
    
    var body: some View {
        // 스토어를 받는 방법 WithViewStore : 스토어를 받고 할 수 있도록하는 옵져버블이 되는애
        // 스토어랑 스유 뷰랑 연결해주는 친구임
        WithViewStore(self.store) { viewStore in
            VStack {
                Text(&quot;count: \(viewStore.state.count)&quot;) // 스토어가 상태도 가지고 있어서 변경이 됨
                    .padding()
                HStack{
                    // 스토어 == 사령관 : 사령관님 저 이제 더하기 할거에요 하고 알려줘야함
                    Button(&quot;더하기&quot;, action: { viewStore.send(.addCount) }) // 이넘으로 다 정의함 
                    Button(&quot;뺴기&quot;, action: { viewStore.send(.subtractCount) })
                }
            }
        }
    }
}

//struct CounterView_Previews: PreviewProvider {
//    static var previews: some View {
//        CounterView()
//    }
//}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;store는 외부에서 주입해주는 방식으로 사용하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723188969746&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;

import SwiftUI
import ComposableArchitecture

@main
struct TCA_Simple_tutorialApp: App {
    
    // 외부에서 Store를 생성
    let counterStore = Store(initialState: CounterState(),
                             reducer: counterReducer,
                             environment: CounterEnvironment())
    
  
    
    var body: some Scene {
        WindowGroup {
            CounterView(store: counterStore)
           
        }
    }
}&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Memo App (서버와 통신 및 Environment(환경) 사용하기)&lt;/h3&gt;
&lt;pre id=&quot;code_1723189095284&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

// MARK: - MemoElement
struct Memo: Codable, Equatable, Identifiable {
    let createdAt, title, viewCount, id: String
}

typealias Memos = [Memo] // 별칭 주기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 호출시 해당 구조로 날아온다고 생각&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;API 통신 작업을 정의&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cixNWv/btsI0JNDcqH/UpP9TsrUGxsjHX8aX7aVe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cixNWv/btsI0JNDcqH/UpP9TsrUGxsjHX8aX7aVe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cixNWv/btsI0JNDcqH/UpP9TsrUGxsjHX8aX7aVe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcixNWv%2FbtsI0JNDcqH%2FUpP9TsrUGxsjHX8aX7aVe0%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;658&quot; height=&quot;529&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://elisha0103.tistory.com/26&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://elisha0103.tistory.com/26&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723189381744&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;TCA(The Composable Architecture)란?&quot; data-og-description=&quot;최근에 TCA에 관련되어 자료조사할 일이 생겨서 TCA 공식 문서와 공식 업데이트, 개발 방향에 대해 공부한 것을 정리해보려고 한다. TCA pointfree에서 Brandon Williams와 Stephen Ceils가 만들어낸 아키텍처 &quot; data-og-host=&quot;elisha0103.tistory.com&quot; data-og-source-url=&quot;https://elisha0103.tistory.com/26&quot; data-og-url=&quot;https://elisha0103.tistory.com/26&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CU84U/hyWKIBSa8A/2l93oSDw7vpfH8Eft9knH0/img.png?width=800&amp;amp;height=498&amp;amp;face=0_0_800_498,https://scrap.kakaocdn.net/dn/cfBmu4/hyWOlE05bU/hcAHbLsrVVvEwl8IGrGrEk/img.png?width=800&amp;amp;height=498&amp;amp;face=0_0_800_498,https://scrap.kakaocdn.net/dn/MIkKE/hyWKGD2000/0bR5Vy0mOoHQ3xgU9E90w0/img.png?width=2000&amp;amp;height=1247&amp;amp;face=0_0_2000_1247&quot;&gt;&lt;a href=&quot;https://elisha0103.tistory.com/26&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://elisha0103.tistory.com/26&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CU84U/hyWKIBSa8A/2l93oSDw7vpfH8Eft9knH0/img.png?width=800&amp;amp;height=498&amp;amp;face=0_0_800_498,https://scrap.kakaocdn.net/dn/cfBmu4/hyWOlE05bU/hcAHbLsrVVvEwl8IGrGrEk/img.png?width=800&amp;amp;height=498&amp;amp;face=0_0_800_498,https://scrap.kakaocdn.net/dn/MIkKE/hyWKGD2000/0bR5Vy0mOoHQ3xgU9E90w0/img.png?width=2000&amp;amp;height=1247&amp;amp;face=0_0_2000_1247');&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;TCA(The Composable Architecture)란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최근에 TCA에 관련되어 자료조사할 일이 생겨서 TCA 공식 문서와 공식 업데이트, 개발 방향에 대해 공부한 것을 정리해보려고 한다. TCA pointfree에서 Brandon Williams와 Stephen Ceils가 만들어낸 아키텍처&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;elisha0103.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;&lt;b&gt;환경(Environment)&lt;/b&gt;: API 클라이언트나 애널리틱스 클라이언트와 같이 어플리케이션이 필요로 하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;의존성(Dependency)&lt;/b&gt;&lt;/span&gt;을 가지고 있는 타입 결국 환경 쪽에서 해당 MemoClient를 가지게 됩니다. (외부에서 소통을 하는 HTTP 통신과 같은 애들을 따로 구현한다음 환경에서 가지고 있다가 Reducer가 해당 환경을 통해 Effect를 받는 구조 입니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Effect는 Composable 밖에서 외부에서 일어나는것들을 다시 Composable로 들어와서 Composable에서 상태를 변경할때&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;SideEffect, Effect 라고함 보통 사이드 이펙트라고 함 (보통은 데이터 처리)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;쉽게 외부에서 일어난 일을 다시 스토어로 가져와 상태를 변경시킬때 이팩트를 쓰는 거군아 라고 생각하면 됨&lt;/p&gt;
&lt;pre id=&quot;code_1723189204303&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;
import Foundation
import ComposableArchitecture

// API 통신

// 통신쪽에 대한 행위들을 정의
struct MemoClient {
    
    /// 단일 아이템 조회
    var fetchMemoItem: (_ id: String) -&amp;gt; Effect&amp;lt;Memo, Failure&amp;gt;
    
    var fetchMemos: () -&amp;gt; Effect&amp;lt;Memos, Failure&amp;gt;
    
    struct Failure : Error, Equatable {} // 에러 정의
    // ComposableArchitecture를 사용할때 거의 대부분 데이터들 Equatable로 되어 있음
}

// 실제 로직처리
extension MemoClient {
    /*
     live는 MemoClient 자체를 반환 하는 것임
     live = MemoClient()  두 문법은 같음
     live = Self()
     */
    static let live = Self(
        
        // fetchMemoItem 클로저로 정의 했음 id를 받음
        fetchMemoItem: { id in
            Effect.task{
                let (data, _) = try await URLSession.shared
                    .data(from: URL(string: &quot;https://603fca51d9528500176060fc.mockapi.io/api/01/todos/\(id)&quot;)!)
                return try JSONDecoder().decode(Memo.self, from: data) // 디코딩
            }
            .mapError { _ in Failure() } // 에러가 나면 내가 정의한 형태로 변환
            .eraseToEffect() //퍼블리셔 랑 똑같음 어떤것이든 Effect로 만들어줌 
        }, fetchMemos: {
            Effect.task{
                let (data, _) = try await URLSession.shared
                    .data(from: URL(string: &quot;https://603fca51d9528500176060fc.mockapi.io/api/01/todos/&quot;)!)
                return try JSONDecoder().decode([Memo].self, from: data)
            }
            .mapError { _ in Failure() }
            .eraseToEffect()
        }
    )
}&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&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;그래서 현제 API 통신이라 반환값이 Effect임 Effect들어가서 보면 퍼블리셔임 결과, 에러가 있음&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Effect&amp;lt;Output, Failure: Error&amp;gt;: Publisher&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 아까와 같이 도메인 별로 상태, 액션, 환경, 라우터, 리듀서를 만들어줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723189309703&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  MemoView.swift
//  TCA_Simple_tutorial
//
//  Created by Jeff Jeong on 2022/08/04.
//

import Foundation
import SwiftUI
import ComposableArchitecture

// 도메인 + 상태
struct MemoState: Equatable {
    var memos : [Memo] = []
    var selectedMemo : Memo? = nil
    var isLoading : Bool = false
}

// 도메인 + 액션 (약간 뷰모델을 정의하는 듯한 느낌인데)
enum MemoAction: Equatable {
    case fetchItem(_ id: String) // 단일 조회 액션
    case fetchItemResponse(Result&amp;lt;Memo, MemoClient.Failure&amp;gt;) // 단일 조회 액션 응답
    case fetchAll // 모두 조회 액션
    case fetchAllResponse(Result&amp;lt;[Memo], MemoClient.Failure&amp;gt;) // 모두 조회 액션 응답
}

// 환경설정 주입 (여기서 주입이 됨)
struct MemoEnvironment {
    var memoClient : MemoClient // API 호출위해 필요한 녀석
    var mainQueue: AnySchedulerOf&amp;lt;DispatchQueue&amp;gt; // 어떤 스레드에서 할건지
}

// 상태와 액션을 가지고 있는 리듀서
// 액션이 들어왔을때 상태를 변경하는 부분
let memoReducer = Reducer&amp;lt;MemoState, MemoAction, MemoEnvironment&amp;gt; { state, action, environment in
    // 들어온 액션에 따라 상태를 변경
    switch action {
    case .fetchItem(let memoId): // 액션이 들어왔을때 행동을 정의
        enum FetchItemId {} // 이건 정대리도 모름 하나의 사용방법
        state.isLoading = true  // 상태 넣어주기
        return environment.memoClient
            .fetchMemoItem(memoId) // api 호출 환경설정을 통해
            .debounce(id: FetchItemId.self,
                      for: 0.3,
                      scheduler: environment.mainQueue) // 스레드 설정
            .catchToEffect(MemoAction.fetchItemResponse) // 다른 행위 액션으로 이동 (즉 액션간의 이동 처리 방법)
    
        // Result 타입이라 성공, 실패 각각 정의
    case .fetchItemResponse(.success(let memo)):
        state.selectedMemo = memo
        state.isLoading = false
        return Effect.none
    case .fetchItemResponse(.failure): // 상태로 들어와 변경 : 이팩트 = 외부에서 일어나는 결과로 상태를 변경
        state.selectedMemo = nil
        state.isLoading = false
        return Effect.none
    case .fetchAll:
        enum FetchAllId {}
        state.isLoading = true
        return environment.memoClient
            .fetchMemos()
            .debounce(id: FetchAllId.self,
                      for: 0.3,
                      scheduler: environment.mainQueue)
            .catchToEffect(MemoAction.fetchAllResponse)
    case .fetchAllResponse(.success(let memos)):
        state.memos = memos
        state.isLoading = false
        return Effect.none
    case .fetchAllResponse(.failure):
        state.memos = []
        state.isLoading = false
        return Effect.none
    }
}


struct MemoView: View {
    
    let store : Store&amp;lt;MemoState, MemoAction&amp;gt;
    
    var body: some View {
        WithViewStore(self.store) { viewStore in
            
            ZStack {
                
                if viewStore.state.isLoading {
                    Color.black.opacity(0.3)
                        .edgesIgnoringSafeArea(.all)
                        .overlay{
                            ProgressView().tint(.white)
                                .scaleEffect(1.7)
                        }.zIndex(1)
                }
                
                List{
                    Section(header:
                        VStack(spacing: 8) {
                            Button(&quot;메모 목록 가져오기&quot;, action: {
                                viewStore.send(.fetchAll, animation: .default)
                            })
                            Text(&quot;선택된 메모정보&quot;)
                            Text(viewStore.state.selectedMemo?.id ?? &quot;비어있음&quot;)
                            Text(viewStore.state.selectedMemo?.title ?? &quot;비어있음&quot;)
                        }
                    ,
                        content: {
                        ForEach(viewStore.state.memos) { aMemo in
                            // 하나씩 나온 memo 누르면 개별 호출을 하기위해 버튼 생성 
                            Button(aMemo.title, action: {
                                viewStore.send(.fetchItem(aMemo.id), animation: .default)
                            })
                        }
                    })
                }.listStyle(PlainListStyle())
            }
        }
    }
}&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_1723189651896&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;

import SwiftUI
import ComposableArchitecture

@main
struct TCA_Simple_tutorialApp: App {
    
    // 외부에서 Store를 생성
    let counterStore = Store(initialState: CounterState(),
                             reducer: counterReducer,
                             environment: CounterEnvironment())
    
    let memoStore = Store(initialState: MemoState(),
                          reducer: memoReducer,
                          environment: MemoEnvironment(
                            memoClient: MemoClient.live, // 클라이언트 만들어서 넣어줘야함 (스테틱으로 정의된)
                            mainQueue: .main
                          ))
    
    var body: some Scene {
        WindowGroup {
            //CounterView(store: counterStore)
            MemoView(store: memoStore)
        }
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추천 자료 (공식 git 예제)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/pointfreeco/swift-composable-architecture/tree/main/Examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/pointfreeco/swift-composable-architecture/tree/main/Examples&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723189752713&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;swift-composable-architecture/Examples at main &amp;middot; pointfreeco/swift-composable-architecture&quot; data-og-description=&quot;A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind. - pointfreeco/swift-composable-architecture&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/pointfreeco/swift-composable-architecture/tree/main/Examples&quot; data-og-url=&quot;https://github.com/pointfreeco/swift-composable-architecture/tree/main/Examples&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LbKkF/hyWOkMSoKv/JicYdE41mbod0tBEKbuqak/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/pointfreeco/swift-composable-architecture/tree/main/Examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/pointfreeco/swift-composable-architecture/tree/main/Examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LbKkF/hyWOkMSoKv/JicYdE41mbod0tBEKbuqak/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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-composable-architecture/Examples at main &amp;middot; pointfreeco/swift-composable-architecture&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind. - pointfreeco/swift-composable-architecture&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.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>architecture와 design pattern 차이</category>
      <category>ios tca</category>
      <category>swiftui tca</category>
      <category>TCA</category>
      <category>tca git</category>
      <category>tca 클린아키텍처</category>
      <category>디자인패턴이란</category>
      <category>아키텍처란</category>
      <category>클린아키텍처</category>
      <category>클린아키텍처를 구현하기 위한 디자인패턴</category>
      <author>개발자 aloe</author>
      <guid isPermaLink="true">https://aloe-study.tistory.com/234</guid>
      <comments>https://aloe-study.tistory.com/234#entry234comment</comments>
      <pubDate>Fri, 9 Aug 2024 16:54:46 +0900</pubDate>
    </item>
    <item>
      <title>MapKit with SwiftUI 기초부터 심화까지 2 : MapKit 다루기 심화 과정</title>
      <link>https://aloe-study.tistory.com/233</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;이전시간에는 롯데타워 주변에 주차장과, 병원을 Marker로 찍어 봤습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이번 시간에는 좀 더 심화한 내용인&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;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지도의 카메라 개념&lt;/li&gt;
&lt;li&gt;지도의 영역 값을 얻는 방법&lt;/li&gt;
&lt;li&gt;지도의 영역 값을 기준으로 특정 장소(주차장, 병원)를 검색하는 방법&lt;/li&gt;
&lt;li&gt;MKLookAroundScene을 사용하여 프리뷰를 보는 방법&lt;/li&gt;
&lt;li&gt;사용자의 현 위치를 기점으로 특정 장소와의 거리 계산 표시 방법 등&amp;nbsp;&lt;/li&gt;
&lt;/ul&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;최종적으로 아래와 같은 앱을 만들어 볼거에요!!&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignLeft&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/443791002&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/dPFcrf/hyU2oxt3yx/KZJzz7dYWHGiUiZkLtVGX0/img.jpg?width=888&amp;amp;height=1920&amp;amp;face=0_0_888_1920,https://scrap.kakaocdn.net/dn/mY6I3/hyU2eBDjov/VbfMNvqsGVde5cZoBEPwKK/img.jpg?width=888&amp;amp;height=1920&amp;amp;face=0_0_888_1920&quot; data-video-width=&quot;360&quot; data-video-height=&quot;778&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1859&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/443791002?service=daum_tistory&quot; width=&quot;360&quot; height=&quot;778&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;지도를&amp;nbsp;부산,&amp;nbsp;강릉으로&amp;nbsp;이동시켜&amp;nbsp;보자&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;293&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/woQtn/btsDfiIlPBT/YcKLotaOAoGqNKSfOySUdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/woQtn/btsDfiIlPBT/YcKLotaOAoGqNKSfOySUdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/woQtn/btsDfiIlPBT/YcKLotaOAoGqNKSfOySUdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwoQtn%2FbtsDfiIlPBT%2FYcKLotaOAoGqNKSfOySUdk%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;864&quot; height=&quot;355&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;293&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롯데타워에서 멀리 이동하여 주차장, 병원을 검색하면 지도에 롯데타워 근처의 결과가 더 이상 자동으로 표시되지 않습니다.&lt;br /&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;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;보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704759664330&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension MKCoordinateRegion {
    static let busan = MKCoordinateRegion (
        center: CLLocationCoordinate2D( latitude: 35.1795543, longitude: 129.0756416),
        span: MKCoordinateSpan ( latitudeDelta: 0.1, longitudeDelta: 0.1)
    ) // 부산 좌표
    
    static let gangneung = MKCoordinateRegion (
        center: CLLocationCoordinate2D( latitude: 37.751853, longitude: 128.8760574),
        span: MKCoordinateSpan( latitudeDelta: 0.5, longitudeDelta: 0.5)
    ) // 강릉 좌표
}&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;span이 뭐지?&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;span은 지도의 경도 및 위도 방향으로 얼마나 넓게 보여줄지에 대한 값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MKCoordinateSpan&lt;/b&gt; 클래스를 사용하여 정의하며, latitudeDelta는 위도 방향으로의 크기, longitudeDelta는 경도 방향으로의 크기를 나타냅니다.&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;여기서 주어진 코드에서 span은 두 개의 MKCoordinateRegion에 대해 각각 다르게 설정되어 있습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;busan:&lt;/b&gt; **latitudeDelta**와 **longitudeDelta**가 모두 0.1로 설정되어 있습니다. 이는 부산 지역을 표시할 때, 지도가 위도와 경도 방향으로 각각 0.1만큼의 범위를 갖도록 하는 것입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;gangneung:&lt;/b&gt; **latitudeDelta**와 **longitudeDelta**가 모두 0.5로 설정되어 있습니다. 이는 강릉 지역을 표시할 때, 지도가 위도와 경도 방향으로 각각 0.5만큼의 범위를 갖도록 하는 것입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 span을 조절하면 지도의 확대 수준이나 축소 수준을 조절할 수 있습니다. 더 큰 &lt;b&gt;latitudeDelta&lt;/b&gt; 및 &lt;b&gt;longitudeDelta&lt;/b&gt; 값은 더 큰 영역을 보여주게 되어 지도가 더 크게 확대됨을 의미합니다.&lt;/p&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;/p&gt;
&lt;pre id=&quot;code_1704759828623&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 카메라 위치추적 변수 지도에 추가한 콘텐츠를 화면에 잡아주는 기본 automatic
    @State private var position: MapCameraPosition = .automatic&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;MapCameraPosition은 MapKit에서 사용되는 열거형(enumeration)으로, 지도의 카메라 위치와 관련된 여러 가지 옵션을 나타냅니다. 이 열거형은 &lt;b&gt;Map&lt;/b&gt; 뷰의 초기화에서 &lt;b&gt;position&lt;/b&gt; 매개변수로 사용되어 카메라의 초기 위치를 설정하거나, 지도를 이동할 때 카메라의 동작을 제어하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 &lt;b&gt;MapCameraPosition&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;b&gt;.automatic:&lt;/b&gt; 이 옵션은 지도를 자동으로 이동하게끔 카메라를 설정합니다. 주로 사용자의 현재 위치를 중심으로 지도를 표시하고, 사용자가 이동하면 카메라가 자동으로 이동하여 위치를 유지합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.coordinated(latitude:longitude:latitudeDelta:longitudeDelta:):&lt;/b&gt; 이 옵션은 특정 좌표를 중심으로 하고, 지정된 위도와 경도의 범위로 지도를 표시합니다. 주어진 좌표를 중심으로 하되, 사용자의 이동에 따라 자동으로 지도를 갱신하지는 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.none:&lt;/b&gt; 이 옵션은 카메라를 사용하지 않고, 특정 위치로 이동하지 않습니다. 즉, 초기 지도 상태를 고정시킵니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 옵션들은 &lt;b&gt;Map&lt;/b&gt; 뷰를 초기화할 때 &lt;b&gt;position&lt;/b&gt; 매개변수에 전달되어 사용되며, 사용자가 지도와 상호 작용할 때마다 카메라의 위치를 조정하는 데 사용됩니다. MapCameraPosition을 통해 지도를 초기화하고 설정함으로써 사용자에게 더 편리하고 직관적인 지도 경험을 제공할 수 있습니다.&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;전체코드&lt;/b&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;pre id=&quot;code_1704759924279&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  ContentView.swift
//  MapKitStudy
//
//  Created by 정정욱 on 1/7/24.
//

import SwiftUI
import MapKit

extension CLLocationCoordinate2D {
    static let lotteTower = CLLocationCoordinate2D(latitude: 37.5125, longitude: 127.102778)
}

extension MKCoordinateRegion {
    static let busan = MKCoordinateRegion (
        center: CLLocationCoordinate2D( latitude: 35.1795543, longitude: 129.0756416),
        span: MKCoordinateSpan ( latitudeDelta: 0.1, longitudeDelta: 0.1)
    ) // 도시좌표
    
    static let gangneung = MKCoordinateRegion (
        center: CLLocationCoordinate2D( latitude: 37.751853, longitude: 128.8760574),
        span: MKCoordinateSpan( latitudeDelta: 0.5, longitudeDelta: 0.5)
    ) // 해안 좌표
}


struct ContentView: View {
    
    //   카메라 위치추적 변수 지도에 추가한 콘텐츠를 화면에 잡아주는 기본 automatic
    @State private var position: MapCameraPosition = .automatic
    
     @State private var searchResults: [MKMapItem] = []
    
    var body: some View {
        Map(position: $position){ //   Map의 카메라를 초기화 할수있게 설정
            Annotation(&quot;lotteTower&quot;, coordinate: .lotteTower) {
                ZStack {
                    RoundedRectangle(cornerRadius: 5)
                        .fill(.background)
                    RoundedRectangle(cornerRadius: 5)
                        .stroke(.secondary, lineWidth: 5)
                    Image(systemName: &quot;house.circle.fill&quot;)
                        .padding(5)
                }
            }
            .annotationTitles(.hidden) 
        
            ForEach(searchResults, id: \.self) { result in
                Marker(item: result)
            }
        }
        .mapStyle(.standard)
        .safeAreaInset(edge: .bottom) {
            HStack {
                Spacer()
                //   $position(카메라)넣어주기
                BeanTownButtons(position:  $position, searchResults: $searchResults)
                    .padding(.top)
                Spacer()
            }
            .background(.ultraThinMaterial)
        }
        .onChange(of: searchResults){
                   position = .automatic //   카메라 업데이트
                   // 이제 다른곳에 있어도 지도를 직접 올려서 찾아 가지 않아도 됨
               }

    }
}


#Preview {
    ContentView()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;/p&gt;
&lt;pre id=&quot;code_1704760004398&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  BeantownButtonView.swift
//  MapKitStudy
//
//  Created by 정정욱 on 1/7/24.
//

import SwiftUI
import MapKit

struct BeanTownButtons: View {
    
    @Binding var position: MapCameraPosition //   카메라 위치상태 바인딩
    @Binding var searchResults: [MKMapItem]
    
    var body: some View {
        HStack {
            Button {
                search(for: &quot;parking&quot;)
            } label: {
                Label(&quot;parking lot&quot;, systemImage: &quot;car&quot;)
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                search(for: &quot;hospital&quot;)
            } label: {
                Label(&quot;restroom&quot;, systemImage: &quot;cross.case&quot;)
            }
            .buttonStyle(.borderedProminent)
            
            //  부산, 강릉으로 이동 버튼
            Button {
                position = .region(.busan) // 부산 표시
            } label: {
                Label(&quot;Busan&quot;, systemImage: &quot;figure.open.water.swim&quot;)
            }
            .buttonStyle (.bordered)
            
            Button {
                position = .region(.gangneung) // 강릉 표시
            } label: {
                Label(&quot;Gangneung&quot;, systemImage: &quot;figure.sailing&quot;)
            }
            .buttonStyle (.bordered)
            
        }
        .labelStyle(.iconOnly)
    }
    

    func search(for query: String) {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = query
        request.resultTypes = .pointOfInterest
        request.region = MKCoordinateRegion(
            center: .lotteTower,
            span: MKCoordinateSpan (latitudeDelta: 0.0125, longitudeDelta: 0.0125)
            )

        Task {
            let search = MKLocalSearch(request: request)
            let response = try? await search.start()
            searchResults = response?.mapItems ?? []
        }
    }

}&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;이번 part에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MapCameraPosition을 이용하여 지도의 화면을 조절해 보았습니다.&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;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;사용자가 지도를 움직여 &lt;span data-token-index=&quot;1&quot;&gt;보이는 영역에서 검색하기&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종로구에서 가까운 주차장, 병원 찾기, 부산지역 주차장 찾기, 강릉지역 병원 찾기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;1137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkuyRa/btsC9AcoR62/OK4MzOc7QWbdPz8Fa7ckHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkuyRa/btsC9AcoR62/OK4MzOc7QWbdPz8Fa7ckHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkuyRa/btsC9AcoR62/OK4MzOc7QWbdPz8Fa7ckHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkuyRa%2FbtsC9AcoR62%2FOK4MzOc7QWbdPz8Fa7ckHk%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;582&quot; height=&quot;1137&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;1137&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;카메라가 바뀔 때 눈앞에 보이는 지도 영역의 값을 얻는 방법&lt;/p&gt;
&lt;pre id=&quot;code_1704760396090&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.
.
.

struct ContentView: View {
    
    // 지도에 표시되는 지역을 추적하기 위해 상태를 추가
    @State private var visibleRegion: MKCoordinateRegion?
    
		 .
		 .
		 .

        .mapStyle(.standard)
        .safeAreaInset(edge: .bottom) {
            HStack {
                Spacer()
                // 
                BeanTownButtons(position: $position, searchResults: $searchResults, visibleRegion: visibleRegion)
                    .padding(.top)
                Spacer()
            }
            .background(.ultraThinMaterial)
        }
        .onChange(of: searchResults){
            position = .automatic
        }
        .onMapCameraChange { context in
            visibleRegion = context.region
        }
        /*  
         onMapCameraChange에 제공된 클로저는 사용자가 지도와의 상호작용을 마치면 호출됩니다. 사용자가 지도와 상호작용하는 동안 클로저를 호출하려면 빈도 매개변수를 전달하여 지속적인 업데이트를 요청할 수 있습니다.
         */
    }
}


#Preview {
    ContentView()
}&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;코드설명&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MKCoordinateRegion&lt;b&gt;은 MapKit 프레임워크에서 사용되는 구조체로, 지도의 특정 영역을 정의합니다. &lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;이 구조체는 중심 좌표 (&lt;/b&gt;center&lt;/b&gt;)와 지도 영역의 크기를 나타내는 스팬 (&lt;b&gt;span&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;&lt;b&gt;center:&lt;/b&gt; 중심 좌표는 해당 영역의 지도에서 가운데 위치하는 지점의 위도와 경도입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;span:&lt;/b&gt; 스팬은 지도 영역의 크기를 나타내며, 위도 방향과 경도 방향의 크기를 각각 latitudeDelta와 longitudeDelta로 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 MKCoordinateRegion을 사용하면 특정 지점을 중심으로 하는 지도 영역을 정의할 수 있습니다.&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;&lt;b&gt;.onMapCameraChange&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704760525429&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;swiftCopy code
.onMapCameraChange { context in
    visibleRegion = context.region
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;.onMapCameraChange:&lt;/b&gt; SwiftUI에서 제공하는 modifier로, 사용자가 지도와의 상호작용을 마치면 호출되는 클로저를 등록하는 역할을 합니다. 사용자가 지도와 상호작용하는 동안 해당 클로저를 지속적으로 호출하려면 빈도 매개변수를 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;{ context in visibleRegion = context.region }:&lt;/b&gt; 클로저 내부에서 &lt;b&gt;context&lt;/b&gt; 매개변수를 받아와서 사용합니다. context.region은 사용자가 지도를 조작할 때 현재 지도 영역에 대한 정보를 제공합니다. 이 정보를 사용하여 &lt;b&gt;visibleRegion&lt;/b&gt; 상태 변수를 업데이트합니다. 따라서 지도의 카메라가 변경될 때마다 visibleRegion이 해당 지도 영역을 반영하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 지도의 카메라가 변경될 때마다 해당 변경사항을 감지하고, 그에 따라 visibleRegion을 업데이트하여 현재 지도가 보여주는 영역을 추적하는데 사용됩니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;region이 뭔가요?&lt;/b&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;region은 &lt;b&gt;onMapCameraChange&lt;/b&gt; 클로저의 &lt;b&gt;context&lt;/b&gt; 매개변수에서 제공되는 속성 중 하나로, 사용자가 지도와의 상호 작용 중에 현재 지도 영역에 대한 정보를 포함합니다. 이 정보는 &lt;b&gt;MKCoordinateRegion&lt;/b&gt; 타입으로 제공되며, 지도의 특정 영역을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MKCoordinateRegion은 다음과 같은 속성을 포함합니다:&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;center:&lt;/b&gt; 중심 좌표는 해당 영역의 지도에서 가운데 위치하는 지점의 위도와 경도입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;span:&lt;/b&gt; 스팬은 지도 영역의 크기를 나타내며, 위도 방향과 경도 방향의 크기를 각각 **latitudeDelta**와 **longitudeDelta**로 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, context.region은 현재 지도의 영역을 정의하는 &lt;b&gt;MKCoordinateRegion&lt;/b&gt; 객체를 나타냅니다. 따라서 &lt;b&gt;onMapCameraChange&lt;/b&gt; 클로저 내에서 이 정보를 활용하여 지도의 변경사항에 대응하는 데 사용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704760803083&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  BeantownButtonView.swift
//  MapKitStudy
//
//  Created by 정정욱 on 1/7/24.
//

import SwiftUI
import MapKit

struct BeanTownButtons: View {
    
	  .
		.
		.
    var visibleRegion: MKCoordinateRegion? //   사용자에게 표시되는 지역 내에서 검색되도록 BeantownButton을 업데이트
		.
		.
		.
    

    func search(for query: String) {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = query
        request.resultTypes = .pointOfInterest
				//  검색을 할때 사용자가 바라보고 있는 지도의 한부분에 대한 좌표를 가지고 검색
        request.region = visibleRegion ?? MKCoordinateRegion(
            center: .lotteTower,
            span: MKCoordinateSpan (latitudeDelta: 0.0125, longitudeDelta: 0.0125)
            )

        Task {
            let search = MKLocalSearch(request: request)
            let response = try? await search.start()
            searchResults = response?.mapItems ?? []
        }
    }

}&lt;/code&gt;&lt;/pre&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;street&amp;nbsp;view&amp;nbsp;정보&amp;nbsp;표시&amp;nbsp;(지역&amp;nbsp;Preview,&amp;nbsp;지역이름,&amp;nbsp;현&amp;nbsp;위치로부터&amp;nbsp;도착&amp;nbsp;시간)&lt;/b&gt;&lt;/h2&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignLeft&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/443791272&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cd8UGu/hyU2e9uSR4/4PuxQQPwhbS591UPs1gvm1/img.jpg?width=888&amp;amp;height=1920&amp;amp;face=0_0_888_1920,https://scrap.kakaocdn.net/dn/Q5ks6/hyU2jJJY5Q/kEcN8f6PQ6peNaYgEb8U11/img.jpg?width=888&amp;amp;height=1920&amp;amp;face=0_0_888_1920&quot; data-video-width=&quot;360&quot; data-video-height=&quot;778&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1859&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/443791272?service=daum_tistory&quot; width=&quot;360&quot; height=&quot;778&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉽게 한국은 MKLookAroundScene 표시가 아직 많이 지원되지 않습니다. 다른 나라를 기준으로 잡고 결과를 확인해 보세요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cv00Ly/btsC86JaYBT/qVKGKk5kbxlPyGAu1xPqn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cv00Ly/btsC86JaYBT/qVKGKk5kbxlPyGAu1xPqn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cv00Ly/btsC86JaYBT/qVKGKk5kbxlPyGAu1xPqn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcv00Ly%2FbtsC86JaYBT%2FqVKGKk5kbxlPyGAu1xPqn1%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;796&quot; height=&quot;486&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;355&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;pre id=&quot;code_1704775026240&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  ContentView.swift
//  MapKitStudy
//
//  Created by 정정욱 on 1/7/24.
//

import SwiftUI
import MapKit

struct ContentView: View {
    
    .
		.
		.

    
    @State private var selectedResult: MKMapItem? //여러 결과중 하나 선택하여 담기 위함
    
    // 주차장에서 선택한 검색결과까지 route정보를 구하기
    @State private var route: MKRoute?
    
    var body: some View {
        // selection: $selectedResult 추가
        Map(position: $position, selection: $selectedResult){
          .
					.
					.

        .safeAreaInset(edge: .bottom) {
            HStack {
                Spacer()
                VStack(spacing:0) {
                    //   여러 결과중 하나를 선택하면 해당 선택한 MKMapItem 과 출발지(롯데타워)부터 목적지까지의 거리를 전달
                    if let selectedResult {
                        ItemInfoView(selectedResult: selectedResult, route: route)
                            .frame(height: 128)
                            .clipShape(RoundedRectangle (cornerRadius: 10))
                            .padding([.top, .horizontal])
                    }
                    
                    BeanTownButtons(position: $position, searchResults: $searchResults, visibleRegion: visibleRegion)
                        .padding(.top)
                    
                }
                Spacer()
                
            }
            .background(.ultraThinMaterial)
        }
        .onChange(of: searchResults){
            position = .automatic
            getDirections() // 실행
        }
        .onMapCameraChange { context in
            visibleRegion = context.region
        }
        
    }
    
    
    
    func getDirections() {
        route = nil
        guard let selectedResult else { return }
        let request = MKDirections.Request()
        request.source = MKMapItem(placemark: MKPlacemark(coordinate: .lotteTower))
        request.destination = selectedResult
        
        Task {
            let directions = MKDirections(request: request)
            let response = try? await directions.calculate()
            route = response?.routes.first
        }
    }
    
}


#Preview {
    ContentView()
}&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;&lt;b&gt;Map(position: $position, selection: $selectedResult) 설명&lt;/b&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서&lt;span&gt;&amp;nbsp;&lt;/span&gt;selection: $selectedResult를 사용하는 것은 사용자가 지도에서 어떤 지점을 선택했을 때&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;selectedResult&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;변수를 업데이트하고자 하는 의도를 나타냅니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 사용자가 지도에서 특정 지점을 선택하면 해당 지점에 대한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;MKMapItem&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;정보가 selectedResult에 할당되어, 이를 통해&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;예를 들어, selectedResult가 업데이트될 때마다 길 찾기를 수행하거나 선택된 지점에 대한 추가 정보를 로드하는 등의 작업을 수행할 수 있습니다. 이것은 사용자가 지도에서 특정 위치를 선택할 때 앱이 해당 이벤트에 반응하고 관련된 동작을 수행할 수 있도록 하는 SwiftUI의 편리한 기능 중 하나입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Map&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;뷰의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;selection&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;매개변수는 사용자가 지도에서 특정 위치를 선택했을 때 해당 위치에 대한 정보를 나타내기 위한 Binding을 나타냅니다. 이것은 사용자가 지도에서 특정 지점을 선택할 때마다 해당 위치에 대한 정보를 업데이트하고 관련된 동작을 수행할 수 있게 해줍니다.&lt;/li&gt;
&lt;/ul&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;@State private var route: MKRoute?가 무슨 변수이지?&lt;/b&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;route&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;변수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;ContentView&lt;b&gt;에서 현재 선택된 지도 항목(&lt;/b&gt;selectedResult&lt;/b&gt;)과&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;lotteTower&quot;라는 기본 출발지 사이의 길 찾기 정보를 나타냅니다. 사용자가 지도에서 어떤 지점을 선택하면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(selectedResult가 업데이트될 때),&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;getDirections&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수가 호출되어 해당 지점까지의 길 찾기 정보를 구하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;route&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;변수에 저장됩니다.즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;route&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;변수는 사용자가 선택한 지점과 출발지 간의 길 찾기 정보를 저장하는 데 사용되며, SwiftUI의 반응형 프레임워크를 활용하여 UI를 업데이트하는 데 활용됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 속성인 @State는 SwiftUI 뷰에서 변경이 감지되면 해당 뷰를 다시 그리게끔 하는 역할을 합니다. 따라서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;route&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값이 업데이트되면 해당 값을 사용하는 뷰들이 자동으로 업데이트되어 새로운 길 찾기 정보를 표시하게 됩니다.&lt;/li&gt;
&lt;li&gt;@State private var route: MKRoute?은 SwiftUI에서 사용되는 상태 속성입니다. 이 상태 속성은 현재 선택된 지도 항목과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;MKDirections&lt;b&gt;를 사용하여 해당 항목에 대한 길 찾기 정보(&lt;/b&gt;MKRoute&lt;/b&gt;)를 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getDirections 메서드는 다음 chapter에서 제대로 활용합니다. 이해하고 넘어와 주세요&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;pre id=&quot;code_1704773087913&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI
import MapKit

struct ItemInfoView: View {
    // ItemInfoView의 상태 속성으로 주변보기 씬을 저장하는 변수
    @State private var lookAroundScene: MKLookAroundScene?
    
    // 선택된 지도 항목 및 길 찾기 정보를 나타내는 속성
    let selectedResult: MKMapItem
    let route: MKRoute?

    // 길 찾기 정보의 예상 이동 시간을 포맷팅하는 속성
    private var travelTime: String? {
        // 길 찾기 정보가 있으면 실행
        guard let route else { return nil }
        
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .abbreviated
        formatter.allowedUnits = [.hour, .minute]
        
        // 길 찾기 정보의 예상 이동 시간을 포맷팅하여 반환
        return formatter.string(from: route.expectedTravelTime)
    }
    
    var body: some View {
        // 주변보기 씬을 표시하는 뷰
        LookAroundPreview(initialScene: lookAroundScene)
            .overlay(alignment: .bottomTrailing) {
                // 선택된 지도 항목의 이름과 예상 이동 시간을 표시하는 HStack
                HStack {
                    Text(&quot;\(selectedResult.name ?? &quot;&quot;)&quot;)
                    // 예상 이동 시간이 있는 경우 표시
                    if let travelTime {
                        Text(travelTime)
                    }
                }
                .font(.caption)
                .foregroundStyle(.white)
                .padding(10)
            }
            // 뷰가 나타날 때 주변보기 씬을 가져오는 작업 수행
            .onAppear {
                getLookAroundScene()
            }
            // 선택된 지도 항목이 변경될 때마다 주변보기 씬을 업데이트하는 작업 수행
            .onChange(of: selectedResult) {
                getLookAroundScene()
            }
    }

    // 주변보기 이미지를 가져오는 기능
    func getLookAroundScene() {
        // 초기화
        lookAroundScene = nil
        // 비동기 작업 시작
        Task {
            // 선택된 지도 항목의 주변보기 씬 요청
            let request = MKLookAroundSceneRequest(mapItem: selectedResult)
            lookAroundScene = try? await request.scene
        }
    }
}&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;b&gt;MKLookAroundScene&lt;/b&gt;&amp;nbsp;&amp;nbsp;타입&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;MKLookAroundScene?&lt;/b&gt;: lookAroundScene은 주변보기(MKLookAroundScene) 씬을 나타내는 변수입니다. MKLookAroundScene은 MapKit에서 제공하는 클래스로, 지도 항목의 주변 환경을 시각적으로 나타내기 위한 3D 씬을 생성하는 데 사용됩니다. 이 변수는 @State로 선언되어 뷰의 상태를 나타냅니다.&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;getLookAroundScene&lt;/b&gt; 메서드에서 해당 주변보기 씬을 가져오고, 이후에 lookAroundScene이 업데이트되면 해당 변경을 SwiftUI에 알려서 뷰를 갱신하게 됩니다.&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;&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최종 결과물 : 사용자의 현 위치에서 주차장, 병원 찾기 앱&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&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;사용자의 현 위치를 계속 추적하여 파악해 보자&lt;/li&gt;
&lt;li&gt;사용자가 바라보고 있는 지도 화면을 기준으로 주차장, 병원을 찾아보자&lt;/li&gt;
&lt;li&gt;사용자가 하나의 marker를 선택하면 사용자의 현 위치를 기점으로 걸리는 시간 및 경로를 시각화해 보자&lt;/li&gt;
&lt;li&gt;지도에&amp;nbsp;추가적인&amp;nbsp;컨트롤&amp;nbsp;요소를&amp;nbsp;설정해&amp;nbsp;보자,&amp;nbsp;나침반,&amp;nbsp;현&amp;nbsp;위치로&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;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignLeft&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/443791002&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/dPFcrf/hyU2oxt3yx/KZJzz7dYWHGiUiZkLtVGX0/img.jpg?width=888&amp;amp;height=1920&amp;amp;face=0_0_888_1920,https://scrap.kakaocdn.net/dn/mY6I3/hyU2eBDjov/VbfMNvqsGVde5cZoBEPwKK/img.jpg?width=888&amp;amp;height=1920&amp;amp;face=0_0_888_1920&quot; data-video-width=&quot;360&quot; data-video-height=&quot;778&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1859&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/443791002?service=daum_tistory&quot; width=&quot;360&quot; height=&quot;778&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;⭐️ 꼭&amp;nbsp;권한&amp;nbsp;설정을&amp;nbsp;해주고&amp;nbsp;진행해야&amp;nbsp;합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1189&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNLMuQ/btsDdHogpYo/OXAjZ1u6JAPfcPK16q4261/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNLMuQ/btsDdHogpYo/OXAjZ1u6JAPfcPK16q4261/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNLMuQ/btsDdHogpYo/OXAjZ1u6JAPfcPK16q4261/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNLMuQ%2FbtsDdHogpYo%2FOXAjZ1u6JAPfcPK16q4261%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;1189&quot; height=&quot;345&quot; data-origin-width=&quot;1189&quot; data-origin-height=&quot;345&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 data-ke-size=&quot;size16&quot;&gt;전반적으로 코드를 수정했어요. 일단 표시한 코드를 수정, 추가 하고 설명을 읽어주세요. ㅎㅎ&lt;/p&gt;
&lt;pre id=&quot;code_1704775356981&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  ContentView.swift
//  MapKitStudy
//
//  Created by 정정욱 on 1/7/24.
//

import SwiftUI
import MapKit


//  
@MainActor class LocationsHandler: ObservableObject {
    
    static let shared = LocationsHandler()
    public let manager: CLLocationManager

    init() {
        self.manager = CLLocationManager()
        if self.manager.authorizationStatus == .notDetermined {
            self.manager.requestWhenInUseAuthorization()
        }
    }
}

.
.
.

struct ContentView: View {
    
    @ObservedObject var locationsHandler = LocationsHandler.shared
    
    @State private var visibleRegion: MKCoordinateRegion?

    //  
    @State private var position: MapCameraPosition = .userLocation(followsHeading: true, fallback: .automatic)
    
    
    @State private var searchResults: [MKMapItem] = []
    
    @State private var selectedResult: MKMapItem?
    
    @State private var route: MKRoute?
    

    var body: some View {
  
        Map(position: $position, selection: $selectedResult){
        //                
            ForEach(searchResults, id: \.self) {result in
                Marker(item: result)
            }
            .annotationTitles(.hidden)
            
            if let route {
                MapPolyline(route)
                    .stroke(.blue, lineWidth: 5)
            }

            UserAnnotation()
        }
        .mapStyle(.standard(elevation: .realistic))
        .safeAreaInset(edge: .bottom) {
            HStack {
                Spacer()
                VStack(spacing:0) {
                    if let selectedResult {
                        ItemInfoView(selectedResult: selectedResult, route: route)
                            .frame(height: 128)
                            .clipShape(RoundedRectangle (cornerRadius: 10))
                            .padding([.top, .horizontal])
                    }
                    
                    }//   바라보는 지도의 영역 위치 값을 보내 해당 영역에서 검색 하도록 
                    BeanTownButtons(position: $position, searchResults: $searchResults, visibleRegion: visibleRegion)
                        .padding(.top)
                    
                }
                Spacer()
                
            }
            .background(.ultraThinMaterial)
        }//  
        .onChange(of: searchResults) {
            withAnimation{
                position = .automatic
           }
        }
        .onChange(of: selectedResult) {
            getDirections()
        }
        .onMapCameraChange { context in
            visibleRegion = context.region
        }
				//  
        .mapControls { // 이제 버튼을 탭하여 내 위치를 표시할 수 있습니다. 내가 움직일 때 지도 카메라가 나를 따라다닐 것입니다.
            MapUserLocationButton() // 누르면 내 위치로 바로 이동함, 내가 이동하면 카메라도 이동함
            MapCompass()
            MapScaleView()
            /*
             mapControls 설정은 지도를 회전하면 나침반을 띄우고 화면을 확대하거나 축소하면 축적을 표시함
             */
        }

    }
    
    
    
    func getDirections() {
        route = nil
        guard let selectedResult else { return }
        //  
        let location = locationsHandler.manager.location
        guard let coordinate = location?.coordinate else { return }
				//  
        let request = MKDirections.Request()
        request.source = MKMapItem(placemark: MKPlacemark(coordinate: coordinate))
        request.destination = selectedResult
        
        Task {
            let directions = MKDirections(request: request)
            let response = try? await directions.calculate()
            route = response?.routes.first
        }
    }
    
}


#Preview {
    ContentView()
}&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용자의 현 위치를 계속 추적하여 파악해 보자&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;코드 설명&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704775402952&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@MainActor class LocationsHandler: ObservableObject {

static let shared = LocationsHandler()
public let manager: CLLocationManager

	init() {
	    self.manager = CLLocationManager()
	    if self.manager.authorizationStatus == .notDetermined {
	        self.manager.requestWhenInUseAuthorization()
	    }
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 특정 위치 정보를 다루기 위한 클래스를 정의하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LocationsHandler라는 클래스는 위치 정보를 다루는 데 도움을 주는 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 클래스를 사용하면 위치 정보를 관리하고, 사용자에게 위치 정보 사용 권한을 요청할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 클래스는 ObservableObject 를 채택하여 클래스의 상태가 변할 때 다른 객체들에게 알림을 보낼 수 있게 구현하였습니다. 이렇게 하면 다른 객체들이 위치 정보의 변화를 쉽게 감지하고 반응할 수 있겠죠?&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;클래스 안에는 manager라는 객체도 있는데. 이 객체는 CLLocationManager 즉 위치 정보를 다루는 데 도움이 되는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 기능들을 제공합니다. 클래스의 초기화 메서드인 init()에서는 manager를 초기화하고, 권한 상태를 확인한 뒤,&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;/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;pre id=&quot;code_1704775471510&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//  
@State private var position: MapCameraPosition = .userLocation(followsHeading: true, fallback: .automatic)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째로, position 변수는 MapCameraPosition이라는 형식을 가지고 있고, 초기값으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.userLocation(followsHeading: true, fallback: .automatic)을 가지고 있습니다.&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;MapCameraPosition은 지도에서 카메라의 위치를 나타내는 열거형(enum)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;position 변수는 이 열거형을 가지고 있으며, 초기값으로 .userLocation(followsHeading: true, fallback: .automatic)을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가지고 있습니다. .userLocation(followsHeading: true, fallback: .automatic)은 MapCameraPosition의 한 경우(case)입니다.&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;이 경우, 카메라의 위치를 사용자의 현재 위치로 설정합니다. followsHeading 매개변수는 사용자의 위치가 변경될 때마다 카메라가 사용자의 이동 방향을 따라가도록 지정하는 데 사용됩니다.&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;true로 설정되어 있으므로 카메라는 사용자의 이동 방향을 따라갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fallback 매개변수는 사용자의 위치를 가져올 수 없는 경우 카메라의 위치를 대체로 설정하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.automatic으로 설정되어 있으므로 자동 대체가 이루어집니다.&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;이 코드는 SwiftUI의 @State 속성을 사용하여 position 변수를 선언하고 초기값을 설정하는 역할을 합니다. position 변수는 지도의 카메라 위치를 나타내며, 초기값으로는 사용자의 위치를 따라가는 설정으로 설정되어 있습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704775572422&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//  
        .onChange(of: searchResults) {
            withAnimation{
                position = .automatic
           }
        }&lt;/code&gt;&lt;/pre&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;지도에 추가적인 컨트롤 요소를 설정&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1704775618407&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; .mapControls { // 이제 버튼을 탭하여 내 위치를 표시할 수 있습니다. 내가 움직일 때 지도 카메라가 나를 따라다닐 것입니다.
            MapUserLocationButton() // 누르면 내 위치로 바로 이동함, 내가 이동하면 카메라도 이동함
            MapCompass()
            MapScaleView()
            /*
             mapControls 설정은 지도를 회전하면 나침반을 띄우고 화면을 확대하거나 축소하면 축적을 표시함
             */
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 코드는 지도에 추가적인 컨트롤 요소를 설정하는 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.mapControls는 지도에 컨트롤 요소를 추가하는 데 사용되는 메서드입니다.&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;MapUserLocationButton()은 내 위치를 표시하고, 해당 위치로 바로 이동할 수 있는 버튼입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버튼을 누르면 현재 사용자의 위치로 지도가 이동하며, 사용자가 이동하면 지도 카메라도 사용자를 따라다닙니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MapCompass()는 지도 회전 시 나침반을 표시하는 요소입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지도가 회전하면 나침반이 함께 회전하여 현재 방향을 표시합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MapScaleView()는 화면을 확대하거나 축소할 때 지도의 축척을 표시하는 요소입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면을 확대하거나 축소하면 해당 축척이 지도 상단에 표시됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정된 mapControls는 지도에 버튼을 추가하여 사용자의 위치를 표시하고, 사용자가 이동할 때 지도 카메라가 따라다니며, 지도 회전 시 나침반을 표시하고, 화면 확대/축소 시 축척을 표시합니다. 이를 통해 사용자는 지도를 더 편리하게 조작할 수 있습니다.&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;b&gt;사용자가 하나의 Marker를 선택하면 사용자의 현 위치를 기점으로 걸리는 시간 및 경로를 시각화해 보자&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1704775700725&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; Map(position: $position, selection: $selectedResult){
        //                
            ForEach(searchResults, id: \.self) {result in
                Marker(item: result)
            }
            .annotationTitles(.hidden)
            
            if let route {
                MapPolyline(route)
                    .stroke(.blue, lineWidth: 5)
            }

            UserAnnotation() // 사용자의 현위치를 표시
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵이 시작하면 일단 사용자의 위치를 표시하다가 사용자가 주차장, 병원 버튼을 누른다면 selectedResult에 값이 들어오게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 그 결과 list를 하나하나 Marker로 표시합니다.&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;그리고 값이 바뀌면 즉 하나의 Marker를 선택하면&lt;/p&gt;
&lt;pre id=&quot;code_1704775752065&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func getDirections() {
        route = nil
        guard let selectedResult else { return }
        //  
        let location = locationsHandler.manager.location
        guard let coordinate = location?.coordinate else { return }
				//  
        let request = MKDirections.Request()
        request.source = MKMapItem(placemark: MKPlacemark(coordinate: coordinate))
        request.destination = selectedResult
        
        Task {
            let directions = MKDirections(request: request)
            let response = try? await directions.calculate()
            route = response?.routes.first
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 함수가 실행 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 사용자 현 위치에서 선택한 지점의 거리 계산을 수행하는 getDirections() 함수입니다.&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;먼저, 함수 내에서 route 변수를 초기화합니다. 이 변수는 경로를 저장하는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음, selectedResult 변수가 nil이 아닌지 확인합니다.(결과 Marker 중 하나를 선택한 값) nil이라면 함수를 종료합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;locationsHandler.manager.location을 통해 사용자 현재 위치를 가져옵니다.&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;coordinate 변수를 통해 현재 위치의 좌표를 가져옵니다. (사용자 위도, 경도)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;request 객체를 생성합니다. 이 객체는 경로 계산에 필요한 정보를 담고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;request.source에는 현재 위치를 나타내는 MKMapItem 객체를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 좌표를 활용하여 MKPlacemark 객체를 생성하고, 이를 MKMapItem에 설정합니다.&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;request.destination에는 경로의 목적지를 설정합니다. selectedResult 변수를 이용하여 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음, 비동기적인 작업을 수행하기 위해 Task를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MKDirections 객체를 생성하고, 이를 이용하여 directions.calculate()를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경로 계산이 완료되면, 응답 결과에서 첫 번째 경로를 가져와 route 변수에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정된 getDirections() 함수는 현재 위치와 목적지를 기반으로 경로를 계산하고, 결과를 route 변수에 저장합니다. 이를 통해 경로 안내 기능을 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704775843943&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if let route {
                MapPolyline(route)
                    .stroke(.blue, lineWidth: 5)
            }&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;그 다음 해당 route에는 현 위치에서 선택한 Marker 위치 값의 거리를 계산한 값이 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MapPolyline를 통하여 경로를 표시 할 수 있습니다.&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;MapPolyline은 지도 위에 선을 그리기 위한 SwiftUI View입니다. 이를 사용하여 경로를 시각적으로 표시할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MapPolyline은 Polyline 데이터와 선의 스타일을 인자로 받아 지도 위에 선을 그립니다.&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;Polyline은 경로를 나타내는 좌표의 집합입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 코드에서는 MapPolyline을 사용하여 route 변수에 저장된 경로를 표시합니다. 경로의 좌표는 Polyline 데이터로 전달되며, 선의 스타일은 파란색으로 설정되고 두께는 5로 설정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로, MapPolyline을 사용하여 경로를 시각적으로 표시할 수 있으며, 이를 통해 사용자에게 명확한 경로 안내를 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용자가 바라보고 있는 지도 화면을 기준으로 주차장, 병원을 찾아보자&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1704778462650&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  BeantownButtonView.swift
//  MapKitStudy
//
//  Created by 정정욱 on 1/7/24.
//

import SwiftUI
import MapKit

struct BeanTownButtons: View {
    
    @Binding var position: MapCameraPosition
    @Binding var searchResults: [MKMapItem]
    var visibleRegion: MKCoordinateRegion? //   사용자에게 표시되는 지역 내에서 검색되도록 BeantownButton을 업데이트
    
    .
    .
    .

    
    func search(for query: String) {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = query
        request.resultTypes = .pointOfInterest
        
        //   
        request.region = visibleRegion ?? MKCoordinateRegion(
            center: .lotteTower,
            span: MKCoordinateSpan (latitudeDelta: 0.0125, longitudeDelta: 0.0125)
            )

        Task {
            let search = MKLocalSearch(request: request)
            let response = try? await search.start()
            searchResults = response?.mapItems ?? []
        }
    }

}&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;b&gt;코드설명&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704778641800&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;request.region = visibleRegion ?? MKCoordinateRegion(
    center: .lotteTower,
    span: MKCoordinateSpan(latitudeDelta: 0.0125, longitudeDelta: 0.0125)
)&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;br /&gt;롯데타워&amp;nbsp;주변으로&amp;nbsp;주차장,&amp;nbsp;병원을&amp;nbsp;찾겠다는&amp;nbsp;것입니다.&lt;br /&gt;&lt;br /&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;/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: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 request.region은 visibleRegion을 기반으로 설정되고 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이는 지도 관련 기능에서 검색을 위한 지리적 영역을 제공하려는 일반적인 패턴입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;visibleRegion은 옵셔널한 MKCoordinateRegion으로, nil일 수 있습니다. 만약 visibleRegion이 nil이 아니면, 그것을 request.region의 값으로 사용합니다. 만약&amp;nbsp;visibleRegion이&amp;nbsp;nil이라면(즉,&amp;nbsp;제공되지&amp;nbsp;않았거나&amp;nbsp;nil인&amp;nbsp;경우),&amp;nbsp;기본&amp;nbsp;지역이&amp;nbsp;설정됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본 지역은 특정 중심 위치 및 지역 크기로 정의된 MKCoordinateRegion입니다.&lt;br /&gt;지역의 중심은 미리 정의된 위치 .lotteTower로 설정됩니다. 지역의&amp;nbsp;크기는&amp;nbsp;MKCoordinateSpan을&amp;nbsp;사용하여&amp;nbsp;latitudeDelta&amp;nbsp;및&amp;nbsp;longitudeDelta가&amp;nbsp;각각&amp;nbsp;0.0125로&amp;nbsp;설정됩니다.&amp;nbsp;latitudeDelta&amp;nbsp;및&amp;nbsp;longitudeDelta는&amp;nbsp;지역의&amp;nbsp;너비와&amp;nbsp;높이&amp;nbsp;또는&amp;nbsp;&quot;줌&quot;&amp;nbsp;레벨을&amp;nbsp;결정하는&amp;nbsp;데&amp;nbsp;사용되는&amp;nbsp;값으로&amp;nbsp;각각&amp;nbsp;위도와&amp;nbsp;경도의&amp;nbsp;각도입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;요약하면,&amp;nbsp;이&amp;nbsp;코드는&amp;nbsp;로컬&amp;nbsp;검색&amp;nbsp;요청의&amp;nbsp;지역을&amp;nbsp;visibleRegion이&amp;nbsp;제공되었는지&amp;nbsp;여부에&amp;nbsp;따라&amp;nbsp;설정합니다.&amp;nbsp;제공되면&amp;nbsp;해당&amp;nbsp;지역을&amp;nbsp;사용하고,&amp;nbsp;그렇지&amp;nbsp;않으면&amp;nbsp;.lotteTower&amp;nbsp;주변의&amp;nbsp;특정&amp;nbsp;줌&amp;nbsp;레벨을&amp;nbsp;사용하는&amp;nbsp;기본&amp;nbsp;지역을&amp;nbsp;설정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>APP/iOS</category>
      <category>iOS 내 location 트래킹하기</category>
      <category>iOS 사용자 위치 추적</category>
      <category>MapCamera로 보이는 위치 이동하기</category>
      <category>Mapkit 사용법</category>
      <category>MapKit 정리</category>
      <category>Marker와 Annotation</category>
      <category>MKLookAroundScene을 활용해서 이동거리 계산 / 주변 풍경 살펴보기</category>
      <category>SwiftUI MapKit</category>
      <category>SwiftUI MapKit 배우기</category>
      <category>위치 값 불러와서 MapItem으로 관리하기</category>
      <author>개발자 aloe</author>
      <guid isPermaLink="true">https://aloe-study.tistory.com/233</guid>
      <comments>https://aloe-study.tistory.com/233#entry233comment</comments>
      <pubDate>Tue, 9 Jan 2024 09:22:55 +0900</pubDate>
    </item>
    <item>
      <title>MapKit with SwiftUI 기초부터 심화까지 1 : MapKit 다루기 기본 과정</title>
      <link>https://aloe-study.tistory.com/232</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10043/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10043/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704719383035&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 MapKit for SwiftUI - WWDC23 - Videos - Apple Developer&quot; data-og-description=&quot;Discover how expanded SwiftUI support for MapKit has made it easier than ever for you to integrate Maps into your app. We'll show you how...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10043/&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10043/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/5Mwyh/hyU2rOlgfX/2rqRKeSZdaKqWJKl1vYKBK/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/10043/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10043/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/5Mwyh/hyU2rOlgfX/2rqRKeSZdaKqWJKl1vYKBK/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 MapKit for SwiftUI - WWDC23 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Discover how expanded SwiftUI support for MapKit has made it easier than ever for you to integrate Maps into your app. We'll show you how...&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://laurentbrusa.hashnode.dev/summarising-meet-mapkit-for-swiftui-from-wwdc23&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://laurentbrusa.hashnode.dev/summarising-meet-mapkit-for-swiftui-from-wwdc23&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704719449192&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;Summarising &amp;quot;Meet MapKit for SwiftUI&amp;quot; from WWDC23  &quot; data-og-description=&quot;Summarising &amp;quot;Meet MapKit for SwiftUI&amp;quot; from WWDC23   tldr a collection of notes and screenshots&quot; data-og-host=&quot;laurentbrusa.hashnode.dev&quot; data-og-source-url=&quot;https://laurentbrusa.hashnode.dev/summarising-meet-mapkit-for-swiftui-from-wwdc23&quot; data-og-url=&quot;https://laurentbrusa.hashnode.dev/summarising-meet-mapkit-for-swiftui-from-wwdc23&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/KJcHQ/hyU2trRqla/TdPiLHFINEHZuSepo7fG0K/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/cDcmDt/hyU2rgvtrc/uTalgP4pKqsFwzarvXAZKK/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/dnG4Gm/hyU2ujZdkb/3ya2XW0m7PR8NTYDq17MXK/img.jpg?width=3443&amp;amp;height=1939&amp;amp;face=0_0_3443_1939&quot;&gt;&lt;a href=&quot;https://laurentbrusa.hashnode.dev/summarising-meet-mapkit-for-swiftui-from-wwdc23&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://laurentbrusa.hashnode.dev/summarising-meet-mapkit-for-swiftui-from-wwdc23&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/KJcHQ/hyU2trRqla/TdPiLHFINEHZuSepo7fG0K/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/cDcmDt/hyU2rgvtrc/uTalgP4pKqsFwzarvXAZKK/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/dnG4Gm/hyU2ujZdkb/3ya2XW0m7PR8NTYDq17MXK/img.jpg?width=3443&amp;amp;height=1939&amp;amp;face=0_0_3443_1939');&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;Summarising &quot;Meet MapKit for SwiftUI&quot; from WWDC23  &lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Summarising &quot;Meet MapKit for SwiftUI&quot; from WWDC23   tldr a collection of notes and screenshots&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;laurentbrusa.hashnode.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Map을 지도에 표시해보자!!!&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1704719511923&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI
import MapKit

struct ContentView: View {
    var body: some View {
        Map ()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1VtTf/btsC836u4ot/VPBVJiL6hdOrtT36ir9fm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1VtTf/btsC836u4ot/VPBVJiL6hdOrtT36ir9fm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1VtTf/btsC836u4ot/VPBVJiL6hdOrtT36ir9fm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1VtTf%2FbtsC836u4ot%2FVPBVJiL6hdOrtT36ir9fm1%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;254&quot; height=&quot;483&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;599&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;끝 엄청 쉽습니다!!!!&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;지도의 Marker를 표시해보자!&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1704719686659&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension CLLocationCoordinate2D {
    static let lotteTower = CLLocationCoordinate2D(latitude: 37.5125, longitude: 127.102778)
}

struct ContentView: View {
    var body: some View {
        Map() {
            Marker(&quot;lotteTower&quot;, coordinate: .lotteTower)
        }
    }
}&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;b&gt;롯데타워를 찍어볼 거예요!!!&lt;br /&gt;기본적으로&amp;nbsp;Marker를&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;span style=&quot;background-color: #ffc9af;&quot;&gt;CLLocationCoordinate2D&lt;/span&gt; 은 Core Location 프레임워크에서 제공되는 구조체로, 지리적인 위치를 나타내는 데 사용됩니다. 이 구조체는 위도(latitude)와 경도(longitude)의 두 속성을 가지며, 지구상의 특정 지점을 나타냅니다.&lt;br /&gt;&lt;br /&gt;Marker는 기본적으로 LLocationCoordinate2D 값을 가져요. 어디인지 알아야 찍어주겠죠??&lt;/b&gt;&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;365&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qkcQW/btsDd02dyzD/WtxMMmkuJAnyrfYCmDjzkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qkcQW/btsDd02dyzD/WtxMMmkuJAnyrfYCmDjzkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qkcQW/btsDd02dyzD/WtxMMmkuJAnyrfYCmDjzkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqkcQW%2FbtsDd02dyzD%2FWtxMMmkuJAnyrfYCmDjzkK%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;270&quot; height=&quot;532&quot; data-origin-width=&quot;365&quot; data-origin-height=&quot;719&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;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;&lt;b&gt;커스텀 Marker를 만들어보자&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Marker는&amp;nbsp;기본적인&amp;nbsp;pin의&amp;nbsp;View를&amp;nbsp;보여주지만,&amp;nbsp;Annotation을&amp;nbsp;사용하면&amp;nbsp;커스텀을&amp;nbsp;통해&amp;nbsp;보여주고&amp;nbsp;싶은&amp;nbsp;pin을&amp;nbsp;찍을&amp;nbsp;수&amp;nbsp;있어요.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704719824136&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  ContentView.swift
//  MapKitStudy
//
//  Created by 정정욱 on 1/7/24.
//

import SwiftUI
import MapKit

extension CLLocationCoordinate2D {
    static let lotteTower = CLLocationCoordinate2D(latitude: 37.5125, longitude: 127.102778)
}

struct ContentView: View {
    var body: some View {
        Map() {
            Annotation(&quot;lotteTower&quot;, coordinate: .lotteTower) {
                ZStack {
                    RoundedRectangle(cornerRadius: 5)
                        .fill(.background)
                    RoundedRectangle(cornerRadius: 5)
                        .stroke(.secondary, lineWidth: 5)
                    Image(systemName: &quot;house.circle.fill&quot;)
                        .padding(5)
                }
            }
            .annotationTitles(.hidden) // 제목 감추기
        }
    }
}


#Preview {
    ContentView()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;271&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkvPhp/btsC7t5mFch/bS3ukqJpj7Z4VpOS3zWk60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkvPhp/btsC7t5mFch/bS3ukqJpj7Z4VpOS3zWk60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkvPhp/btsC7t5mFch/bS3ukqJpj7Z4VpOS3zWk60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkvPhp%2FbtsC7t5mFch%2FbS3ukqJpj7Z4VpOS3zWk60%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;271&quot; height=&quot;558&quot; data-origin-width=&quot;271&quot; data-origin-height=&quot;558&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;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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;mapStyle을 알아보자!!!!&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;b&gt;apple은 다양한 MapStyle을 제공해 주고 있습니다.&lt;br /&gt;지금&amp;nbsp;저희가&amp;nbsp;보고&amp;nbsp;있는&amp;nbsp;지도의&amp;nbsp;style은&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704719973921&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.mapStyle(.standard)&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_1704719992120&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.mapStyle(.imagery(elevation: .realistic))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 스타일은 위성 사진을 이용하여 지도를 표시합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uXH4Z/btsDbesSqSj/MTHUT8qf9Lyvhv6QHfnmhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uXH4Z/btsDbesSqSj/MTHUT8qf9Lyvhv6QHfnmhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uXH4Z/btsDbesSqSj/MTHUT8qf9Lyvhv6QHfnmhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuXH4Z%2FbtsDbesSqSj%2FMTHUT8qf9Lyvhv6QHfnmhK%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;223&quot; height=&quot;480&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1704720058606&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.mapStyle(.hybrid(elevation: .realistic))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이브리드&amp;nbsp;지도&amp;nbsp;스타일은&amp;nbsp;다양한&amp;nbsp;도로&amp;nbsp;표지판까지&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Fz4G/btsC74dxIjD/6ZY1TAKsbRk8kx3qhBeFZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Fz4G/btsC74dxIjD/6ZY1TAKsbRk8kx3qhBeFZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Fz4G/btsC74dxIjD/6ZY1TAKsbRk8kx3qhBeFZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Fz4G%2FbtsC74dxIjD%2F6ZY1TAKsbRk8kx3qhBeFZk%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;232&quot; height=&quot;479&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;562&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;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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;심화 과정 1 : 찾고 싶은 장소를 찾아봅시다!!!&amp;nbsp; ex (주차장, 병원)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도에서 롯데타워 근처 주차장, 병원을 찾아볼 거예요&lt;br /&gt;따라오시는 여러분은 다른 장소를 찾아서 확인해 보세요!!!!&lt;br /&gt;조금이라고&amp;nbsp;응용해&amp;nbsp;봐야&amp;nbsp;자기&amp;nbsp;것이&amp;nbsp;됩니다!!!&amp;nbsp;ㅎㅎ&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MKLocalSearch&lt;/b&gt;를 사용하여 lotteTower 근처의 특정 장소를 찾아보겠습니다.&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;우선 검색 버튼을 만들어 보겠습니다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704720362259&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  BeantownButtonView.swift
//  MapKitStudy
//
//  Created by 정정욱 on 1/7/24.
//

import SwiftUI
import MapKit

struct BeanTownButtons: View {
    
    @Binding var searchResults: [MKMapItem]
    
    var body: some View {
        HStack {
            Button {
                search(for: &quot;parking&quot;)
            } label: {
                Label(&quot;parking lot&quot;, systemImage: &quot;car&quot;)
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                search(for: &quot;hospital&quot;)
            } label: {
                Label(&quot;restroom&quot;, systemImage: &quot;cross.case&quot;)
            }
            .buttonStyle(.borderedProminent)
        }
        .labelStyle(.iconOnly)
    }
    

    func search(for query: String) {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = query
        request.resultTypes = .pointOfInterest
        request.region = MKCoordinateRegion(
            center: .lotteTower,
            span: MKCoordinateSpan (latitudeDelta: 0.0125, longitudeDelta: 0.0125)
            )

        Task {
            let search = MKLocalSearch(request: request)
            let response = try? await search.start()
            searchResults = response?.mapItems ?? []
        }
    }

}&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;중점적으로 봐야할 부분은 search함수 내부 코드 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704720387077&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func search(for query: String) {
    // MKLocalSearch의 Request를 생성하여 검색 조건을 설정합니다.
    let request = MKLocalSearch.Request()
    // 검색할 키워드를 설정합니다.
    request.naturalLanguageQuery = query
    // 검색 결과의 타입을 관심 지점(POI)으로 설정합니다.
    request.resultTypes = .pointOfInterest
    
    // 검색을 수행할 지역을 설정합니다. 여기서는 .lotteTower 위치 주변으로 설정합니다.
    request.region = MKCoordinateRegion(
        center: .lotteTower,
        span: MKCoordinateSpan(latitudeDelta: 0.0125, longitudeDelta: 0.0125)
    )

    // 비동기 함수로서의 Task를 사용하여 검색을 시작합니다.
    Task {
        // MKLocalSearch 객체를 생성합니다.
        let search = MKLocalSearch(request: request)
        
        // 검색을 비동기로 실행하고 결과를 받아옵니다.
        let response = try? await search.start()
        
        // 검색 결과를 searchResults에 업데이트합니다.
        searchResults = response?.mapItems ?? []
    }
}&lt;/code&gt;&lt;/pre&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;&lt;b&gt;MKLocalSearch.Request():&lt;/b&gt; MKLocalSearch.Request()는 MapKit에서 제공하는 지역 검색 요청 객체를 생성하는 메서드입니다. 이 객체를 사용하여 검색에 필요한 매개변수를 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 설명하면 버튼을 누르면 롯데타워 근처 주차장과 병원이 검색됩니다.&lt;br /&gt;&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;사용합시다&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;request.resultTypes = .pointOfInterest:&lt;/b&gt; 이 부분은 생성한 &lt;b&gt;MKLocalSearch.Request&lt;/b&gt; 객체에 대한 설정 중 하나입니다. &lt;b&gt;resultTypes&lt;/b&gt; 속성은 검색 결과의 타입을 나타내며, 여기서는 .pointOfInterest로 설정되어 있습니다.&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;이는 관심 지점(POI)을 의미하며, 주변의 특정 지점들에 대한 정보를 검색하고자 할 때 사용됩니다.&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;&lt;b&gt;MKCoordinateRegion:&lt;/b&gt; &lt;b&gt;MKCoordinateRegion&lt;b&gt;은 지도에서 특정 지역을 표현하는 데 사용되는 객체입니다. 매개변수로 받는 것은 중심 좌표(&lt;/b&gt;center&lt;/b&gt;)와 지도 영역의 스팬(&lt;b&gt;span&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;&lt;b&gt;center: .lotteTower:&lt;/b&gt; 중심 좌표를 설정합니다. 여기서는 정적으로 정의된 CLLocationCoordinate2D.lotteTower를 사용하여 Lotte Tower의 좌표를 중심으로 설정하고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;span: MKCoordinateSpan(latitudeDelta: 0.0125, longitudeDelta: 0.0125):&lt;/b&gt; 지도의 스팬을 설정합니다.&lt;/li&gt;
&lt;li&gt;latitudeDelta와 longitudeDelta는 각각 위도 및 경도의 변화를 나타냅니다.&lt;/li&gt;
&lt;li&gt;이를 조절하여 지도의 확대 수준을 조절할 수 있습니다. 작은 값은 더 큰 확대 수준을 의미합니다.&lt;/li&gt;
&lt;/ul&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;이야기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704720598548&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;searchResults = response?.mapItems ?? []&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 data-token-index=&quot;1&quot;&gt;&lt;b&gt;MKMapItem&lt;/b&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&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;MKMapItem은 MapKit 프레임워크에서 제공되는 클래스로, 지도 위의 특정 지점이나 장소를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MKMapItem은 주로 지도 검색 결과를 나타내거나, 특정 위치에 대한 정보를 표현하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MKMapItem의 인스턴스는 일반적으로 MKLocalSearch를 사용하여 수행된 지역 검색의 결과로 반환됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 MKMapItem은 특정 위치에 대한 정보를 가지고 있으며, 이 정보에는 위치의 이름, 주소, 좌표 등이 포함될 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여러 MKMapItem을 배열로 관리하면, 지도 상에 여러 장소를 표시하거나 검색 결과를 나타낼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 MKMapItem은 지도에 표시되는 항목의 기본 데이터를 캡슐화합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, MKMapItem을 사용하여 레스토랑, 호텔, 상점 등의 장소에 대한 정보를 가져와서 지도에 표시하거나 해당 장소로 길 안내 등의 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;검색 결과를 뿌려보자!!!&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1704721010955&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  ContentView.swift
//  MapKitStudy
//
//  Created by 정정욱 on 1/7/24.
//

import SwiftUI
import MapKit

extension CLLocationCoordinate2D {
    static let lotteTower = CLLocationCoordinate2D(latitude: 37.5125, longitude: 127.102778)
}

struct ContentView: View {
    
    /*   검색결과를 담을 배열
      MKLocalSearch와 같은 MapKit API가 장소를 표시할 때 사용하는 MKMapItem임
      MKMapItem로 작업하면 Marker가 콘텐츠와 형식을 자동으로 지원해 무척 유용함
      */
     @State private var searchResults: [MKMapItem] = []
    
    var body: some View {
        Map() {
            Annotation(&quot;lotteTower&quot;, coordinate: .lotteTower) {
                ZStack {
                    RoundedRectangle(cornerRadius: 5)
                        .fill(.background)
                    RoundedRectangle(cornerRadius: 5)
                        .stroke(.secondary, lineWidth: 5)
                    Image(systemName: &quot;house.circle.fill&quot;)
                        .padding(5)
                }
            }
            .annotationTitles(.hidden) // 제목 감추기
            
            //   검색 결과 마커 추가
            ForEach(searchResults, id: \.self) { result in
                Marker(item: result)
								// 결과가 MKMapItem type이였는데 
								// MKMapItem로 작업하면 Marker가 콘텐츠와 형식을 자동으로 지원해서 넣어주기만 하면 끝이에요
            }
        }
        .mapStyle(.standard)
        .safeAreaInset(edge: .bottom) {
            HStack {
                Spacer()
						//   바인딩이 되어 있어서 버튼을 누르면 바로바로 결과를 확인 할 수 있어요
                BeanTownButtons(searchResults: $searchResults)
                    .padding(.top)
                Spacer()
            }
            .background(.ultraThinMaterial)
        }

    }
}


#Preview {
    ContentView()
}&lt;/code&gt;&lt;/pre&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-origin-width=&quot;695&quot; data-origin-height=&quot;689&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lmIWq/btsC7txv7SO/6HUO5C6JC7N6MbFukKDCk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lmIWq/btsC7txv7SO/6HUO5C6JC7N6MbFukKDCk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lmIWq/btsC7txv7SO/6HUO5C6JC7N6MbFukKDCk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlmIWq%2FbtsC7txv7SO%2F6HUO5C6JC7N6MbFukKDCk0%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;695&quot; height=&quot;689&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;689&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;</description>
      <category>APP/iOS</category>
      <category>iOS MapKit 정리</category>
      <category>iOS 지도앱 만들기</category>
      <category>Mapkit Marker</category>
      <category>MapKit MKLocalSearch</category>
      <category>MapKit pin 찍는법</category>
      <category>MapKit SwiftUI 사용법</category>
      <category>Mapkit 사용법</category>
      <category>MapKit 커스텀 Marker</category>
      <category>MKLocalSearch</category>
      <category>SwiftUi MKLocalSearch</category>
      <author>개발자 aloe</author>
      <guid isPermaLink="true">https://aloe-study.tistory.com/232</guid>
      <comments>https://aloe-study.tistory.com/232#entry232comment</comments>
      <pubDate>Mon, 8 Jan 2024 22:35:50 +0900</pubDate>
    </item>
    <item>
      <title>iOS 주니어 개발자의 2023년도를 돌아보며 회고</title>
      <link>https://aloe-study.tistory.com/231</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;안녕하세요.&amp;nbsp;오랜만에&amp;nbsp;인사드립니다...&amp;nbsp;aloe입니다.&lt;br /&gt;&lt;br /&gt;블로그 포스팅을 꽤 오랫동안 쉬었습니다.. 정말 바쁜 2023년도를 보냈었는데&lt;br /&gt;네&amp;nbsp;맞습니다&amp;nbsp;핑계입니다..&amp;nbsp;다시&amp;nbsp;열심히&amp;nbsp;달려보려고&amp;nbsp;합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xCjjk/btsC5Zcvk4b/SVVjezN2CbkQAccZIvqTK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xCjjk/btsC5Zcvk4b/SVVjezN2CbkQAccZIvqTK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xCjjk/btsC5Zcvk4b/SVVjezN2CbkQAccZIvqTK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxCjjk%2FbtsC5Zcvk4b%2FSVVjezN2CbkQAccZIvqTK0%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;693&quot; height=&quot;507&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;507&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;두서없이 적었지만, 부족한 저의 글을 읽어 주실 분들에게 미리 감사드립니다.&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;해였습니다.&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;2023년도를 돌아보며&lt;/b&gt;&lt;/h2&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;b&gt;학업적인 측면에서 잘한점&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBZoV7/btsC4HpC1ac/iBDrcQNrFTRmDHMIumO9A0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBZoV7/btsC4HpC1ac/iBDrcQNrFTRmDHMIumO9A0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBZoV7/btsC4HpC1ac/iBDrcQNrFTRmDHMIumO9A0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBZoV7%2FbtsC4HpC1ac%2FiBDrcQNrFTRmDHMIumO9A0%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;1344&quot; height=&quot;196&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 컴퓨터 소프트웨어학과에 다니며 꽤 열심히 대학 생활을 했다.&lt;br /&gt;20살 때 대학에 입학한 후로 목표는 하나였다. 돈 내고 학교 다니지 않기&amp;hellip;. 수석으로 졸업해보기...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;살면서 한 번도 1등을 해보지 못했기 때문에 막연하게 한 번이라도 해보자고 생각했던 거 같다.&lt;br /&gt;운 좋게도 현재 그 목표는 이루면서 살고 있다. 수업이 재미있었다기보단. 배우고 싶은 학문이었고.&lt;br /&gt;반 오십이 된 2024년도 기준으로 나의 가장 큰 장점이 성실함아닐지 생각해 본다..&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;생각한다.&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;그래서 아무튼 결과로써 성실함을 보여줄 수 있는 지표 하나를 완성하고 있다는 게 2023년도에 잘한 점이라고 생각한다.&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;b&gt;학업적인 측면에서 부족했던점&lt;/b&gt;&lt;/h3&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;p data-ke-size=&quot;size16&quot;&gt;좋은 회사에 취업할 수 있나? 이건 또 다른 이야기라고 생각한다. 물론 필요한 역량들은 맞다..&lt;br /&gt;&lt;br /&gt;내가 하고 싶은 말은 취업을 하기 위한 공부 방법, 계획과 학점을 잘 받기 위한 공부 방법, 계획은 다르게 설계 해야 맞는다고 보는 것이다.&lt;br /&gt;암튼&amp;nbsp;본론으로&amp;nbsp;다시&amp;nbsp;돌아와서&lt;br /&gt;&lt;br /&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;/li&gt;
&lt;li&gt;선 후배 간 교류 가능&lt;/li&gt;
&lt;li&gt;CS 지식 학습 가능&lt;/li&gt;
&lt;/ul&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이대로 가다간 취업 시작에서 저 개발자인데요. 이력은 졸업장 밖에 없습니다&amp;hellip;ㅎㅎ를 외치고 바로 탈락 +외면당할 게 뻔했으니까&amp;hellip;&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZAiZY/btsC4rf0gNO/5o9DVsZQnuGHqnc7IcfrPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZAiZY/btsC4rf0gNO/5o9DVsZQnuGHqnc7IcfrPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZAiZY/btsC4rf0gNO/5o9DVsZQnuGHqnc7IcfrPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZAiZY%2FbtsC4rf0gNO%2F5o9DVsZQnuGHqnc7IcfrPk%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;412&quot; height=&quot;327&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 뭘 개발할 수 있고, 뭘 잘하는 사람이지? 어떻게 어느 분야로 취업하지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흠 일단 전공자 타이틀 학과(컴퓨터공학, 소프트웨어학과 등등)를 다니는 학부생들 80%는 이 생각을 하는 거 같다.&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;개발자는 개발을 하는 사람 즉 뭔가를 만드는 사람인데 전공지식이 개발을 하는데 도움을 줄 수 있지만 정작 뭔가를 만들 줄 모르는사람이라는 걸 깨달은 것이다. 그리하여 어느 분야를 깊게 공부(개발)를 해볼까? 하다가 어릴 때부터 아이폰만을 사용했고 애플을 좋아하기도 해서 맥북을 구매한 다음 iOS 개발을 본격적으로 하기 시작했다.&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;이전까지 6개월 동안 Flutter를 이용하여 앱 출시까지 하고 공모전도 나가보았기에 앱 쪽은 알 수 없는 근거 없는 자신감 ... 진입장벽이 낮다고 생각했다. (오만했다 매우 오만했다.)&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;iOS 개발 시작.&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfWnQS/btsC6UIQmXU/qVGZwF7FeWLAVnafr76kC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfWnQS/btsC6UIQmXU/qVGZwF7FeWLAVnafr76kC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfWnQS/btsC6UIQmXU/qVGZwF7FeWLAVnafr76kC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfWnQS%2FbtsC6UIQmXU%2FqVGZwF7FeWLAVnafr76kC0%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;689&quot; height=&quot;398&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;398&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;2023년도 1월부터 iOS 개발을 제대로 시작했다.&lt;br /&gt;인생 첫 부트캠프 설레는 마음으로 공부를 시작했고 Swift 문법에 대하여 깊게 학습하고 UIKit으로 많은 앱을 만들어보며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실력을 향상할 수 있었다. 사실 말이 2개월 과정이지 앱 만들기 과정 그리고 복습을 포함하면 5개월&amp;nbsp;과정인&amp;nbsp;거&amp;nbsp;같다.&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;재미있게&amp;nbsp;공부&amp;nbsp;했던&amp;nbsp;거&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;&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;부트캠프에서 잘한 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아무래도&amp;nbsp;Swift&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;&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;않을까? &lt;br /&gt;&lt;br /&gt;근데 뭐 사실 취업도 취업이지만 에초에 해당 전공을 하게 된 이유가 머릿속에 생각나는 아이디어들을 바로바로 만들어&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;생각한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUPyFC/btsC4whn2tC/mHI368ZpCTk94kUfbiFENK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUPyFC/btsC4whn2tC/mHI368ZpCTk94kUfbiFENK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUPyFC/btsC4whn2tC/mHI368ZpCTk94kUfbiFENK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUPyFC%2FbtsC4whn2tC%2FmHI368ZpCTk94kUfbiFENK%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;272&quot; height=&quot;173&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;555&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUOQMD/btsC5XMyNWN/L9a0LsM4fljPKLrHtiha7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUOQMD/btsC5XMyNWN/L9a0LsM4fljPKLrHtiha7K/img.png&quot; data-alt=&quot;출처 : https://maily.so/mindbook/posts/7a231d31&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUOQMD/btsC5XMyNWN/L9a0LsM4fljPKLrHtiha7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUOQMD%2FbtsC5XMyNWN%2FL9a0LsM4fljPKLrHtiha7K%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;554&quot; height=&quot;444&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;555&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://maily.so/mindbook/posts/7a231d31&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;(그래서 생각난 아이디어를 구현해 앱까지 출시했다&amp;hellip;. 추후 회고 예정)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;부트캠프에서 부족했던점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;느꼈다.&lt;br /&gt;&lt;br /&gt;물론&amp;nbsp;기록을&amp;nbsp;노선에다가&amp;nbsp;하나하나&amp;nbsp;잘&amp;nbsp;정리하고&amp;nbsp;1일&amp;nbsp;1커밋&amp;nbsp;하면서&amp;nbsp;정리를&amp;nbsp;나름&amp;nbsp;하긴&amp;nbsp;했지만&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGGuts/btsC9kUfYZK/vz70ZeYxLDmG2DRmWUHJUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGGuts/btsC9kUfYZK/vz70ZeYxLDmG2DRmWUHJUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGGuts/btsC9kUfYZK/vz70ZeYxLDmG2DRmWUHJUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGGuts%2FbtsC9kUfYZK%2Fvz70ZeYxLDmG2DRmWUHJUk%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;746&quot; height=&quot;163&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;163&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;개발 또한 영어, 불어 등등 마찬가지로 일단 Swift라는 언어를 배웠다면 실제로 써봐야 완전히 내 것이 된다는 걸 알았다.&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;주관적인&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;정말&amp;nbsp;다&amp;nbsp;외우려고&amp;nbsp;했다&amp;hellip;&amp;nbsp;조금만&amp;nbsp;또다시&amp;nbsp;복습하지&amp;nbsp;않으면&amp;nbsp;까먹는&amp;nbsp;것을&amp;hellip;&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;hellip;&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;그래서 많은 개발 학도들이 GitHub이라는 비휘발성 메모리 즉 실제 영구적인 저장장소에 저장하며 기록하는 거 같다.&lt;br /&gt;블로그&amp;nbsp;포스팅도&amp;nbsp;마찬가지고&amp;hellip;&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;있을&amp;nbsp;정도로&amp;nbsp;설명하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋을 남기거나 주석을 다는 것이었다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;요약 다 외우려말자, 필수적인 건&amp;nbsp;&amp;nbsp;외우더라도 나중에 봤을 때 이해할 수 있게 설명해서 정리해 두자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개선할 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 포스팅을 열심히 하자 그리고 글을 좀 더 잘 쓰도록 노력하자&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;br /&gt;&lt;br /&gt;2개월 3개월 뒤에 돌아왔을 때는 다시 모르는 사람에게 설명하듯이 글을 써 기록을 남기는 게 중요 한 거 같다.&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;그리하여&amp;nbsp;2024&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;1시간~3시간의&amp;nbsp;포스팅이&amp;nbsp;3개월&amp;nbsp;뒤에&amp;nbsp;나에게,&amp;nbsp;혹은&amp;nbsp;다른&amp;nbsp;사람들의&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;클론코딩에 대하여&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DK9RW/btsDd3kbkYU/IA5GdB59rkomRtrQ4Lpjb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DK9RW/btsDd3kbkYU/IA5GdB59rkomRtrQ4Lpjb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DK9RW/btsDd3kbkYU/IA5GdB59rkomRtrQ4Lpjb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDK9RW%2FbtsDd3kbkYU%2FIA5GdB59rkomRtrQ4Lpjb1%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;1190&quot; height=&quot;477&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;477&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부트캠프가&amp;nbsp;끝난&amp;nbsp;후&amp;nbsp;포트폴리오용으로&amp;nbsp;추천받은&amp;nbsp;강의가&amp;nbsp;Twitter&amp;nbsp;클론&amp;nbsp;코딩&amp;nbsp;강의였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개월 반 정도 들었던 거 같은데&lt;br /&gt;클론 코딩을 하며 느낀 점이다. 클론 코딩의 장점은 해당 로직을 어떻게 구현할 수 있는지 볼 수 있다는 것이다.&lt;br /&gt;여기서&amp;nbsp;포인트는&amp;nbsp;&amp;ldquo;단지&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있다는&amp;nbsp;것&amp;rdquo;..&amp;nbsp;&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;매우&amp;nbsp;오만했다.)&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;b&gt;장점이라고&amp;nbsp;생각하는&amp;nbsp;점&lt;/b&gt;&lt;/h3&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;있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 육각형 개발자라는 책을 읽었는데 좋은 글이 있었다.&lt;br /&gt;&amp;ldquo;개발은 코드를 짜는 거뿐만 아니라 서비스를 설계하고, 출시 했다면 유지보수 등등 이 일련의 과정 전체가 개발이라는 것&amp;rdquo;&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;있다.&lt;br /&gt;&lt;br /&gt;그런&amp;nbsp;의미에서&amp;nbsp;보면&amp;nbsp;많이&amp;nbsp;배웠던&amp;nbsp;거&amp;nbsp;같다.&amp;nbsp;MVVM&amp;nbsp;패턴을&amp;nbsp;활용하여&amp;nbsp;UIKit으로&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;Firebase는 어떻게 사용할 수 있는지 등등 다만 부트캠프에서 했던 실수를 반복하고 싶지는 않아서&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;br /&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;b&gt;단점, 개선할점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;513&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSQTz4/btsC4whppLB/gJKKDj2Hdrw4ekgV9jR530/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSQTz4/btsC4whppLB/gJKKDj2Hdrw4ekgV9jR530/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSQTz4/btsC4whppLB/gJKKDj2Hdrw4ekgV9jR530/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSQTz4%2FbtsC4whppLB%2FgJKKDj2Hdrw4ekgV9jR530%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;515&quot; height=&quot;410&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;513&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;강의를 구매해서 강의를 다 듣는다.&lt;/li&gt;
&lt;li&gt;내가&amp;nbsp;만들고&amp;nbsp;싶은&amp;nbsp;서비스를&amp;nbsp;구현한다.&lt;/li&gt;
&lt;/ol&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;아이디어는&amp;nbsp;있지만&amp;nbsp;어떻게&amp;nbsp;개발할지&amp;nbsp;&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;위에&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;hellip;&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;ldquo;가이드라인을 알려줬으니 이제 네가 생각하는 걸 만들어~&amp;rdquo;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;또 한번의 부트캠프 멋쟁이사자(테킷 &lt;b&gt;앱 스쿨 : iOS 3기)&lt;/b&gt;&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-01-08 오후 5.12.39.png&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VuKMh/btsC8UIfQMB/FtVLz5IIl7skWq8iy203eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VuKMh/btsC8UIfQMB/FtVLz5IIl7skWq8iy203eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VuKMh/btsC8UIfQMB/FtVLz5IIl7skWq8iy203eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVuKMh%2FbtsC8UIfQMB%2FFtVLz5IIl7skWq8iy203eK%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;325&quot; height=&quot;561&quot; data-filename=&quot;스크린샷 2024-01-08 오후 5.12.39.png&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앨런&amp;nbsp;부트캠프는&amp;nbsp;iOS&amp;nbsp;개발&amp;nbsp;기준으로&amp;nbsp;Swift&amp;nbsp;문법과&amp;nbsp;UIKit&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;하지만 SwiftUI는 따로 배워야 했고. 이글의 서두에서 언급했듯이 필요하다고 느꼈던 협업 개발을 해보지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 SwiftUI를 배워보고 싶었고 협업 개발을 해보고 싶어서 멋쟁이 사자(테킷 앱 스쿨 : iOS 3기 ) 지원했다. 아무튼&amp;hellip; 합격했다.&lt;br /&gt;&lt;br /&gt;여담으로&amp;nbsp;당시&amp;nbsp;apple&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;라이프&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;잘한 점, 장점 ing&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI로&amp;nbsp;개발하는&amp;nbsp;것이&amp;nbsp;익숙해졌다.&amp;nbsp;사실&amp;nbsp;과정을&amp;nbsp;진행하면서&amp;nbsp;SwiftUI를&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;git&amp;nbsp;컨벤션,&amp;nbsp;git&amp;nbsp;Flow&amp;nbsp;전략,&amp;nbsp;노션을&amp;nbsp;통한&amp;nbsp;워크스페이스&amp;nbsp;관리,&amp;nbsp;기획&amp;nbsp;단계의&amp;nbsp;MVP&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;알아갈&amp;nbsp;수&amp;nbsp;있었다.&amp;nbsp;같은&amp;nbsp;팀에&amp;nbsp;현업에서&amp;nbsp;개발하다&amp;nbsp;오신&amp;nbsp;분이&amp;nbsp;3분이나&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;1주일이라는&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/APP-iOS3rd/PJ2T1_WeatherPlaylist&quot;&gt;https://github.com/APP-iOS3rd/PJ2T1_WeatherPlaylist&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;부족한 점, 개선할 점 ing&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으로 내가 부족하다는 게 많다는 걸 느꼈다. 그리고 git이 완벽하게 익숙하지 않다 보니 branch를 merge하는 과정에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;conflict가 많이 일어났고, 코드 리뷰를 겪어보며 내 코드가 얼마나 지저분하고 스파게티 코드인지 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 리뷰&amp;hellip; 받는 것, 하는 것 둘 다 아직 필자가 아주 부족해서 시험시간 5분 전처럼 떨리는 마음으로 임하고 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후에 최적화에 대하여 학습해 보고 Clean&amp;nbsp;Architecture를 꼭 한번 읽어봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경험 덕분에 현재는 짬짬이 &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;Clean Code&lt;/span&gt;라는 책을 읽게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멋사가 끝나면 전체적인 회고를 한 번 더 해서 포스팅하겠다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;다음 회고 이야기&lt;/b&gt;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;드디어&amp;nbsp;내&amp;nbsp;것을&amp;nbsp;만들다,&amp;nbsp;앱&amp;nbsp;출시&amp;nbsp;회고&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLDjPj/btsC52G6j6B/dp3PY9UJfvHMsnocSEq6IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLDjPj/btsC52G6j6B/dp3PY9UJfvHMsnocSEq6IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLDjPj/btsC52G6j6B/dp3PY9UJfvHMsnocSEq6IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLDjPj%2FbtsC52G6j6B%2Fdp3PY9UJfvHMsnocSEq6IK%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;1185&quot; height=&quot;471&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;471&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Google Career Certificates 프로그램 선발, 후기&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dX2lxv/btsC6SYBaGI/Ek07Skg8ogxGiwX6h8Kku1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dX2lxv/btsC6SYBaGI/Ek07Skg8ogxGiwX6h8Kku1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dX2lxv/btsC6SYBaGI/Ek07Skg8ogxGiwX6h8Kku1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdX2lxv%2FbtsC6SYBaGI%2FEk07Skg8ogxGiwX6h8Kku1%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;502&quot; height=&quot;283&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;283&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1155&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E8EEc/btsC4sF4FQI/WnCdqopGfznLNELG3YH5JK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E8EEc/btsC4sF4FQI/WnCdqopGfznLNELG3YH5JK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E8EEc/btsC4sF4FQI/WnCdqopGfznLNELG3YH5JK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE8EEc%2FbtsC4sF4FQI%2FWnCdqopGfznLNELG3YH5JK%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;1155&quot; height=&quot;526&quot; data-origin-width=&quot;1155&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>2023 iOS 개발자</category>
      <category>2023 개발자 회고</category>
      <category>iOS 개발 시작하기</category>
      <category>iOS 개발자</category>
      <category>iOS 개발자 취준</category>
      <category>iOS 개발자 회고</category>
      <category>iOS 부트캠프</category>
      <category>iOS 주니어 개발자</category>
      <category>iOS 클론코딩</category>
      <category>클론코딩</category>
      <author>개발자 aloe</author>
      <guid isPermaLink="true">https://aloe-study.tistory.com/231</guid>
      <comments>https://aloe-study.tistory.com/231#entry231comment</comments>
      <pubDate>Mon, 8 Jan 2024 17:27:40 +0900</pubDate>
    </item>
    <item>
      <title>Memoir Mate 개인정보처리방침</title>
      <link>https://aloe-study.tistory.com/230</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt; Memoir Mate&amp;gt;('&lt;a href=&quot;https://aloe-study.tistory.com/'이하&quot;&gt;https://aloe-study.tistory.com/'이하&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;'aloe 기술 블로그')은(는) 「개인정보 보호법」 제30조에 따라 정보주체의 개인정보를 보호하고 이와 관련한 고충을 신속하고 원활하게 처리할 수 있도록 하기 위하여 다음과 같이 개인정보 처리방침을 수립&amp;middot;공개합니다.&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;2023년&lt;span&gt; 12&lt;/span&gt;월&lt;span&gt; 19&lt;/span&gt;부터 적용됩니다.&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;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제1조(개인정보의 처리 목적)&lt;br /&gt;&lt;br /&gt;&amp;lt; Memoir Mate &amp;gt;('&lt;a href=&quot;https://aloe-study.tistory.com/'이하&quot;&gt;https://aloe-study.tistory.com/'이하&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;'aloe 기술 블로그')은(는) 다음의 목적을 위하여 개인정보를 처리합니다. 처리하고 있는 개인정보는 다음의 목적 이외의 용도로는 이용되지 않으며 이용 목적이 변경되는 경우에는 「개인정보 보호법」 제18조에 따라 별도의 동의를 받는 등 필요한 조치를 이행할 예정입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제2조(개인정보의 처리 및 보유 기간)&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;①&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt; Memoir Mate &amp;gt;은(는) 법령에 따른 개인정보 보유&amp;middot;이용기간 또는 정보주체로부터 개인정보를 수집 시에 동의받은 개인정보 보유&amp;middot;이용기간 내에서 개인정보를 처리&amp;middot;보유합니다.&lt;br /&gt;&lt;br /&gt;② 각각의 개인정보 처리 및 보유 기간은 다음과 같습니다.&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 style=&quot;list-style-type: disc;&quot;&gt;1.&amp;lt;홈페이지 회원가입 및 관리&amp;gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&amp;lt;홈페이지 회원가입 및 관리&amp;gt;와 관련한 개인정보는 수집.이용에 관한 동의일로부터&amp;lt;없음&amp;gt;까지 위 이용목적을 위하여 보유.이용됩니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;보유근거 : 없음&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;관련법령 :&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;예외사유 : 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제3조(처리하는 개인정보의 항목)&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;①&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt; Memoir Mate &amp;gt;은(는) 다음의 개인정보 항목을 처리하고 있습니다.&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 style=&quot;list-style-type: disc;&quot;&gt;1&amp;lt; 홈페이지 회원가입 및 관리 &amp;gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;필수항목 : Email, 이름&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;선택항목 : 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제4조(개인정보의 파기절차 및 파기방법)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;① &amp;lt; Memoir Mate &amp;gt; 은(는) 개인정보 보유기간의 경과, 처리목적 달성 등 개인정보가 불필요하게 되었을 때에는 지체없이 해당 개인정보를 파기합니다.&lt;br /&gt;&lt;br /&gt;② 정보주체로부터 동의받은 개인정보 보유기간이 경과하거나 처리목적이 달성되었음에도 불구하고 다른 법령에 따라 개인정보를 계속 보존하여야 하는 경우에는, 해당 개인정보를 별도의 데이터베이스(DB)로 옮기거나 보관장소를 달리하여 보존합니다.&lt;br /&gt;1. 법령 근거 :&lt;br /&gt;2. 보존하는 개인정보 항목 : 계좌정보, 거래날짜&lt;br /&gt;&lt;br /&gt;③ 개인정보 파기의 절차 및 방법은 다음과 같습니다.&lt;br /&gt;1. 파기절차&lt;br /&gt;&amp;lt; Memoir Mate &amp;gt; 은(는) 파기 사유가 발생한 개인정보를 선정하고, &amp;lt; 우기 &amp;gt; 의 개인정보 보호책임자의 승인을 받아 개인정보를 파기합니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 파기방법&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제5조(정보주체와 법정대리인의 권리&amp;middot;의무 및 그 행사방법에 관한 사항)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;① 정보주체는 Memoir Mate에 대해 언제든지 개인정보 열람&amp;middot;정정&amp;middot;삭제&amp;middot;처리정지 요구 등의 권리를 행사할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;② 제1항에 따른 권리 행사는Memoir Mate에 대해 「개인정보 보호법」 시행령 제41조제1항에 따라 서면, 전자우편, 모사전송(FAX) 등을 통하여 하실 수 있으며 우기은(는) 이에 대해 지체 없이 조치하겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;③ 제1항에 따른 권리 행사는 정보주체의 법정대리인이나 위임을 받은 자 등 대리인을 통하여 하실 수 있습니다.이 경우 &amp;ldquo;개인정보 처리 방법에 관한 고시(제2020-7호)&amp;rdquo; 별지 제11호 서식에 따른 위임장을 제출하셔야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;④ 개인정보 열람 및 처리정지 요구는 「개인정보 보호법」 제35조 제4항, 제37조 제2항에 의하여 정보주체의 권리가 제한 될 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;⑤ 개인정보의 정정 및 삭제 요구는 다른 법령에서 그 개인정보가 수집 대상으로 명시되어 있는 경우에는 그 삭제를 요구할 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;⑥ Memoir Mate은(는) 정보주체 권리에 따른 열람의 요구, 정정&amp;middot;삭제의 요구, 처리정지의 요구 시 열람 등 요구를 한 자가 본인이거나 정당한 대리인인지를 확인합니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제6조(개인정보의 안전성 확보조치에 관한 사항)&lt;br /&gt;&lt;br /&gt;&amp;lt; Memoir Mate &amp;gt;은(는) 개인정보의 안전성 확보를 위해 다음과 같은 조치를 취하고 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 정기적인 자체 감사 실시&lt;br /&gt;개인정보 취급 관련 안정성 확보를 위해 정기적(분기 1회)으로 자체 감사를 실시하고 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제7조(개인정보를 자동으로 수집하는 장치의 설치&amp;middot;운영 및 그 거부에 관한 사항)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;Memoir Mate&amp;nbsp;은(는) 정보주체의 이용정보를 저장하고 수시로 불러오는 &amp;lsquo;쿠키(cookie)&amp;rsquo;를 사용하지 않습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제8조(행태정보의 수집&amp;middot;이용&amp;middot;제공 및 거부 등에 관한 사항)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;행태정보의 수집&amp;middot;이용&amp;middot;제공 및 거부등에 관한 사항&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;개인정보처리자명&amp;gt;은(는) 온라인 맞춤형 광고 등을 위한 행태정보를 수집&amp;middot;이용&amp;middot;제공하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제9조(추가적인 이용&amp;middot;제공 판단기준)&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt; Memoir Mate &amp;gt; 은(는) ｢개인정보 보호법｣ 제15조제3항 및 제17조제4항에 따라 ｢개인정보 보호법 시행령｣ 제14조의2에 따른 사항을 고려하여 정보주체의 동의 없이 개인정보를 추가적으로 이용&amp;middot;제공할 수 있습니다.&lt;br /&gt;이에 따라 &amp;lt; Memoir Mate &amp;gt; 가(이) 정보주체의 동의 없이 추가적인 이용&amp;middot;제공을 하기 위해서 다음과 같은 사항을 고려하였습니다.&lt;br /&gt;▶ 개인정보를 추가적으로 이용&amp;middot;제공하려는 목적이 당초 수집 목적과 관련성이 있는지 여부&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;▶ 개인정보를 수집한 정황 또는 처리 관행에 비추어 볼 때 추가적인 이용&amp;middot;제공에 대한 예측 가능성이 있는지 여부&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;▶ 개인정보의 추가적인 이용&amp;middot;제공이 정보주체의 이익을 부당하게 침해하는지 여부&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;▶ 가명처리 또는 암호화 등 안전성 확보에 필요한 조치를 하였는지 여부&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;※ 추가적인 이용&amp;middot;제공 시 고려사항에 대한 판단기준은 사업자/단체 스스로 자율적으로 판단하여 작성&amp;middot;공개함&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제10조(가명정보를 처리하는 경우 가명정보 처리에 관한 사항)&lt;br /&gt;&lt;br /&gt;&amp;lt; Memoir Mate &amp;gt; 은(는) 다음과 같은 목적으로 가명정보를 처리하고 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▶ 가명정보의 처리 목적&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 직접작성 가능합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▶ 가명정보의 처리 및 보유기간&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 직접작성 가능합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▶ 가명정보의 제3자 제공에 관한 사항(해당되는 경우에만 작성)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 직접작성 가능합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▶ 가명정보 처리의 위탁에 관한 사항(해당되는 경우에만 작성)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 직접작성 가능합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▶ 가명처리하는 개인정보의 항목&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 직접작성 가능합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▶ 법 제28조의4(가명정보에 대한 안전조치 의무 등)에 따른 가명정보의 안전성 확보조치에 관한 사항&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 직접작성 가능합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;제11조 (개인정보 보호책임자에 관한 사항)&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;①&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;Memoir Mate&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;은(는) 개인정보 처리에 관한 업무를 총괄해서 책임지고, 개인정보 처리와 관련한 정보주체의 불만처리 및 피해구제 등을 위하여 아래와 같이 개인정보 보호책임자를 지정하고 있습니다.&lt;/b&gt;&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 style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;▶ 개인정보 보호책임자&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;성명 :정정욱&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;직책 :대표&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;직급 :CEO&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;연락처 :01073392128, jeonguk29@naver.com,&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 개인정보 보호 담당부서로 연결됩니다.&lt;/b&gt;&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;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;▶ 개인정보 보호 담당부서&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;부서명 :&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;담당자 :&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;연락처 :, ,&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② 정보주체께서는 Memoir Mate 의 서비스(또는 사업)을 이용하시면서 발생한 모든 개인정보 보호 관련 문의, 불만처리, 피해구제 등에 관한 사항을 개인정보 보호책임자 및 담당부서로 문의하실 수 있습니다. Memoir Mate 은(는) 정보주체의 문의에 대해 지체 없이 답변 및 처리해드릴 것입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제12조(국내대리인의 지정)&lt;br /&gt;&lt;br /&gt;정보주체는 ｢개인정보 보호법｣ 제39조의11에 따라 지정된 &amp;lt; Memoir Mate &amp;gt;의 국내대리인에게 개인정보 관련 고충처리 등의 업무를 위하여 연락을 취할 수 있습니다. &amp;lt; Memoir Mate &amp;gt;은(는) 정보주체의 개인정보 관련 고충처리 등 개인정보 보호책임자의 업무 등을 신속하게 처리할 수 있도록 노력하겠습니다.&lt;br /&gt;&lt;br /&gt;▶ &amp;lt; Memoir Mate &amp;gt; 은(는) ｢개인정보 보호법｣ 제39조의11에 따라 국내대리인을 지정하였습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 국내대리인의 성명 : 정정욱&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 국내대리인의 주소 : 서울 특별시&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 국내대리인의 전화번호 : 01073392128&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 국내대리인의 전자우편 주소 : [대리인 전자우편_직접입력]&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;제13조(개인정보의 열람청구를 접수&amp;middot;처리하는 부서)&lt;br /&gt;정보주체는 ｢개인정보 보호법｣ 제35조에 따른 개인정보의 열람 청구를 아래의 부서에 할 수 있습니다.&lt;br /&gt;&amp;lt; Memoir Mate &amp;gt;은(는) 정보주체의 개인정보 열람청구가 신속하게 처리되도록 노력하겠습니다.&lt;/b&gt;&lt;/b&gt;&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 style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;▶ 개인정보 열람청구 접수&amp;middot;처리 부서&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;부서명 :&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;담당자 :&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;연락처 : , ,&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제14조(정보주체의 권익침해에 대한 구제방법)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;정보주체는 개인정보침해로 인한 구제를 받기 위하여 개인정보분쟁조정위원회, 한국인터넷진흥원 개인정보침해신고센터 등에 분쟁해결이나 상담 등을 신청할 수 있습니다. 이 밖에 기타 개인정보침해의 신고, 상담에 대하여는 아래의 기관에 문의하시기 바랍니다.&lt;br /&gt;&lt;br /&gt;1. 개인정보분쟁조정위원회 : (국번없이) 1833-6972 (www.kopico.go.kr)&lt;br /&gt;2. 개인정보침해신고센터 : (국번없이) 118 (privacy.kisa.or.kr)&lt;br /&gt;3. 대검찰청 : (국번없이) 1301 (www.spo.go.kr)&lt;br /&gt;4. 경찰청 : (국번없이) 182 (ecrm.cyber.go.kr)&lt;br /&gt;&lt;br /&gt;「개인정보보호법」제35조(개인정보의 열람), 제36조(개인정보의 정정&amp;middot;삭제), 제37조(개인정보의 처리정지 등)의 규정에 의한 요구에 대 하여 공공기관의 장이 행한 처분 또는 부작위로 인하여 권리 또는 이익의 침해를 받은 자는 행정심판법이 정하는 바에 따라 행정심판을 청구할 수 있습니다.&lt;br /&gt;&lt;br /&gt;※ 행정심판에 대해 자세한 사항은 중앙행정심판위원회(www.simpan.go.kr) 홈페이지를 참고하시기 바랍니다.&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제15조(영상정보처리기기 운영&amp;middot;관리에 관한 사항)&lt;br /&gt;①&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt; Memoir Mate &amp;gt;은(는) 아래와 같이 영상정보처리기기를 설치&amp;middot;운영하고 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1.영상정보처리기기 설치근거&amp;middot;목적 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;lt; Memoir Mate &amp;gt;&lt;/span&gt;의&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 style=&quot;list-style-type: disc;&quot;&gt;2.설치 대수, 설치 위치, 촬영 범위 :&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;설치대수 : 대&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;설치위치 :&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;촬영범위 :&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3.관리책임자, 담당부서 및 영상정보에 대한 접근권한자 :&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 style=&quot;list-style-type: disc;&quot;&gt;4.영상정보 촬영시간, 보관기간, 보관장소, 처리방법&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;촬영시간 : 시간&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;보관기간 : 촬영시부터&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;보관장소 및 처리방법 :&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5.영상정보 확인 방법 및 장소 :&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;6.정보주체의 영상정보 열람 등 요구에 대한 조치 : 개인영상정보 열람.존재확인 청구서로 신청하여야 하며, 정보주체 자신이 촬영된 경우 또는 명백히 정보주체의 생명.신체.재산 이익을 위해 필요한 경우에 한해 열람을 허용함&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;7.영상정보 보호를 위한 기술적.관리적.물리적 조치 :&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제16조(개인정보 처리방침 변경)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;① 이 개인정보처리방침은 2023년 12월 19부터 적용됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;② 이전의 개인정보 처리방침은 아래에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예시 ) - 20XX. X. X ~ 20XX. X. X 적용 (클릭)&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예시 ) - 20XX. X. X ~ 20XX. X. X 적용 (클릭)&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예시 ) - 20XX. X. X ~ 20XX. X. X 적용 (클릭)&lt;/p&gt;</description>
      <author>개발자 aloe</author>
      <guid isPermaLink="true">https://aloe-study.tistory.com/230</guid>
      <comments>https://aloe-study.tistory.com/230#entry230comment</comments>
      <pubDate>Tue, 19 Dec 2023 16:52:33 +0900</pubDate>
    </item>
    <item>
      <title>영진위 API활용 영잘알 App 완성</title>
      <link>https://aloe-study.tistory.com/223</link>
      <description>&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bgjsEt/btsidkNsFsx/OIiwCDm5FUYvyxVvvyl651/MovieJJK%203.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;MovieJJK 3.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.05MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/BEzhL/btsicMKj4rZ/yE6T8qdSX162NZwNDqKGNK/MovieJJK%204.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;MovieJJK 4.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.69MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNlIv9/btsiaDm3UQo/Gx0sxDvzi6k2zqNP4zHdR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNlIv9/btsiaDm3UQo/Gx0sxDvzi6k2zqNP4zHdR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNlIv9/btsiaDm3UQo/Gx0sxDvzi6k2zqNP4zHdR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNlIv9%2FbtsiaDm3UQo%2FGx0sxDvzi6k2zqNP4zHdR1%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;359&quot; height=&quot;778&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zfAbJ/btsibu4fo8l/hNUAKozOp2TUJm8n8yqts1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zfAbJ/btsibu4fo8l/hNUAKozOp2TUJm8n8yqts1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zfAbJ/btsibu4fo8l/hNUAKozOp2TUJm8n8yqts1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzfAbJ%2Fbtsibu4fo8l%2FhNUAKozOp2TUJm8n8yqts1%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;347&quot; height=&quot;2556&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b20at4/btsiagMrMEi/ZYONkKoslPB962Jti99aSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b20at4/btsiagMrMEi/ZYONkKoslPB962Jti99aSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b20at4/btsiagMrMEi/ZYONkKoslPB962Jti99aSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb20at4%2FbtsiagMrMEi%2FZYONkKoslPB962Jti99aSk%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;351&quot; height=&quot;2556&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;pre id=&quot;code_1685587451185&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  ViewController.swift
//  MovieJJK
//
//  Created by comsoft on 2023/05/04.
//

import UIKit

let name = [&quot;1. 가디언즈 오브 갤럭시: Volume 3&quot;, &quot;2. 슈퍼 마리오 브라더스&quot;, &quot;3. 드림&quot;,&quot;4. 존 윅 4&quot;, &quot;5. 스즈메의 문단속&quot;, &quot;6. 옥수역귀신&quot;, &quot;7. 리바운드&quot;, &quot;8. 더 퍼스트 슬램덩크&quot;, &quot;9. 킬링 로맨스&quot;, &quot;10. 치치핑핑의 쿵쿵따 탐험대&quot;,]


// 구조체라 모두 프로토콜을 채택한 것임
struct MovieData: Codable {
    let boxOfficeResult: BoxOfficeResult
}

// MARK: - BoxOfficeResult
struct BoxOfficeResult: Codable {
    let dailyBoxOfficeList: [DailyBoxOfficeList]
}

// MARK: - DailyBoxOfficeList
struct DailyBoxOfficeList: Codable {
    
    let movieNm: String
    let audiAcc: String // 누적관객수를 출력합니다.
    let audiCnt: String // 해당일의 관객수를 출력합니다.
    let salesShare: String // 해당일자 상영작의 매출총액 대비 해당 영화의 매출비율을 출력합니다.
    let salesAcc: String // 누적매출액을 출력합니다.
    
}



class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    // 셀에 네트워크 작업이후 디코드한 데이터를 찍기위해 생성
    var movieData : MovieData?
    
    // 날짜 자동 지정하기위해 var로 변환
    var movieURL = &quot;https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=4d5d8aaff18ee7542b218db4a2632b6b&amp;amp;targetDt=&quot;
    // http에 s붙여주기
    
    @IBOutlet weak var table: UITableView!
    
    
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        table.delegate = self
        table.dataSource = self
        movieURL += makeYesterdayString() // getData호출전 URL의 어제 날짜 문자 추가
        getData()
    }
    
    func makeYesterdayString() -&amp;gt; String {
            let y = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
            let dateF = DateFormatter()
            dateF.dateFormat = &quot;yyyyMMdd&quot;
            let day = dateF.string(from: y)
            return day
     }
    
//    func getYesterdayDate() -&amp;gt; String {
//        let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date()) // 어제 날짜 계산 -2면 그제 날짜
//        let dateFormatter = DateFormatter()
//        dateFormatter.dateFormat = &quot;yyyyMMdd&quot; // 날짜 형식 지정
//
//        if let yesterdayDate = yesterday {
//            let formattedDate = dateFormatter.string(from: yesterdayDate)
//            return formattedDate
//        } else {
//            return &quot;&quot; // 어제 날짜를 가져올 수 없는 경우 빈 문자열 반환
//        }
//    }
    
    
    func getData(){
        if let url = URL(string: movieURL){
            //1. URL(구조체) 객체 생성
            // 실패 가능 생성자라 옵셔널 타입으로 반환되기 때문에 한번 풀어서 사용
            
            let session = URLSession(configuration: .default) // 2. URLSession 객체 만들기
            //  3. URLSession 인스턴스에게 task주기
            let tesk = session.dataTask(with: url) { data, response, error in
                // 에러가 있는지 체크 nil이면 에러가 없다는 것 아닐때 에러가 있음
                if error != nil {
                    print(error!)
                    return
                }
                
                if let JSONdata = data{
                    //print(JSONdata, response!)
                    
                    // utf8 인코딩 방식으로 바꿔서 출력
                    let dataString = String(data: JSONdata, encoding: .utf8)
                    // print(dataString!)
                    
                    let docoder = JSONDecoder()
                    // throws 키워드 있는 메서드는 예외처리를 해줘야함
                    do {
                        let decodedData = try docoder.decode(MovieData.self, from: JSONdata)
                        
                        // 클로저안에서 클래스에 저장속성 사용시 self키워드 필요
                        self.movieData = decodedData
                        
                        // UI 처리 메인 스레드에서 처리
                        DispatchQueue.main.async {
                            self.table.reloadData()
                        }
                        
                        print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
                        print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].salesAcc)
                    }catch{
                        print(error)
                    }
                    
                }
            }
            tesk.resume() // task시작하기 task.resume()
        }
        
    }
    
    // 뷰 컨트롤러에게 segue가 수행될 예정임을 알림
    // segue 실행되기 직전에 자동으로호출되는 메서드
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let dest = segue.destination as! DetailViewController
        //segue.destination는 UIViewController 타입임 다운 캐스팅해서 사용하기
        let myIndexPath = table.indexPathForSelectedRow!
        let row = myIndexPath.row
        dest.movieName = (movieData?.boxOfficeResult.dailyBoxOfficeList[row].movieNm)!
        // segue.destination 는 UIViewController 형임
        // 보내면 viewDidLoad에서 이름을 바꾸기 때문에 뷰에 올라올때마다 이름이 바뀔수 있는 것임
   
        
    }
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
        return 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&amp;gt; UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: &quot;myCell&quot;, for: indexPath) as! MyTableViewCell
        
        //cell.movieName.text = name[indexPath.row]
        cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
        //cell.audiAccumulate.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc
        if let aCnt = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc {
                   let numF = NumberFormatter()
                   numF.numberStyle = .decimal
                   let aCount = Int(aCnt)!
                   let result = numF.string(for: aCount)!+&quot;명&quot;
                   cell.audiAccumulate.text = &quot;누적 관객수:\(result)&quot;
                   //cell.audiCount.text = &quot;어제:\(aCnt)명&quot;
               }
        //cell.audiCount.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt
        if let aCnt = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt {
                   let numF = NumberFormatter()
                   numF.numberStyle = .decimal
                   let aCount = Int(aCnt)!
                   let result = numF.string(for: aCount)!+&quot;명&quot;
                   cell.audiCount.text = &quot;어제 관객수:\(result)&quot;
                   //cell.audiCount.text = &quot;어제:\(aCnt)명&quot;
               }
        //cell.salesAcc.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].salesAcc
        if let aCnt = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].salesAcc {
                   let numF = NumberFormatter()
                   numF.numberStyle = .decimal
                   let aCount = Int(aCnt)!
                   let result = numF.string(for: aCount)!+&quot;원&quot;
                   cell.salesAcc.text = &quot;누적 매출액:\(result)&quot;
                   //cell.audiCount.text = &quot;어제:\(aCnt)명&quot;
               }
        
        cell.salesShare.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].salesShare
        
       
        return cell
    }
    
    // 테이블 헤더 주기 텍스트를 할때는 이 메서드를 사용하면 되지만 그래픽 같은것을 이용시 viewForHeaderInSection 메서드 이용
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -&amp;gt; String? {
        return &quot;박스오피스(영화진흥위원회제공:&quot;+makeYesterdayString()+&quot;)&quot;
    }
    
    func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -&amp;gt; String? {
        return &quot;Woogie&quot;
    }
    
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685587470497&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  DetailViewController.swift
//  MovieJJK
//
//  Created by comsoft on 2023/05/25.
//

import UIKit
import WebKit
class DetailViewController: UIViewController {
    
    @IBOutlet weak var nameLabel: UILabel!
    var movieName = &quot;&quot;
    @IBOutlet weak var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // nameLabel.text = movieName
        navigationItem.title = movieName
        
        /* 크룸 url
        
        guard let url = URL(string: &quot;https://search.naver.com/search.naver?where=nexearch&amp;amp;sm=top_hty&amp;amp;fbm=0&amp;amp;ie=utf8&amp;amp;query=%EB%B0%95%EC%8A%A4%EC%98%A4%ED%94%BC%EC%8A%A4&quot;) else {return}
        
         사파리 url search.naver.com/search.naver?sm=tab_hty.top&amp;amp;where=nexearch&amp;amp;query=박스오피스&amp;amp;oquery=영화순위&amp;amp;tqi=ibJS3sprvhGssFKpOfKssssstAw-448005
         url 사용시 주의점 url에 한글이 있어서 동작하지 않음 변환시켜주는 코드 필요
         */
        
        let urlKorString = &quot;https://www.youtube.com/results?search_query=&quot;+movieName
        let urlString = urlKorString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        guard let url = URL(string:urlString) else { return }
        let request = URLRequest(url: url)
        webView.load(request)

    }
    
    
    /*
     // MARK: - Navigation
     
     // In a storyboard-based application, you will often want to do a little preparation before navigation
     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
     // Get the new view controller using segue.destination.
     // Pass the selected object to the new view controller.
     }
     */
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685587484900&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  MapViewController.swift
//  MovieJJK
//
//  Created by comsoft on 2023/06/01.
//

import UIKit
import WebKit

class MapViewController: UIViewController {

    @IBOutlet weak var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let urlKorString = &quot;https://map.naver.com/v5/search/영화관&quot;
        let urlString = urlKorString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        guard let url = URL(string:urlString) else { return }
        let request = URLRequest(url: url)
        webView.load(request)
        

        // Do any additional setup after loading the view.
    }
    

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685587497841&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  CommunityViewController.swift
//  MovieJJK
//
//  Created by comsoft on 2023/06/01.
//

import UIKit
import WebKit

class CommunityViewController: UIViewController {

    @IBOutlet weak var webView: WKWebView!
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let urlKorString = &quot;https://gall.dcinside.com/board/lists/?id=movie2&quot;
        let urlString = urlKorString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        guard let url = URL(string:urlString) else { return }
        let request = URLRequest(url: url)
        webView.load(request)
        // Do any additional setup after loading the view.
    }
    

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}&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_1685587515212&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  MyTableViewCell.swift
//  MovieJJK
//
//  Created by comsoft on 2023/05/04.
//

import UIKit

class MyTableViewCell: UITableViewCell {

    
   
    @IBOutlet weak var movieName: UILabel!
    @IBOutlet weak var audiCount: UILabel!
    @IBOutlet weak var audiAccumulate: UILabel!
    @IBOutlet weak var salesShare: UILabel!
    @IBOutlet weak var salesAcc: UILabel!
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzOsYL/btsicMXRptb/eK6zdk6BlupfX9s1d4OudK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzOsYL/btsicMXRptb/eK6zdk6BlupfX9s1d4OudK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzOsYL/btsicMXRptb/eK6zdk6BlupfX9s1d4OudK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzOsYL%2FbtsicMXRptb%2FeK6zdk6BlupfX9s1d4OudK%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;1294&quot; height=&quot;697&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1685590339816&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Double {
    //계산 메서드 get생략으로 확장
    var squared : Double {
    return self * self
    }
}

let myValue: Double = 3.5
print(myValue.squared) //
print(3.5.squared) //Double형 값에도 .으로 바로 사용 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;627&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MR9Sk/btsiaZDLMuY/lQ3ckFr98wnxInvuh4nHk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MR9Sk/btsiaZDLMuY/lQ3ckFr98wnxInvuh4nHk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MR9Sk/btsiaZDLMuY/lQ3ckFr98wnxInvuh4nHk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMR9Sk%2FbtsiaZDLMuY%2FlQ3ckFr98wnxInvuh4nHk0%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;1166&quot; height=&quot;627&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;627&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1685590724979&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum Compass {
    case North
    case South
    case East
    case West
}

print(Compass.North) //North
var x : Compass //Compass형 인스턴스 x
x = Compass.West // West가 인스턴스임 
print(x, type(of:x)) // West Compass
x = .East
print(x) //East&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685590924249&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum Compass {
    case North
    case South
    case East
    case West
}
var direction : Compass
direction = .South
switch direction { //switch의 비교값이 열거형 Compass
    case .North: //direction이 .North이면 &quot;북&quot; 출력
        print(&quot;북&quot;)
    case .South:
        print(&quot;남&quot;)
    case .East:
        print(&quot;동&quot;)
    case .West:
        print(&quot;서&quot;) //모든 열거형 case를 포함하면 default 없어도 됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685590973667&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum Week: String {
    case Mon,Tue,Wed,Thur,Fri,Sat,Sun
    func printWeek() { //메서드도 가능
        switch self {
        case .Mon, .Tue, .Wed, .Thur, .Fri:
          print(&quot;주중&quot;)
        case .Sat, .Sun:
            print(&quot;주말&quot;)
        }
    }
}
Week.Sun.printWeek() //레포트
let x = Week.Mon // West가 인스턴스임 
x.printWeek()&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685591247629&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum Color: Int {
case red
case green = 2
case blue
}
print(Color.red) //red
print(Color.blue)
print(Color.red.rawValue) //0
print(Color.blue.rawValue) // 초기화 된 다음 값으로 자동 할당 
// red
// blue
// 0
// 3&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685591282647&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum Week: String {
    case Monday = &quot;월&quot;
    case Tuesday = &quot;화&quot;
    case Wednesday = &quot;수&quot;
    case Thursday = &quot;목&quot;
    case Friday = &quot;금&quot;
    case Saturday //값이 지정되지 않으면 case 이름이 할당됨
    case Sunday // = &quot;Sunday&quot;
}
print(Week.Monday) //Monday
print(Week.Monday.rawValue) //월
print(Week.Sunday)
print(Week.Sunday.rawValue)
// Monday
// 월
// Sunday
// Sunday&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685591423338&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//연관 값(associated value)을 갖는 Enum
enum Date {
    case intDate(Int,Int,Int) //(int,Int,Int)형 연관값을 갖는 intDate
    case stringDate(String) //String형 연관값을 값는 stringDate
}

var todayDate = Date.intDate(2023,4,30)
todayDate = Date.stringDate(&quot;2023년 5월 20일&quot;) //주석처리하면?
switch todayDate {
    case .intDate(let year, let month, let day):
        print(&quot;\(year)년 \(month)월 \(day)일&quot;)
    case .stringDate(let date):
        print(date)
}&lt;/code&gt;&lt;/pre&gt;</description>
      <author>개발자 aloe</author>
      <guid isPermaLink="true">https://aloe-study.tistory.com/223</guid>
      <comments>https://aloe-study.tistory.com/223#entry223comment</comments>
      <pubDate>Thu, 1 Jun 2023 11:46:35 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 액티비티 생명주기</title>
      <link>https://aloe-study.tistory.com/222</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJe5SD/btsh17ujLgF/uBAQHbfiNc9HLD99L32ACk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJe5SD/btsh17ujLgF/uBAQHbfiNc9HLD99L32ACk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJe5SD/btsh17ujLgF/uBAQHbfiNc9HLD99L32ACk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJe5SD%2Fbtsh17ujLgF%2FuBAQHbfiNc9HLD99L32ACk%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;1019&quot; height=&quot;556&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onPause() 잠시대기 상태 아직 메모리에 남아있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onStop() 정지상태 : 메모리에서 비워질때 우선 순위가 됨 아직 메모리에 남아있긴함&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IO05d/btsh169ZiEr/FvHyUSKkHbbHQo9bFYITx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IO05d/btsh169ZiEr/FvHyUSKkHbbHQo9bFYITx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IO05d/btsh169ZiEr/FvHyUSKkHbbHQo9bFYITx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIO05d%2Fbtsh169ZiEr%2FFvHyUSKkHbbHQo9bFYITx0%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;761&quot; height=&quot;342&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Destory() 되면 부를때 다시 onCreate() 부터 시작해야함&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;액티비티 객체 생성 단계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;479&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clAjiU/btsh2lswLPO/iOvuARcO44bB4RDvRjaQl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clAjiU/btsh2lswLPO/iOvuARcO44bB4RDvRjaQl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clAjiU/btsh2lswLPO/iOvuARcO44bB4RDvRjaQl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclAjiU%2Fbtsh2lswLPO%2FiOvuARcO44bB4RDvRjaQl1%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;844&quot; height=&quot;479&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;479&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SKdW8/btshYdBRKWI/DPcrUCjLkf6Bghxi2grHfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SKdW8/btshYdBRKWI/DPcrUCjLkf6Bghxi2grHfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SKdW8/btshYdBRKWI/DPcrUCjLkf6Bghxi2grHfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSKdW8%2FbtshYdBRKWI%2FDPcrUCjLkf6Bghxi2grHfk%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;868&quot; height=&quot;451&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;451&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 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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkRCTz/btshPpQyB13/Xrs9AvV3vZ98kAsP9Rtz2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkRCTz/btshPpQyB13/Xrs9AvV3vZ98kAsP9Rtz2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkRCTz/btshPpQyB13/Xrs9AvV3vZ98kAsP9Rtz2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkRCTz%2FbtshPpQyB13%2FXrs9AvV3vZ98kAsP9Rtz2K%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;958&quot; height=&quot;487&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;487&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;액티비티 생명주기, 실행되는 과정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;1045&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBlKeJ/btshWs0hcJz/MuyCeLpuxNF1aYS6oB1dWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBlKeJ/btshWs0hcJz/MuyCeLpuxNF1aYS6oB1dWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBlKeJ/btshWs0hcJz/MuyCeLpuxNF1aYS6oB1dWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBlKeJ%2FbtshWs0hcJz%2FMuyCeLpuxNF1aYS6oB1dWK%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;1067&quot; height=&quot;1045&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;1045&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 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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qtCZB/btshZuQ2qeB/drGs3TlrUmoKCHFMyZpYQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qtCZB/btshZuQ2qeB/drGs3TlrUmoKCHFMyZpYQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qtCZB/btshZuQ2qeB/drGs3TlrUmoKCHFMyZpYQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqtCZB%2FbtshZuQ2qeB%2FdrGs3TlrUmoKCHFMyZpYQk%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;938&quot; height=&quot;561&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;561&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 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;&lt;a href=&quot;https://velog.io/@woga1999/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%9E%AC%EC%83%9D%EC%84%B1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@woga1999/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%9E%AC%EC%83%9D%EC%84%B1&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685429073780&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;Android 런타임(ART)과 Dalvik 가상 머신은 페이징과 메모리 매핑(mmapping)을 사용하여 메모리를 관리합니다. 즉, 새로운 개체를 할당해서든 메모리 매핑된 페이지를 터치해서든 앱이 수정하는 모든 메&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@woga1999/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%9E%AC%EC%83%9D%EC%84%B1&quot; data-og-url=&quot;https://velog.io/@woga1999/안드로이드-메모리-재생성&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ozbhd/hySPhUiDyx/38OFSi9sLnwl4EVwHoBMv1/img.png?width=830&amp;amp;height=410&amp;amp;face=0_0_830_410,https://scrap.kakaocdn.net/dn/AJdwh/hySNmiEtSn/Su18qh6XU62Yblypu5DKQk/img.png?width=830&amp;amp;height=410&amp;amp;face=0_0_830_410,https://scrap.kakaocdn.net/dn/pv4R4/hySPeccsgy/S9EgJJvQBz5opafgl5UluK/img.png?width=830&amp;amp;height=410&amp;amp;face=0_0_830_410&quot;&gt;&lt;a href=&quot;https://velog.io/@woga1999/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%9E%AC%EC%83%9D%EC%84%B1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@woga1999/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%9E%AC%EC%83%9D%EC%84%B1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ozbhd/hySPhUiDyx/38OFSi9sLnwl4EVwHoBMv1/img.png?width=830&amp;amp;height=410&amp;amp;face=0_0_830_410,https://scrap.kakaocdn.net/dn/AJdwh/hySNmiEtSn/Su18qh6XU62Yblypu5DKQk/img.png?width=830&amp;amp;height=410&amp;amp;face=0_0_830_410,https://scrap.kakaocdn.net/dn/pv4R4/hySPeccsgy/S9EgJJvQBz5opafgl5UluK/img.png?width=830&amp;amp;height=410&amp;amp;face=0_0_830_410');&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;Android 런타임(ART)과 Dalvik 가상 머신은 페이징과 메모리 매핑(mmapping)을 사용하여 메모리를 관리합니다. 즉, 새로운 개체를 할당해서든 메모리 매핑된 페이지를 터치해서든 앱이 수정하는 모든 메&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;가비지 컬렉션 동작 방식&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;a href=&quot;https://mangkyu.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mangkyu.tistory.com/118&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685428698825&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;[Java] Garbage Collection(가비지 컬렉션)의 개념 및 동작 원리 (1/2)&quot; data-og-description=&quot;1. Garbage Collection(가비지 컬렉션)이란? [ Garbage Collection(가비지 컬렉션)이란? ] 프로그램을 개발 하다 보면 유효하지 않은 메모리인 가바지(Garbage)가 발생하게 된다. C언어를 이용하면 free()라는 함&quot; data-og-host=&quot;mangkyu.tistory.com&quot; data-og-source-url=&quot;https://mangkyu.tistory.com/118&quot; data-og-url=&quot;https://mangkyu.tistory.com/118&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pRuEE/hySPjdvBQe/jlsu0fh1uw4ivaX6Q6MomK/img.png?width=264&amp;amp;height=178&amp;amp;face=0_0_264_178,https://scrap.kakaocdn.net/dn/cZooPi/hySPkDt3Dm/Tkw3rDrcexu5liWlKfoyz1/img.png?width=264&amp;amp;height=178&amp;amp;face=0_0_264_178,https://scrap.kakaocdn.net/dn/tQr1o/hySPloRLbY/A4t0Ie7gqewKwbXsWKOwK0/img.png?width=865&amp;amp;height=290&amp;amp;face=0_0_865_290&quot;&gt;&lt;a href=&quot;https://mangkyu.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mangkyu.tistory.com/118&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pRuEE/hySPjdvBQe/jlsu0fh1uw4ivaX6Q6MomK/img.png?width=264&amp;amp;height=178&amp;amp;face=0_0_264_178,https://scrap.kakaocdn.net/dn/cZooPi/hySPkDt3Dm/Tkw3rDrcexu5liWlKfoyz1/img.png?width=264&amp;amp;height=178&amp;amp;face=0_0_264_178,https://scrap.kakaocdn.net/dn/tQr1o/hySPloRLbY/A4t0Ie7gqewKwbXsWKOwK0/img.png?width=865&amp;amp;height=290&amp;amp;face=0_0_865_290');&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;[Java] Garbage Collection(가비지 컬렉션)의 개념 및 동작 원리 (1/2)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. Garbage Collection(가비지 컬렉션)이란? [ Garbage Collection(가비지 컬렉션)이란? ] 프로그램을 개발 하다 보면 유효하지 않은 메모리인 가바지(Garbage)가 발생하게 된다. C언어를 이용하면 free()라는 함&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mangkyu.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;&amp;nbsp;&lt;/p&gt;</description>
      <category>APP</category>
      <author>개발자 aloe</author>
      <guid isPermaLink="true">https://aloe-study.tistory.com/222</guid>
      <comments>https://aloe-study.tistory.com/222#entry222comment</comments>
      <pubDate>Tue, 30 May 2023 15:57:52 +0900</pubDate>
    </item>
    <item>
      <title>UDP 서버 만들기 및 채팅 프로그램 만들기</title>
      <link>https://aloe-study.tistory.com/221</link>
      <description>&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/r6fqO/btshVAXnOb0/K1txazeHp6qKdgN0tWJd41/13.1_UDP%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D_%EC%BD%94%EB%93%9C%EA%B3%B5%EA%B0%9C3.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;13.1_UDP 프로그래밍_코드공개3.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.21MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bG5kat/btshAZx5Cve/BTReVsP1FQaDtEMfphFYgk/13.1_UDP%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;13.1_UDP 프로그래밍.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;1.28MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/dApAd7/btshTxsZXUM/mKoVkIiGVC0glqkaifX0w1/13.1_UDP%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D_%EC%BD%94%EB%93%9C%EA%B3%B5%EA%B0%9C2.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;13.1_UDP 프로그래밍_코드공개2.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.18MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bAomwL/btshYdOmMU3/I4zrj73vwKvBWWmR7uaVQ0/13.1_UDP%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D_%EC%BD%94%EB%93%9C%EA%B3%B5%EA%B0%9C1.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;13.1_UDP 프로그래밍_코드공개1.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.14MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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;TCP와 비교해 UDP는 간단함&lt;br /&gt;클 라이언트에서 쏘면 서버 쪽에서 받아주는 것만 처리하면 됨&lt;br /&gt;UDP는 목적지까지 가는 데 아니면 말고&lt;br /&gt;잘&amp;nbsp;전달됐는지&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;없으면&amp;nbsp;ACK&amp;nbsp;같은&amp;nbsp;게&amp;nbsp;없어서&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;APP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;|&amp;lt;= TCP OR UDP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;------------------------------------------&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;O/S&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H/W&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; IP&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;IP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LAN&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;LAN&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; LAN&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; &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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MAC&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MAC&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MAC&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w30TN/btshBlnn431/cYMoc55HxNZVxVlVQCoUCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w30TN/btshBlnn431/cYMoc55HxNZVxVlVQCoUCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w30TN/btshBlnn431/cYMoc55HxNZVxVlVQCoUCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw30TN%2FbtshBlnn431%2FcYMoc55HxNZVxVlVQCoUCK%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;349&quot; height=&quot;375&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sendto 함수는 누구에게 보낼지 일일히 설정 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 sendto함수를 통해 통로가 만들어지는 것임&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP와 다르게 UDP 서버는 하나의 소켓만 사용&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhJxsw/btshE9s4xsS/fEdA9do1a621S2bc2X7Ugk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhJxsw/btshE9s4xsS/fEdA9do1a621S2bc2X7Ugk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhJxsw/btshE9s4xsS/fEdA9do1a621S2bc2X7Ugk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhJxsw%2FbtshE9s4xsS%2FfEdA9do1a621S2bc2X7Ugk%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;622&quot; height=&quot;534&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;534&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;UDP 서버, 클라이언트 검증 가능&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baR6lZ/btshTvu5NI9/t3unvxhDpKtlSGVT9dq6v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baR6lZ/btshTvu5NI9/t3unvxhDpKtlSGVT9dq6v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baR6lZ/btshTvu5NI9/t3unvxhDpKtlSGVT9dq6v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaR6lZ%2FbtshTvu5NI9%2Ft3unvxhDpKtlSGVT9dq6v0%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;753&quot; height=&quot;602&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;602&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;로컬 어드레스 0 0 0 0 의미는 내컴퓨터의 IP가 여러개일때 일단 모두 모아서 이쪽으로 점유하겠다는 뜻&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FOREIGN 0 0 0 0누구던 나한테 접속가능&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgd9OC/btsh04wN7Ve/gvEwURNfjwlMLfGy5bVwJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgd9OC/btsh04wN7Ve/gvEwURNfjwlMLfGy5bVwJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgd9OC/btsh04wN7Ve/gvEwURNfjwlMLfGy5bVwJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdgd9OC%2Fbtsh04wN7Ve%2FgvEwURNfjwlMLfGy5bVwJ0%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;843&quot; height=&quot;295&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤 c 종료&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;&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;&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;UdpClient.c&lt;/p&gt;
&lt;pre id=&quot;code_1685411563193&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;unistd.h&amp;gt; 
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;strings.h&amp;gt;
#include &amp;lt;arpa/inet.h&amp;gt; 
#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;

int main(int argc, char *argv[])
{
    int nSockFd; // 소켓 파일 디스크립터
    char p_Buffer[BUFSIZ]; // 데이터 버퍼
    int nBufferLen; // 버퍼 길이

    struct sockaddr_in stSAddr; // 서버 주소 구조체
    int nSAddr_size; // 주소 구조체 크기

    if (argc != 3)
    {
        printf(&quot;Usage: %s &amp;lt;IP Address&amp;gt; &amp;lt;port&amp;gt;\n&quot;, argv[0]); 
        return -1;
    }

    // UDP 소켓 생성
    nSockFd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    // 서버 주소 설정
    bzero(&amp;amp;stSAddr, sizeof(stSAddr));
    stSAddr.sin_family = AF_INET;
    stSAddr.sin_addr.s_addr = inet_addr(argv[1]); // 명령줄 인수로 전달된 IP 주소 사용
    stSAddr.sin_port = htons(atoi(argv[2])); // 명령줄 인수로 전달된 포트 번호 사용
    nSAddr_size = sizeof(stSAddr);

    // 데이터 입력 및 송신
    bzero(p_Buffer, BUFSIZ);
    nBufferLen = read(0, p_Buffer, BUFSIZ); // 표준 입력으로부터 데이터를 읽음
    nBufferLen = sendto(nSockFd, p_Buffer, nBufferLen, 0, (struct sockaddr *)&amp;amp;stSAddr, nSAddr_size); // 소켓을 통해 데이터 전송

    if (nBufferLen &amp;gt; 0)
    {
        printf(&quot;TX: %s\n&quot;, p_Buffer); // 송신한 데이터 출력
    }

    // 데이터 수신
    bzero(p_Buffer, BUFSIZ);
    nBufferLen = recvfrom(nSockFd, p_Buffer, BUFSIZ, 0, (struct sockaddr *)&amp;amp;stSAddr, &amp;amp;nSAddr_size); // 소켓으로부터 데이터 수신

    if (nBufferLen &amp;gt; 0)
    {
        printf(&quot;Server Information: \n&quot;);
        printf(&quot;Addr: %s\n&quot;, inet_ntoa(stSAddr.sin_addr)); // 서버의 IP 주소 출력
        printf(&quot;Port: %d\n&quot;, ntohs(stSAddr.sin_port)); // 서버의 포트 번호 출력
        printf(&quot;RX: %s\n&quot;, p_Buffer); // 수신한 데이터 출력
    }

    close(nSockFd); // 소켓 닫기
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의&amp;nbsp;코드는&amp;nbsp;UDP&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;프로그램&amp;nbsp;실행&amp;nbsp;시&amp;nbsp;명령줄&amp;nbsp;인수로&amp;nbsp;서버의&amp;nbsp;IP&amp;nbsp;주소와&amp;nbsp;포트&amp;nbsp;번호를&amp;nbsp;입력해야&amp;nbsp;합니다. &lt;br /&gt;&lt;br /&gt;1.&amp;nbsp;UDP&amp;nbsp;소켓&amp;nbsp;생성 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`nSockFd&amp;nbsp;=&amp;nbsp;socket(AF_INET,&amp;nbsp;SOCK_DGRAM,&amp;nbsp;IPPROTO_UDP);` &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;AF_INET은&amp;nbsp;IPv4&amp;nbsp;주소&amp;nbsp;체계를&amp;nbsp;사용함을&amp;nbsp;나타내며,&amp;nbsp;SOCK_DGRAM은&amp;nbsp;데이터그램&amp;nbsp;소켓을&amp;nbsp;생성하라는&amp;nbsp;의미입니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;2.&amp;nbsp;서버&amp;nbsp;주소&amp;nbsp;설정 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`bzero(&amp;amp;stSAddr,&amp;nbsp;sizeof(stSAddr));` &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;서버&amp;nbsp;주소&amp;nbsp;구조체(stSAddr)를&amp;nbsp;초기화합니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`stSAddr.sin_family&amp;nbsp;=&amp;nbsp;AF_INET;`&amp;nbsp;:&amp;nbsp;주소&amp;nbsp;체계를&amp;nbsp;IPv4로&amp;nbsp;설정합니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`stSAddr.sin_addr.s_addr&amp;nbsp;=&amp;nbsp;inet_addr(argv[1]);`&amp;nbsp;:&amp;nbsp;명령줄&amp;nbsp;인수로&amp;nbsp;전달된&amp;nbsp;IP&amp;nbsp;주소를&amp;nbsp;설정합니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`stSAddr.sin_port&amp;nbsp;=&amp;nbsp;htons(atoi(argv[2]));`&amp;nbsp;:&amp;nbsp;명령줄&amp;nbsp;인수로&amp;nbsp;전달된&amp;nbsp;포트&amp;nbsp;번호를&amp;nbsp;설정합니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`nSAddr_size&amp;nbsp;=&amp;nbsp;sizeof(stSAddr);`&amp;nbsp;:&amp;nbsp;주소&amp;nbsp;구조체의&amp;nbsp;크기를&amp;nbsp;저장합니다. &lt;br /&gt;&lt;br /&gt;3.&amp;nbsp;데이터&amp;nbsp;입력&amp;nbsp;및&amp;nbsp;송신 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`nBufferLen&amp;nbsp;=&amp;nbsp;read(0,&amp;nbsp;p_Buffer,&amp;nbsp;BUFSIZ);`&amp;nbsp;:&amp;nbsp;표준&amp;nbsp;입력으로부터&amp;nbsp;데이터를&amp;nbsp;읽어옵니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`nBufferLen&amp;nbsp;=&amp;nbsp;sendto(nSockFd,&amp;nbsp;p_Buffer,&amp;nbsp;nBufferLen,&amp;nbsp;0,&amp;nbsp;(struct&amp;nbsp;sockaddr&amp;nbsp;*)&amp;amp;stSAddr,&amp;nbsp;nSAddr_size);`&amp;nbsp;:&amp;nbsp;소켓을&amp;nbsp;통해&amp;nbsp;서버로&amp;nbsp;데이터를&amp;nbsp;전송합니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;전송한&amp;nbsp;데이터를&amp;nbsp;출력합니다. &lt;br /&gt;&lt;br /&gt;4.&amp;nbsp;데이터&amp;nbsp;수신 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`nBufferLen&amp;nbsp;=&amp;nbsp;recvfrom(nSockFd,&amp;nbsp;p_Buffer,&amp;nbsp;BUFSIZ,&amp;nbsp;0,&amp;nbsp;(struct&amp;nbsp;sockaddr&amp;nbsp;*)&amp;amp;stSAddr,&amp;nbsp;&amp;amp;nSAddr_size);`&amp;nbsp;:&amp;nbsp;소켓으로부터&amp;nbsp;서버로부터&amp;nbsp;데이터를&amp;nbsp;수신합니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;수신한&amp;nbsp;데이터와&amp;nbsp;서버의&amp;nbsp;IP&amp;nbsp;주소,&amp;nbsp;포트&amp;nbsp;번호를&amp;nbsp;출력합니다. &lt;br /&gt;&lt;br /&gt;5.&amp;nbsp;소켓&amp;nbsp;닫기 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`close(nSockFd);`&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;단순한&amp;nbsp;UDP&amp;nbsp;통신을&amp;nbsp;수행하는&amp;nbsp;것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5225Y/btshBjQsY6q/DOMgBYREOs7c4PL9LaZjXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5225Y/btshBjQsY6q/DOMgBYREOs7c4PL9LaZjXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5225Y/btshBjQsY6q/DOMgBYREOs7c4PL9LaZjXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5225Y%2FbtshBjQsY6q%2FDOMgBYREOs7c4PL9LaZjXK%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;808&quot; height=&quot;260&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;260&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;nc명령으로 서버를 열고 테스트 해보기&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rly7s/btshYdgvd4i/aAVvs7jLUUtyNFJb3DjlGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rly7s/btshYdgvd4i/aAVvs7jLUUtyNFJb3DjlGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rly7s/btshYdgvd4i/aAVvs7jLUUtyNFJb3DjlGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRly7s%2FbtshYdgvd4i%2FaAVvs7jLUUtyNFJb3DjlGk%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;822&quot; height=&quot;198&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;198&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;305&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCxIEW/btshVBPuse3/FbYdoNrpdu1r67wMUUNFZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCxIEW/btshVBPuse3/FbYdoNrpdu1r67wMUUNFZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCxIEW/btshVBPuse3/FbYdoNrpdu1r67wMUUNFZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCxIEW%2FbtshVBPuse3%2FFbYdoNrpdu1r67wMUUNFZ0%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;305&quot; height=&quot;282&quot; data-origin-width=&quot;305&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edp2ey/btshBmzRrIG/oyUoEM5xP8HYtBiPntCTs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edp2ey/btshBmzRrIG/oyUoEM5xP8HYtBiPntCTs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edp2ey/btshBmzRrIG/oyUoEM5xP8HYtBiPntCTs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fedp2ey%2FbtshBmzRrIG%2FoyUoEM5xP8HYtBiPntCTs1%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;298&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;udp는 사진처럼 연결구조가 되어야 통신가능&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;strncasecmp : 앞에서 부터 4개 같으면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;strcasecmp : 이전시간과 조금 다른데 확인 해보기&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;&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;UdpServer.c&lt;/p&gt;
&lt;pre id=&quot;code_1685413436918&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;


int main(int argc, char *argv[])
{
    int nSocketFd; // 소켓 파일 디스크립터
    char pBuffer[BUFSIZ]; // 데이터 버퍼
    int nBufferLen = 0; // 버퍼 길이
    struct sockaddr_in stSAddr; // 서버 소켓 주소 구조체
    struct sockaddr_in stCAddr; // 클라이언트 소켓 주소 구조체
    int nCAddr_size; // 클라이언트 소켓 주소 구조체 크기

    if (argc != 2)
    {
        printf(&quot;Usage: %s &amp;lt;port&amp;gt;\n&quot;, argv[0]);
        return -1;
    }

    // UDP 소켓 생성
    nSocketFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    // 서버 소켓 주소 설정
    memset(&amp;amp;stSAddr, 0, sizeof(stSAddr));
    stSAddr.sin_family = PF_INET;
    stSAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    stSAddr.sin_port = htons(atoi(argv[1]));

    // 소켓에 주소 바인딩
    if (bind(nSocketFd, (struct sockaddr *)&amp;amp;stSAddr, sizeof(stSAddr)) &amp;lt; 0)
    {
        printf(&quot;Binding Failed.\n&quot;);
        return -1;
    }

    do
    {
        // 클라이언트 소켓 주소 구조체 초기화
        nCAddr_size = sizeof(stCAddr);
        memset(pBuffer, 0, BUFSIZ);

        // 클라이언트로부터 데이터 수신
        nBufferLen = recvfrom(nSocketFd, pBuffer, BUFSIZ, 0, (struct sockaddr *)&amp;amp;stCAddr, &amp;amp;nCAddr_size);

        if (nBufferLen &amp;gt; 0)
        {
            printf(&quot;Client Information: \n&quot;);
            printf(&quot;Addr: %s\n&quot;, inet_ntoa(stCAddr.sin_addr)); // 클라이언트의 IP 주소 출력
            printf(&quot;Port: %d\n&quot;, ntohs(stCAddr.sin_port)); // 클라이언트의 포트 번호 출력
            printf(&quot;RX: %s\n&quot;, pBuffer); // 수신한 데이터 출력

            if (strncasecmp(pBuffer, &quot;exit&quot;, 4) == 0)
                break;
        }
        else if (nBufferLen &amp;lt;= 0)
            break;

        // 클라이언트로 데이터 전송
        nBufferLen = sendto(nSocketFd, pBuffer, nBufferLen, 0, (struct sockaddr *)&amp;amp;stCAddr, nCAddr_size);
    } while (1);

    close(nSocketFd); // 소켓 닫기
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: start;&quot;&gt;위의 코드는 UDP 프로토콜을 사용하여 클라이언트로부터 데이터를 수신하고, 수신한 데이터를 다시 클라이언트로 전송하는 간단한 서버 프로그램입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWE1Bd/btshBkoolmf/JQ8qkqbTUKqPl0dPuSY1kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWE1Bd/btshBkoolmf/JQ8qkqbTUKqPl0dPuSY1kK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWE1Bd/btshBkoolmf/JQ8qkqbTUKqPl0dPuSY1kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWE1Bd%2FbtshBkoolmf%2FJQ8qkqbTUKqPl0dPuSY1kK%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;886&quot; height=&quot;523&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3z0p5/btshAZY6Dh7/lSeKq2rM7bOfj1DocPfsqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3z0p5/btshAZY6Dh7/lSeKq2rM7bOfj1DocPfsqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3z0p5/btshAZY6Dh7/lSeKq2rM7bOfj1DocPfsqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3z0p5%2FbtshAZY6Dh7%2FlSeKq2rM7bOfj1DocPfsqk%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;358&quot; height=&quot;99&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;클라이언트 쪽에서 exit가 꼭 필요함 컨트롤 + c 해서 종료하면 포트를 물고있어서 자원을 놔주지 못하는 경우가 생길 수 있음 다음번에 실행했을때 정상적인 코드 실행이 안될 수 있음&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;UdpClient2.c&lt;/p&gt;
&lt;pre id=&quot;code_1685414606123&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;strings.h&amp;gt;
#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;sys/poll.h&amp;gt;


int main(int argc, char *argv[])
{
    int nSockFd; // 소켓 파일 디스크립터
    struct pollfd rfds[2]; // 파일 디스크립터 감시를 위한 pollfd 구조체 배열
    int nRetval; // poll() 함수의 반환값

    char p_Buffer[BUFSIZ]; // 데이터 버퍼
    int nBufferLen; // 버퍼 길이

    struct sockaddr_in stSAddr; // 서버 주소 구조체
    int nSAddr_size; // 주소 구조체 크기

    if (argc != 3)
    {
        printf(&quot;Usage: %s &amp;lt;IP Address&amp;gt; &amp;lt;port&amp;gt;\n&quot;, argv[0]);
        return -1;
    }

    // UDP 소켓 생성
    nSockFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    // 키보드 입력을 감시하는 파일 디스크립터 설정
    rfds[0].fd = 0; // 표준 입력(키보드)의 파일 디스크립터
    rfds[0].events = POLLIN; // 입력 가능한 데이터의 상태 변화를 감시

    // 소켓 입력을 감시하는 파일 디스크립터 설정
    rfds[1].fd = nSockFd; // 소켓의 파일 디스크립터
    rfds[1].events = POLLIN; // 입력 가능한 데이터의 상태 변화를 감시

    // 서버 주소 설정
    bzero(&amp;amp;stSAddr, sizeof(stSAddr));
    stSAddr.sin_family = AF_INET;
    stSAddr.sin_addr.s_addr = inet_addr(argv[1]); // 명령줄 인수로 전달된 IP 주소 사용
    stSAddr.sin_port = htons(atoi(argv[2])); // 명령줄 인수로 전달된 포트 번호 사용
    nSAddr_size = sizeof(stSAddr);

    do
    {
        nRetval = poll(rfds, 2, 1000); // 파일 디스크립터 감시
        if (nRetval &amp;lt; 0)
            break;
        if (nRetval == 0)
            continue;

        // 키보드 입력 처리
        if (rfds[0].revents &amp;amp; POLLIN)
        {
            bzero(p_Buffer, BUFSIZ);
            nBufferLen = read(0, p_Buffer, BUFSIZ); // 키보드로부터 데이터를 읽음
            nBufferLen = sendto(nSockFd, p_Buffer, nBufferLen, 0, (struct sockaddr *)&amp;amp;stSAddr, nSAddr_size); // 소켓을 통해 데이터 전송

            if (nBufferLen &amp;gt; 0)
            {
                printf(&quot;TX: %s&quot;, p_Buffer); // 송신한 데이터 출력
                if (strncasecmp(p_Buffer, &quot;exit&quot;, 4) == 0)
                    break;
            }
       

              // 소켓 수신 처리
        if (rfds[1].revents &amp;amp; POLLIN)
        {
            bzero(p_Buffer, BUFSIZ);
            nBufferLen = recvfrom(nSockFd, p_Buffer, BUFSIZ, 0, (struct sockaddr *)&amp;amp;stSAddr, &amp;amp;nSAddr_size); // 소켓으로부터 데이터 수신
            if (nBufferLen &amp;gt; 0)
            {
                printf(&quot;RX: %s&quot;, p_Buffer); // 수신한 데이터 출력
                if (strncasecmp(p_Buffer, &quot;exit&quot;, 4) == 0)
                    break;
            }
        }

    } while (1);

    close(nSockFd); // 소켓 닫기
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 UDP 프로토콜을 사용하여 클라이언트로부터 키보드 입력을 받아 서버로 전송하고, 동시에 서버로부터 데이터를 수신하여 출력하는 간단한 클라이언트 프로그램입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #f7f7f8; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로그램 실행 시 명령줄 인수로 서버의 IP 주소와 포트 번호를 전달해야 합니다. 프로그램은 키보드 입력과 소켓 입력을 모두 감시하고, 데이터를 송수신하며 &quot;exit&quot;라는 입력이 들어오면 종료됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdr7bn/btshFaS5ZJD/C7tuPBvug5X5sukJsTRbe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdr7bn/btshFaS5ZJD/C7tuPBvug5X5sukJsTRbe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdr7bn/btshFaS5ZJD/C7tuPBvug5X5sukJsTRbe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbdr7bn%2FbtshFaS5ZJD%2FC7tuPBvug5X5sukJsTRbe0%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;912&quot; height=&quot;351&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;351&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 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;&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;UdpServer2.c&lt;/p&gt;
&lt;pre id=&quot;code_1685415395234&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;sys/poll.h&amp;gt;


int main(int argc, char *argv[])
{
    int nSocketFd;                   // 소켓 파일 디스크립터
    char pBuffer[BUFSIZ];             // 버퍼
    int nBufferLen = 0;               // 버퍼 길이
    struct sockaddr_in stSAddr;       // 서버 소켓 주소 구조체
    struct sockaddr_in stCAddr;       // 클라이언트 소켓 주소 구조체
    int nCAddr_size;                  // 클라이언트 소켓 주소 구조체의 길이
    
    int nRetval;
    struct pollfd rfds[2];            // 표준 입력(키보드 입력), 소켓 입력을 감시하는 pollfd 구조체 배열


    if (argc != 2)
    {
        printf(&quot;Usage: %s &amp;lt;port&amp;gt;\n&quot;, argv[0]);
        return -1;
    }
    
    // UDP 소켓 생성
    nSocketFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    
    // 키보드 입력 설정
    rfds[0].fd = 0;                   // 표준 입력(키보드 입력)의 파일 디스크립터 0
    rfds[0].events = POLLIN;          // 입력 가능 이벤트를 감시
    rfds[0].revents = 0;              // 이벤트 상태 초기화
    
    // 소켓 입력 설정
    rfds[1].fd = nSocketFd;           // 소켓 파일 디스크립터
    rfds[1].events = POLLIN;          // 입력 가능 이벤트를 감시
    rfds[1].revents = 0;              // 이벤트 상태 초기화
    
    // 서버 소켓 주소 초기화
    memset(&amp;amp;stSAddr, 0, sizeof(stSAddr));
    stSAddr.sin_family = PF_INET;                          // IPv4 주소 패밀리
    stSAddr.sin_addr.s_addr = htonl(INADDR_ANY);           // 모든 IP 주소로부터의 입력을 허용
    stSAddr.sin_port = htons(atoi(argv[1]));               // 명령줄 인수로 전달된 포트 번호 설정

    // 소켓과 주소를 바인딩
    if (bind(nSocketFd, (struct sockaddr *)&amp;amp;stSAddr, sizeof(stSAddr)) &amp;lt; 0)
    {
        printf(&quot;Binding Failed.\n&quot;); 
        return -1;
    }
    
    do
    {
        // poll을 이용하여 입력을 감시
        nRetval = poll(rfds, 2, 1000);       // 1000ms(1초) 동안 입력을 감시
        if (nRetval &amp;lt; 0)
            break;
        if (nRetval == 0)
            continue;
        
        // 키보드 입력 처리
        if (rfds[0].revents &amp;amp; POLLIN)          // 표준 입력(키보드 입력)에서 입력 가능 이벤트가 발생한 경우
        {
            memset(pBuffer, 0, BUFSIZ);       // 버퍼 초기화
            nBufferLen = read(0, pBuffer, BUFSIZ);    // 표준 입력(키보드 입력)에서 데이터를 읽어옴
            nBufferLen = sendto(nSocketFd, pBuffer, nBufferLen, 0,
                    (struct sockaddr *)&amp;amp;stCAddr, nCAddr_size);    // 소켓을 통해 읽어온 데이터를 전송

            if (nBufferLen &amp;gt; 0)
            {
                printf(&quot;TX: %s&quot;, pBuffer);      // 전송한 데이터 출력
                if (strncasecmp(pBuffer, &quot;exit&quot;, 4) == 0)     // 전송한 데이터가 &quot;exit&quot;일 경우 종료
                    break;
            }
        }
        
        // 소켓 수신 처리
        if (rfds[1].revents &amp;amp; POLLIN)          // 소켓에서 입력 가능 이벤트가 발생한 경우
        {
            memset(pBuffer, 0, BUFSIZ);       // 버퍼 초기화
            nBufferLen = recvfrom(nSocketFd, pBuffer, BUFSIZ, 0, 
            (struct sockaddr *)&amp;amp;stCAddr, &amp;amp;nCAddr_size);    // 소켓을 통해 데이터를 수신

            if (nBufferLen &amp;gt; 0)
            {
                printf(&quot;RX: %s&quot;, pBuffer);      // 수신한 데이터 출력
                if (strncasecmp(pBuffer, &quot;exit&quot;, 4) == 0)     // 수신한 데이터가 &quot;exit&quot;일 경우 종료
                    break;
            }
        }
    } while(1);
    
    close(nSocketFd);         // 소켓 종료
    return 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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BW6LA/btshZvO4je0/oassPG0xdMauu5MrKdPVi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BW6LA/btshZvO4je0/oassPG0xdMauu5MrKdPVi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BW6LA/btshZvO4je0/oassPG0xdMauu5MrKdPVi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBW6LA%2FbtshZvO4je0%2FoassPG0xdMauu5MrKdPVi1%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;871&quot; height=&quot;156&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;156&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;코드는&amp;nbsp;UDP&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;1.&amp;nbsp;소켓&amp;nbsp;생성&amp;nbsp;및&amp;nbsp;초기화:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`socket()`&amp;nbsp;함수를&amp;nbsp;사용하여&amp;nbsp;UDP&amp;nbsp;소켓을&amp;nbsp;생성합니다.&lt;br /&gt;&lt;br /&gt;2.&amp;nbsp;주소&amp;nbsp;설정&amp;nbsp;및&amp;nbsp;바인딩:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`stSAddr`&amp;nbsp;구조체를&amp;nbsp;초기화하고,&amp;nbsp;서버의&amp;nbsp;IP&amp;nbsp;주소와&amp;nbsp;포트&amp;nbsp;번호를&amp;nbsp;설정합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`bind()`&amp;nbsp;함수를&amp;nbsp;사용하여&amp;nbsp;소켓을&amp;nbsp;해당&amp;nbsp;주소에&amp;nbsp;바인딩합니다.&lt;br /&gt;&lt;br /&gt;3.&amp;nbsp;`poll()`&amp;nbsp;함수를&amp;nbsp;사용한&amp;nbsp;입력&amp;nbsp;감시:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`poll()`&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;-&amp;nbsp;`poll()`&amp;nbsp;함수의&amp;nbsp;타임아웃&amp;nbsp;값으로&amp;nbsp;1000ms(1초)을&amp;nbsp;설정합니다.&lt;br /&gt;&lt;br /&gt;4.&amp;nbsp;키보드&amp;nbsp;입력&amp;nbsp;처리:&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;읽어옵니다.&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;&amp;nbsp;-&amp;nbsp;전송한&amp;nbsp;데이터를&amp;nbsp;출력하고,&amp;nbsp;만약&amp;nbsp;데이터가&amp;nbsp;&quot;exit&quot;일&amp;nbsp;경우&amp;nbsp;프로그램을&amp;nbsp;종료합니다.&lt;br /&gt;&lt;br /&gt;5.&amp;nbsp;소켓&amp;nbsp;수신&amp;nbsp;처리:&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;수신합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;수신한&amp;nbsp;데이터를&amp;nbsp;출력하고,&amp;nbsp;만약&amp;nbsp;데이터가&amp;nbsp;&quot;exit&quot;일&amp;nbsp;경우&amp;nbsp;프로그램을&amp;nbsp;종료합니다.&lt;br /&gt;&lt;br /&gt;6.&amp;nbsp;반복&amp;nbsp;실행:&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;&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;7.&amp;nbsp;소켓&amp;nbsp;종료:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`close()`&amp;nbsp;함수를&amp;nbsp;사용하여&amp;nbsp;소켓을&amp;nbsp;닫고&amp;nbsp;자원을&amp;nbsp;해제합니다.&lt;br /&gt;&lt;br /&gt;따라서,&amp;nbsp;이&amp;nbsp;코드는&amp;nbsp;UDP&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;&quot;exit&quot;일&amp;nbsp;경우&amp;nbsp;프로그램이&amp;nbsp;종료됩니다.&lt;/p&gt;</description>
      <category>자료구조, 운영체제, 네트워크, 시스템설계/정보보안</category>
      <author>개발자 aloe</author>
      <guid isPermaLink="true">https://aloe-study.tistory.com/221</guid>
      <comments>https://aloe-study.tistory.com/221#entry221comment</comments>
      <pubDate>Tue, 30 May 2023 11:46:00 +0900</pubDate>
    </item>
  </channel>
</rss>