<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>nemo._.log</title>
        <link>https://velog.io/</link>
        <description>맨날 최선을 다하지는 마러라. 피곤해서 못산다.</description>
        <lastBuildDate>Fri, 03 Jan 2025 02:19:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>nemo._.log</title>
            <url>https://velog.velcdn.com/images/nemo_/profile/ca913b6b-c13a-4582-af5f-151dd7577164/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. nemo._.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/nemo_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[날씨 앱 만들기]]></title>
            <link>https://velog.io/@nemo_/%EB%82%A0%EC%94%A8-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@nemo_/%EB%82%A0%EC%94%A8-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Fri, 03 Jan 2025 02:19:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="날씨-앱-만들기">날씨 앱 만들기</h3>
</blockquote>
<hr>
<h4 id="urlsession-을-이용해서-서버에서-날씨-데이터를-가져와-띄우는-간단한-날씨-앱을-개발해봅니다">URLSession 을 이용해서 서버에서 날씨 데이터를 가져와 띄우는 간단한 날씨 앱을 개발해봅니다.</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/659d229a-5db0-4fdb-962e-b505f147f4d1/image.png" alt="">
<strong>아래 데이터들을 화면에 노출합니다.</strong></p>
<ul>
<li>현재 기온</li>
<li>최소 기온</li>
<li>최고 기온</li>
<li>날씨 이미지</li>
<li><code>UITableView</code> 를 사용한 5일 간 예보</li>
</ul>
<p>간단한 날씨 앱을 개발하기 위한 공부들을 해봅시다.</p>
<hr>
<blockquote>
<h3 id="open-weather-api">Open Weather API</h3>
</blockquote>
<hr>
<h4 id="open-weather-api-1">Open Weather API</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/87964e1b-d292-404a-a6b7-2b0f9bb35e8b/image.png" alt=""></p>
<ul>
<li>Open 소스 API 란 모두가 사용할 수 있게 공공적으로 열어놓은 API 를 말합니다.</li>
<li>Open 소스 API 중 날씨 데이터를 제공하는 Open Weather API 를 사용합니다.
<a href="https://openweathermap.org/api">링크텍스트</a></li>
</ul>
<hr>
<h4 id="1-회원-가입">(1) 회원 가입</h4>
<hr>
<h4 id="open-weather-api-를-사용-하기-위해-먼저-회원가입을-해야합니다">Open Weather API 를 사용 하기 위해, 먼저 회원가입을 해야합니다.</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/b673bf85-5a67-431f-8f18-194c28c2e891/image.png" alt=""></p>
<ul>
<li>API 사용 목적을 설문 상 물어볼텐데 적당히 값을 입력하고 회원가입 완료.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/nemo_/post/1ba3f6fb-2608-4628-82d4-6c1168afb130/image.png" alt=""></p>
<ul>
<li>회원가입이 완료되면 My API Keys 에서 API Key 를 확인 가능.</li>
<li>API Key 란 API 명세에 넣어야하는 고유한 값.</li>
</ul>
<hr>
<h4 id="2-api-파악">(2) API 파악</h4>
<hr>
<h4 id="guide-로-가서-api-가이드-읽어보기">Guide 로 가서 API 가이드 읽어보기</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/e8423886-748a-45b4-bb7e-3f5743d9c317/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/nemo_/post/dae619ea-2c5d-4eac-af9d-730a59d12117/image.png" alt=""></p>
<ul>
<li><strong>현재 날씨 API</strong><ul>
<li><a href="https://openweathermap.org/current">https://openweathermap.org/current</a></li>
</ul>
</li>
<li>Current Weather Data 항목을 보면, 현재 날씨를 가져오는 API 명세가 가이드 되어있음.</li>
</ul>
<blockquote>
<p><a href="https://api.openweathermap.org/data/2.5/weather?lat=%7Blat%7D&amp;lon=%7Blon%7D&amp;appid=">https://api.openweathermap.org/data/2.5/weather?lat={lat}&amp;lon={lon}&amp;appid=</a>{API key}&amp;units=metric</p>
</blockquote>
<ul>
<li><p><code>?</code> 뒤의 key-value 는 쿼리 파라미터.</p>
</li>
<li><p><strong>5일간 날씨 예보 API</strong></p>
<ul>
<li><a href="https://openweathermap.org/forecast5">https://openweathermap.org/forecast5</a></li>
<li>5일간 3시간 간격으로 날씨 예보 데이터 API.</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<h3 id="urlsession-으로-날씨-앱-만들기">URLSession 으로 날씨 앱 만들기</h3>
</blockquote>
<hr>
<h4 id="💻-urlsession-을-사용해서-날씨-데이터를-불러와-띄워봅시다">💻 URLSession 을 사용해서 날씨 데이터를 불러와 띄워봅시다.</h4>
<ul>
<li><a href="https://openweathermap.org/current">https://openweathermap.org/current</a></li>
</ul>
<ol>
<li>API 명세를 파악하고, JSON 데이터를 가져오기 위한 Codable 구조체를 선언합니다.</li>
<li><code>URLSession</code> 의 URL 에 사용할 API 의 URL 주소를 입력합니다.</li>
<li><code>URLSession.dataTask</code> 를 통해 네트워크 통신을 수행하고, 성공적으로 데이터를 받아왔다면 데이터를 뷰(<code>UILabel</code>, <code>UITableView</code>)에 적용합니다.</li>
</ol>
<hr>
<h4 id="--currentweatherresultswift">- CurrentWeatherResult.swift</h4>
<pre><code class="language-swift">import Foundation

struct CurrentWeatherResult: Codable {
    let weather: [Weather]
    let main: WeatherMain
}

struct Weather: Codable {
    let id: Int
    let main: String
    let description: String
    let icon: String
}

struct WeatherMain: Codable {
    let temp: Double
    let temp_min: Double
    let temp_max: Double
    let humidity: Int
}</code></pre>
<h4 id="--viewcontrollerswift">- ViewController.swift</h4>
<pre><code class="language-swift">import UIKit
import SnapKit

class ViewController: UIViewController {

    // 테이블 뷰에 넣을 데이터 소스.
    private var dataSource = [ForecastWeather]()

    // URL 쿼리에 넣을 아이템들
    // 서울역 위경도
    private let urlQueryItems: [URLQueryItem] = [
        URLQueryItem(name: &quot;lat&quot;, value: &quot;37.5&quot;),
        URLQueryItem(name: &quot;lon&quot;, value: &quot;126.9&quot;),
        URLQueryItem(name: &quot;appid&quot;, value: &quot;3d1be1b2d3419223212333eb2388ba4a&quot;),
        URLQueryItem(name: &quot;units&quot;, value: &quot;metric&quot;)
    ]

    private let titleLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;서울특별시&quot;
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 30)
        return label
    }()
    private let tempLabel: UILabel = {
        let label = UILabel()
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 50)
        return label
    }()
    private let tempMinLabel: UILabel = {
        let label = UILabel()
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 20)
        return label
    }()
    private let tempMaxLabel: UILabel = {
        let label = UILabel()
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 20)
        return label
    }()
    private let tempStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 20
        stackView.distribution = .fillEqually
        return stackView
    }()
    private let imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        imageView.backgroundColor = .black
        return imageView
    }()
    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.backgroundColor = .black
        // delegate: &quot;대리자. 대신 수행해주는 사람.&quot; tableView 의 여러가지 속성 세팅을 이 ViewController 에서 대신 세팅해주겠다.
        tableView.delegate = self
        // dataSource: 테이블 뷰에 넣을 데이터를 이 ViewController 에서 세팅해주겠다.
        tableView.dataSource = self
        // 테이블 뷰에 테이블 뷰 셀 등록.
        tableView.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.id)
        return tableView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        fetchCurrentWeatherData()
        fetchForecastData()
        configureUI()
    }

    // 서버 데이터를 불러오는 메서드
    private func fetchData&lt;T: Decodable&gt;(url: URL, completion: @escaping (T?) -&gt; Void) {
        let session = URLSession(configuration: .default)
        session.dataTask(with: URLRequest(url: url)) { data, response, error in
            guard let data = data, error == nil else {
                print(&quot;데이터 로드 실패&quot;)
                completion(nil)
                return
            }
            // http status code 성공 범위.
            let successRange = 200..&lt;300
            if let response = response as? HTTPURLResponse, successRange.contains(response.statusCode) {
                guard let decodedData = try? JSONDecoder().decode(T.self, from: data) else {
                    print(&quot;JSON 디코딩 실패&quot;)
                    completion(nil)
                    return
                }
                completion(decodedData)
            } else {
                print(&quot;응답 오류&quot;)
                completion(nil)
            }
        }.resume()
    }

    // 서버에서 현재 날씨 데이터를 불러오는 메서드
    private func fetchCurrentWeatherData() {
        var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/weather&quot;)
        urlComponents?.queryItems = self.urlQueryItems

        guard let url = urlComponents?.url else {
            print(&quot;잘못된 URL&quot;)
            return
        }

        fetchData(url: url) { [weak self] (result: CurrentWeatherResult?) in
            guard let self, let result else { return }
            // UI 작업은 메인 쓰레드에서 작업
            DispatchQueue.main.async {
                self.tempLabel.text = &quot;\(Int(result.main.temp))°C&quot;
                self.tempMinLabel.text = &quot;최소: \(Int(result.main.temp_min))°C&quot;
                self.tempMaxLabel.text = &quot;최고: \(Int(result.main.temp_max))°C&quot;
            }
            guard let imageUrl = URL(string: &quot;https://openweathermap.org/img/wn/\(result.weather[0].icon)@2x.png&quot;) else { return }

            // image 를 로드하는 작업은 백그라운드 쓰레드 작업
            if let data = try? Data(contentsOf: imageUrl) {
                if let image = UIImage(data: data) {
                    // 이미지뷰에 이미지를 그리는 작업은 UI 작업이기 때문에 다시 메인 쓰레드에서 작업.
                    DispatchQueue.main.async {
                        self.imageView.image = image
                    }
                }
            }
        }
    }

    // 서버에서 5일 간 날씨 예보 데이터를 불러오는 메서드
    private func fetchForecastData() {
        var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/forecast&quot;)
        urlComponents?.queryItems = self.urlQueryItems

        guard let url = urlComponents?.url else {
            print(&quot;잘못된 URL&quot;)
            return
        }

        fetchData(url: url) { [weak self] (result: ForecastWeatherResult?) in
            guard let self, let result else { return }

            // result 콘솔에 찍어보기
            for forecastWeather in result.list {
                print(&quot;\(forecastWeather.main)\n\(forecastWeather.dtTxt)\n\n&quot;)
            }

            // UI 작업은 메인 쓰레드에서
            DispatchQueue.main.async {
                self.dataSource = result.list
                self.tableView.reloadData()
            }
        }
    }

    private func configureUI() {
        view.backgroundColor = .black
        [
            titleLabel,
            tempLabel,
            tempStackView,
            imageView,
            tableView
        ].forEach { view.addSubview($0) }

        [
            tempMinLabel,
            tempMaxLabel
        ].forEach { tempStackView.addArrangedSubview($0) }

        titleLabel.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalToSuperview().offset(120)
        }

        tempLabel.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(titleLabel.snp.bottom).offset(10)
        }

        tempStackView.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(tempLabel.snp.bottom).offset(10)
        }

        imageView.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.width.height.equalTo(160)
            $0.top.equalTo(tempStackView.snp.bottom).offset(20)
        }

        tableView.snp.makeConstraints {
            $0.top.equalTo(imageView.snp.bottom).offset(30)
            $0.leading.trailing.equalToSuperview().inset(20)
            $0.bottom.equalToSuperview().inset(50)
        }
    }
}

extension ViewController: UITableViewDelegate {
    // 테이블 뷰 셀의 높이 크기 지정.
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -&gt; CGFloat {
        40
    }
}

extension ViewController: UITableViewDataSource {
    // 테이블 뷰의 indexPath 마다 테이블 셀 지정.
    // indexPath = 테이블 뷰의 행과 섹션을 지정. 여기서 섹션은 사용하지 않고 행만 사용함.
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.id) as? TableViewCell else { return UITableViewCell() }
        cell.configureCell(forecastWeather: dataSource[indexPath.row])
        return cell
    }
    // 테이블 뷰 섹션에 행이 몇 개 들어가는가. 여기서 섹션은 없으니 그냥 총 행 개수를 입력하면 된다.
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {
        dataSource.count
    }
}</code></pre>
<h4 id="--forecastweatherresultswift">- ForecastWeatherResult.swift</h4>
<pre><code class="language-swift">import Foundation

struct ForecastWeatherResult: Codable {
    let list: [ForecastWeather]
}

struct ForecastWeather: Codable {
    let main: WeatherMain
    let dtTxt: String

    enum CodingKeys: String, CodingKey {
        case main
        case dtTxt = &quot;dt_txt&quot;
    }
}</code></pre>
<h4 id="--tablecellswift">- TableCell.swift</h4>
<pre><code class="language-swift">import UIKit

final class TableViewCell: UITableViewCell {

    static let id = &quot;TableViewCell&quot;

    private let dtTxtlabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .black
        label.textColor = .white
        return label
    }()

    private let templabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .black
        label.textColor = .white
        return label
    }()

    // TableView 의 style 과 id 로 초기화할때 사용하는 코드.
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        configureUI()
    }

    private func configureUI() {
        contentView.backgroundColor = .black
        [
            dtTxtlabel,
            templabel
        ].forEach { contentView.addSubview($0) }
        dtTxtlabel.snp.makeConstraints {
            $0.leading.equalToSuperview().inset(20)
            $0.centerY.equalToSuperview()
        }
        templabel.snp.makeConstraints {
            $0.trailing.equalToSuperview().inset(20)
            $0.centerY.equalToSuperview()
        }
    }

    public func configureCell(forecastWeather: ForecastWeather) {
        dtTxtlabel.text = forecastWeather.dtTxt
        templabel.text = &quot;\(forecastWeather.main.temp)°C&quot;
    }

    // 인터페이스 빌더를 통해 셀을 초기화 할 때 사용하는 코드. 여기서는 fatalError 를 통해 명시적으로 인터페이스 빌더로 초기화 하지 않음을 나타냄.
    required init?(coder: NSCoder) {
        fatalError(&quot;init(coder:) has not been implemented&quot;)
    }
}
</code></pre>
<blockquote>
<h3 id="alamofire-로-날씨-앱-만들기">Alamofire 로 날씨 앱 만들기</h3>
</blockquote>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/97fad122-28e5-4fea-a072-59a8345f7696/image.png" alt=""></p>
<ul>
<li>Alamofire 는 Swift 의 HTTP 네트워킹 라이브러리.</li>
<li>내부적으로 URLSession 을 사용.</li>
<li>URLSession 을 한단계 감싸서 네트워크 코드 사용성에 편의를 제공.</li>
<li><code>AF.request(url)</code> 메서드를 통해서 네트워크 통신 수행.</li>
<li><code>AF.request(url).responseDecodable(of: xxx)</code> 를 통해서 네트워크 통신과 동시에 response 를 디코딩 가능.</li>
<li><code>Result&lt;T, AFError&gt;</code> 타입으로 response 반환</li>
</ul>
<hr>
<p><strong>- 위 URLSession 으로 작성했었던 fetchData() 메서드는, Alamofire 로 작성하면 아래와 같습니다.</strong></p>
<pre><code class="language-swift">import Alamofire

// 서버 데이터를 불러오는 메서드
private func fetchDataByAlamofire&lt;T: Decodable&gt;(url: URL, completion: @escaping (Result&lt;T, AFError&gt;) -&gt; Void) {
    AF.request(url).responseDecodable(of: T.self) { response in
        completion(response.result)
    }
}</code></pre>
<ul>
<li><strong>URLSession 과 비교했을 때</strong><ul>
<li><code>successRange</code> 를 정의해주지 않아도, <code>Alamofire</code> 가 <code>success</code> 와 <code>failure</code> 여부를 판단.</li>
<li><code>responseDecodable</code> 메서드를 통해서 <code>JSONDecoder().decode()</code> 과정 생략.</li>
<li>코드 간소화.</li>
</ul>
</li>
</ul>
<p><strong>- Alamofire 를 사용한 Current Weather 와 ForecastWeather fetch 메서드.</strong></p>
<pre><code class="language-swift">// 서버에서 현재 날씨 데이터를 불러오는 메서드
private func fetchCurrentWeatherData() {
    var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/weather&quot;)
    urlComponents?.queryItems = self.urlQueryItems

    guard let url = urlComponents?.url else {
        print(&quot;잘못된 URL&quot;)
        return
    }

    fetchDataByAlamofire(url: url) { [weak self] (result: Result&lt;CurrentWeatherResult, AFError&gt;) in
        switch result {
        case .success(let result):
            DispatchQueue.main.async {
                self?.tempLabel.text = &quot;\(Int(result.main.temp))°C&quot;
                self?.tempMinLabel.text = &quot;최소: \(Int(result.main.temp_min))°C&quot;
                self?.tempMaxLabel.text = &quot;최고: \(Int(result.main.temp_max))°C&quot;
            }

            guard let imageUrl = URL(string: &quot;https://openweathermap.org/img/wn/\(result.weather[0].icon)@2x.png&quot;) else {
                return
            }

            // Alamofire 를 사용한 이미지 로드
            AF.request(imageUrl).responseData { response in
                if let data = response.data, let image = UIImage(data: data) {
                    DispatchQueue.main.async {
                        self?.imageView.image = image
                    }
                }
            }
        case .failure(let error):
            print(&quot;데이터 로드 실패: \(error)&quot;)
        }
    }
}

// 서버에서 5일 간 날씨 예보 데이터를 불러오는 메서드
private func fetchForecastData() {
    var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/forecast&quot;)
    urlComponents?.queryItems = self.urlQueryItems

    guard let url = urlComponents?.url else {
        print(&quot;잘못된 URL&quot;)
        return
    }

    fetchDataByAlamofire(url: url) { [weak self] (result: Result&lt;ForecastWeatherResult, AFError&gt;) in
        guard let self else { return }
        switch result {
        case .success(let result):
            DispatchQueue.main.async {
                self.dataSource = result.list
                self.tableView.reloadData()
            }
        case .failure(let error):
            print(&quot;데이터 로드 실패: \(error)&quot;)
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[네트워크 통신 이해]]></title>
            <link>https://velog.io/@nemo_/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%86%B5%EC%8B%A0-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@nemo_/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%86%B5%EC%8B%A0-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Fri, 27 Dec 2024 05:14:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="네트워크-기본-개념">네트워크 기본 개념</h3>
</blockquote>
<hr>
<p>💻 네트워크란 둘 이상의 컴퓨터가 연결되고 소통하는 것을 말합니다.</p>
<p><img src="https://velog.velcdn.com/images/nemo_/post/674916a3-1e0a-4ba2-8375-9c0a8268063e/image.png" alt=""></p>
<ul>
<li>아이폰도 하나의 컴퓨터, 서버도 하나의 컴퓨터로 생각할 수 있기 때문에, 서버와 아이폰과의 통신도 네트워크 통신입니다.</li>
<li>인터넷이란 전 세계 컴퓨터를 연결하는 거대한 네트워크를 말합니다.</li>
<li>인터넷 연결을 위해서는 와이파이 연결이 되있거나 데이터가 켜져있어야 합니다.</li>
<li>Swift 로 서버와 통신하는 코드를 작성할 수 있습니다.</li>
<li>네트워크 통신 코드를 공부하기 전에, 알아야할 기본 개념들을 먼저 공부해봅시다.</li>
</ul>
<h4 id="json-이란">JSON 이란</h4>
<hr>
<p>💻 JSON (JavaScript Object Notation) 은 데이터를 표현하는 형식 중 하나입니다.</p>
<p><img src="https://velog.velcdn.com/images/nemo_/post/5f10053a-293c-4632-9e2d-eec81967ee79/image.png" alt=""></p>
<ul>
<li>이름은 Adam, 전화번호는 010-1111-2222 라는 전화번호 정보를 알고 있습니다. 이 정보를 다른 누군가에게 전달할 때 어떤 포맷으로 전달하는게 좋을까요?</li>
<li>그냥 쉽게 문자열로 <code>“이름Adam,전화번호010-1111-2222”</code> 로 보낸다면..?</li>
<li>아니면.. <code>“이름:Adam,전화번호:010-1111-2222”</code> 이렇게? 🤔</li>
<li>일반적으로 데이터를 표현하는 형식이 있다면, 그걸 따르는 게 좋을 것 같습니다.</li>
<li>이렇게 네트워크에서 데이터를 주고받으려면, 아무렇게나 주고 받는 것이 아니라 정해진 형식을 지켜서 데이터를 교환하는 것이 좋습니다.</li>
<li>이 중 서버와 클라이언트가 가장 많이 사용하는 데이터 형식이 JSON 형식입니다.</li>
<li>JSON 은 key-value 형태를 가집니다.</li>
<li>예를 들어, 앞선 강의의 전화번호 구조체를 JSON 으로 표현하면 다음과 같습니다.</li>
</ul>
<pre><code class="language-swift">[
    {
        &quot;name&quot;: &quot;Adam&quot;,
        &quot;phoneNumber&quot;: &quot;010-1111-2222&quot;
    },
    {
        &quot;name&quot;: &quot;Eve&quot;,
        &quot;phoneNumber&quot;: &quot;010-3333-4444&quot;
    },
    {
        &quot;name&quot;: &quot;Abel&quot;,
        &quot;phoneNumber&quot;: &quot;010-5555-6666&quot;
    }
]</code></pre>
<ul>
<li>이 JSON 데이터는 리스트<code>[ ]</code> 안에 3개의 전화번호부 데이터를 표현합니다.</li>
<li>JSON 은 특정한 프로그래밍 언어 안에 속하지 않으며, 대부분의 프로그래밍 언어에서는 JSON 포맷의 데이터를 다룰 수 있는 기능을 제공합니다. Swift 역시 마찬가지입니다.</li>
</ul>
<hr>
<h4 id="api-란">API 란</h4>
<h3 id=""></h3>
<hr>
<p>💻 <strong>API (Application Programming Interface) 란?</strong>
<img src="https://velog.velcdn.com/images/nemo_/post/0b4ef2a4-aa2a-40a2-adec-074968b70912/image.png" alt=""></p>
<ul>
<li><p>API 를 이해하기 위해서는 먼저 API 의 I(Interface) 가 뭔지 먼저 이해해야 합니다.</p>
</li>
<li><p><strong>개발 용어에서 인터페이스(Interface)는 항상 <code>창구</code>를 의미합니다.</strong></p>
<blockquote>
<p>너는 나의 내부가 어떻게 생겨먹었는지는 정확히 알 필요가 없어. 그저 내가 뚫어준 창구를 통해 나와 소통하면 돼.</p>
</blockquote>
<p>? 예를 들어, TV 를 컨트롤 하기 위한 리모콘을 생각해봅시다.</p>
<p><img src="https://velog.velcdn.com/images/nemo_/post/c5ea77d4-aeb4-4a6f-aba2-28956c8a649a/image.png" alt=""></p>
</li>
</ul>
<ul>
<li><p>리모콘의 <code>전원 버튼</code>, <code>채널 버튼</code>, <code>음량 버튼</code> 은 TV 를 컨트롤 하기 위한 <strong><code>창구 = API</code></strong>입니다.</p>
<ul>
<li>여러분들이 실제로 <code>음량 버튼</code> 내부 회로 및 하드웨어가 어떻게 동작하는지 알 필요가 있을까요?</li>
<li>그저 <code>음량 버튼을 눌렀을 때</code> → <code>TV 음량이 조절된다.</code> 의 결과가 잘 도출되는지가 중요합니다.<ul>
<li>음량 버튼을 눌렀다 = API 에게 내가 원하는 요청을 했다 = <code>API Request</code></li>
<li>음량이 조절 되었다 = API 로 부터 요청의 결과를 받았다 = <code>API Response</code></li>
</ul>
</li>
</ul>
<p><em>조금 더 실제 개발 상황을 예로 들어볼까요?</em></p>
</li>
</ul>
<ul>
<li><p>서버의 데이터베이스에 모든 유저의 정보를 담고 있습니다. 전화번호까지요.</p>
</li>
<li><p>클라이언트 (= iOS 네이티브 앱) 에서 “아담” 이라는 유저의 정보를 알고 싶습니다.</p>
</li>
<li><p>서버는 API 로 <code>UserInfo</code> 라는 API 를 뚫어 놓았고, 이 API 를 사용하면 유저의 정보를 알 수 있습니다. API 명세는 다음과 같습니다.</p>
</li>
<li><p><code>API Request</code> 는 이렇게 보내주세요.</p>
<pre><code class="language-swift">{
      name: &quot;Adam&quot;
}</code></pre>
</li>
<li><p><code>API Response</code> 는 이렇게 보내주겠습니다.</p>
<pre><code class="language-swift">{
      &quot;name&quot;: &quot;Adam&quot;,
      &quot;phoneNumber&quot;: &quot;010-1111-2222&quot;,
      &quot;Mbti&quot;: &quot;ENTJ&quot;
}</code></pre>
</li>
<li><p>iOS 개발자인 여러분은 이제 이 API 명세를 보고, Request JSON 과 Response JSON 형식을 지켜서 서버와 소통하는 코드를 작성하면 됩니다.</p>
</li>
<li><p>서버가 이 데이터를 돌려주기 위해서 내부적으로 어떤 로직을 수행했고, 서버 데이터베이스 내부가 어떻게 생겨먹었고를 알 필요가 없습니다.</p>
</li>
<li><p>그저 서버가 뚫어놓은 <code>API 라는 창구</code> 를 통해서 서버와 소통을 하고, 원하는 결과를 얻으면 iOS 개발자의 책임은 끝입니다.</p>
</li>
</ul>
<h4 id="👨🏻🍳-다시-이번엔-레스토랑에-비유해보겠습니다">👨🏻‍🍳 다시, 이번엔 레스토랑에 비유해보겠습니다.</h4>
<ul>
<li>클라이언트 앱 = 레스토랑의 손님</li>
<li>API = 레스토랑의 메뉴판, 웨이터</li>
<li>서버 = 레스토랑의 요리사</li>
<li><code>손님</code>이라는 <code>클라이언트</code>는 <code>메뉴판, 웨이터</code> 라는 <code>창구</code>를 통해서 <code>요리사</code>라는 <code>서버</code>의 음식을 받습니다.<ul>
<li>손님이 메뉴판을 보고 웨이터를 부른다 → API 명세를 파악한다</li>
<li>“봉골레 파스타 주세요” → API Request</li>
<li>요리사가 파스타를 손님에게 바친다 → API Response</li>
</ul>
</li>
</ul>
<p><strong>마지막으로 API 가 뭔지 정리해보면,</strong></p>
<ul>
<li>API 는 직역 그대로 <code>Application Programming</code> 에 필요한 <code>Interface</code> 입니다.</li>
<li>즉, 어떤 <code>프로그램을 개발</code>할 때 원하는 기능들을 제공해주는 <code>창구, 설명서, 도구</code> 입니다.</li>
</ul>
<hr>
<h3 id="swift-codable">Swift Codable</h3>
<hr>
<p>🧑🏻‍💻 <strong>Swift 의 인코딩과 디코딩</strong></p>
<ul>
<li><p><strong>인코딩</strong>: 데이터를 특정 형식으로 변환하는 것.</p>
</li>
<li><p><strong>디코딩</strong>: 인코딩 된 데이터를 다시 원본으로 변환하는 것.</p>
</li>
<li><p>Swift 의 <code>Codable</code> 프로토콜을 채택한다는 것은 인코딩과 디코딩이 될 수 있음을 의미.</p>
<ul>
<li><code>Codable</code> 안을 열어보면 <code>Decodable</code> &amp; <code>Encodable</code> 로 구현되어있음.</li>
</ul>
</li>
<li><p>서버와 통신하기 위해서, JSON 형식으로 인코딩을 많이 한다.</p>
<pre><code class="language-swift">struct PhoneBook: Codable {

  let name: String
  let phoneNumber: String
}</code></pre>
<p>Codable 을 채택함으로써 인코딩 디코딩이 가능한 객체가 됨.</p>
</li>
</ul>
<h4 id="🧑🏻💻-json-형식의-데이터에서-swift-로-데이터를-디코딩해서-추출하는-과정">🧑🏻‍💻 JSON 형식의 데이터에서 Swift 로 데이터를 디코딩해서 추출하는 과정</h4>
<pre><code class="language-swift">import Foundation

struct PhoneBook: Codable {

    let name: String
    let phoneNumber: String
}

// string 으로 json 모양의 데이터를 생성.
let jsonString = &quot;&quot;&quot;
[
    {
        &quot;name&quot;: &quot;Adam&quot;,
        &quot;phoneNumber&quot;: &quot;010-1111-2222&quot;
    },
    {
        &quot;name&quot;: &quot;Eve&quot;,
        &quot;phoneNumber&quot;: &quot;010-3333-4444&quot;
    },
    {
        &quot;name&quot;: &quot;Abel&quot;,
        &quot;phoneNumber&quot;: &quot;010-5555-6666&quot;
    }
]
&quot;&quot;&quot;

// jsonString 으로 jsonData 를 생성.
let jsonData = jsonString.data(using: .utf8)!

// Swift 가 제공하는 JSON 디코더.
let jsonDecoder = JSONDecoder()

// JSON -&gt; Codable 디코딩 진행.
do {
    let phoneBooks = try jsonDecoder.decode([PhoneBook].self, from: jsonData)
    for phoneBook in phoneBooks {
        print(&quot;name: \(phoneBook.name), phoneNumber: \(phoneBook.phoneNumber)&quot;)
    }
} catch {
    print(&quot;JSON 디코딩 실패&quot;)
}

</code></pre>
<p><img src="https://velog.velcdn.com/images/nemo_/post/c3a278cb-853c-4192-9fda-c598261d63ca/image.png" alt=""></p>
<hr>
<h3 id="url-구조">URL 구조</h3>
<hr>
<h4 id="url-의-구조에-대해-자세히-공부해봅니다">URL 의 구조에 대해 자세히 공부해봅니다.</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/6f1e96a3-798f-452c-9994-07f89ebb1a4b/image.png" alt=""></p>
<ul>
<li><p><strong>URL</strong> (Uniform Resource Locators): 웹에서 특정 위치를 나타내는 주소.</p>
</li>
<li><p><strong>Protocol</strong>: <code>http</code>, <code>https</code> → 인터넷 통신 규약을 의미.</p>
</li>
<li><p><strong>Domain</strong>: 자원이 위치한 서버(컴퓨터)의 이름. 예를 들어 <code>google</code>, <code>naver</code> . url 의 정체성을 나타낸다.</p>
</li>
<li><p><strong>Port</strong>: 구체적으로 어떤 서버를 이용할지 번호로 결정. HTTP 의 경우 80. HTTPS 는 443.</p>
</li>
<li><p><strong>Path</strong>: 서버에서 제공하는 자원의 경로를 나타냄.</p>
</li>
<li><p><strong>Query</strong>: 자원에 대한 추가적인 매개변수를 전달하는 데 사용됨. 주로 <code>key=value</code> 형식으로 표현되며, 여러 개의 매개변수는 <code>&amp;</code>로 구분.</p>
</li>
<li><p><strong>Fragment</strong>: 자원 내에서 특정 부분을 가리킬 때 사용.</p>
<p>  ex) <a href="https://ko.wikipedia.org/wiki/%EB%8C%80%ED%95%9C%EB%AF%BC%EA%B5%AD#%EB%AC%B8%ED%99%94">https://ko.wikipedia.org/wiki/대한민국#문화</a></p>
</li>
</ul>
<hr>
<h3 id="rest-api">REST API</h3>
<hr>
<h4 id="rest-api-representational-state-transfer-란-무엇인지-공부합니다">REST API (Representational State Transfer) 란 무엇인지 공부합니다.</h4>
<ul>
<li>전세계에서 대표적으로 널리 쓰이는 API 형식 중 하나.</li>
<li>상태 (State) 를 표현해서 정보를 주고 받는 API 이다.</li>
<li>HTTP URL 을 통해서 자원을 명시한다.</li>
<li>HTTP Method (GET, POST, PUT, DELETE 등) 를 통해 해당 자원을 어떻게 할 것인지 CRUD 를 결정한다.<ul>
<li><strong>GET</strong>: 자원을 조회합니다.</li>
<li><strong>POST</strong>: 자원을 생성합니다.</li>
<li><strong>PUT</strong>: 자원을 업데이트합니다.</li>
<li><strong>DELETE</strong>: 자원을 삭제합니다.</li>
</ul>
</li>
<li>REST API 도 결국 API 이기 때문에, 데이터를 주고 받는 형식, 창구라고 생각 할 수 있음.</li>
</ul>
<p><strong>🧐 예를들어, <code>https://spartacodingclub.com</code> 서버와 REST API 통신을 한다고 가정해봅시다.</strong></p>
<p>스파르타 코딩클럽의 유저 정보 데이터들은 <strong><code>https://spartacodingclub.com/users</code></strong> 에 저장되어있다고 가정합니다. (실제로 그렇지 않습니다.) </p>
<p>유저 데이터를 조회하고 싶으면 GET 메소드와 해당 URL 을 사용해서 네트워크 통신을 하면 됩니다.</p>
<h4 id="요청-방식-get-httpsspartacodingclubcomusers">요청 방식: GET <a href="https://spartacodingclub.com/users">https://spartacodingclub.com/users</a></h4>
<pre><code class="language-swift">[
    {
        &quot;id&quot;: 1,
        &quot;name&quot;: &quot;Adam&quot;,
        &quot;email&quot;: &quot;adam.doe@example.com&quot;
    },
    {
        &quot;id&quot;: 2,
        &quot;name&quot;: &quot;Eve&quot;,
        &quot;email&quot;: &quot;eve.smith@example.com&quot;
    }
]
</code></pre>
<p>위처럼 URL 을 가지고 네트워크 통신을 할 수 있게 하는 Swift 의 클래스가 바로 URLSession 입니다.</p>
<hr>
<h3 id="urlsession">URLSession</h3>
<hr>
<h4 id="urlsession-은-swift-에서-서버와-통신하기-위해-제공되는-클래스">URLSession 은 Swift 에서 서버와 통신하기 위해 제공되는 클래스.</h4>
<ul>
<li>URLSession 을 다루기 위해서는 크게 아래 2가지 개념을 알아야 합니다.<ol>
<li>URLSessionConfiguration</li>
<li>URLSessionTask</li>
</ol>
</li>
</ul>
<ol>
<li><p><strong>URLSessionConfiguration</strong></p>
<p> Configuration 이란 환경 설정을 의미.</p>
<p> <code>URLSession</code> 으로 네트워크 통신을 하되, 여러가지 커스텀한 설정들을 할 때 <code>URLSessionConfiguration</code> 을 이용.</p>
<p> 예를들어 네트워크 통신의 타임아웃 시간 설정, 네트워크 통신 캐시 정책 설정 등을 세팅할 수 있음.</p>
<p> <code>URLSession</code> 객체를 생성하려면 <code>URLSessionConfiguration</code> 을 넣어줘야 함.</p>
<p> 다음과 같이 <code>default configuration</code> 을 활용해 <code>URLSession</code> 생성 가능.</p>
</li>
</ol>
<pre><code class="language-swift">let defaultUrlSession = URLSession(configuration .default)</code></pre>
<ol start="2">
<li><p><strong>URLSessionTask</strong></p>
<p> <code>URLSessionTask</code> 으로 네트워크 통신을 할 때 어떤 태스크를 수행할 것 인지 결정 가능.</p>
<ul>
<li><code>URLSessionDataTask</code>: GET 요청. 서버로부터 데이터를 가져오거나 서버에 데이터를 전송할 때 사용.</li>
<li><code>URLSessionDownloadTask</code> 파일 다운로드를 처리할 때 사용. 백그라운드 다운로드 지원.</li>
<li><code>URLSessionUploadTask</code>: 파일 업로드를 처리할 때 사용. 백그라운드 업로드 지원.</li>
</ul>
</li>
</ol>
<p><strong>URLSession 을 통해, 서버의 데이터를 GET 해오는 예제 코드를 작성해봅시다.</strong></p>
<ul>
<li><a href="https://reqres.in/api/users/1">https://reqres.in/api/users/1</a></li>
<li>위 URL 은 테스트를 위한 데이터를 내려주는 사이트입니다.</li>
<li>GET 메소드를 사용해서 REST API 통신을 수행해봅시다.</li>
</ul>
<pre><code class="language-swift">import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        fetchData()
    }

    // 서버 데이터를 불러오는 메서드 선언
    private func fetchData() {

        let defaultUrlSession = URLSession(configuration: .default)

        guard let url: URL = URL(string: &quot;https://reqres.in/api/users/1&quot;) else {
            print(&quot;URL is not correct&quot;)
            return
        }

        // URLRequest 설정
        var request: URLRequest = URLRequest(url: url)

        // GET 메소드 사용
        request.httpMethod = &quot;GET&quot;

        // json 데이터 형식임을 나타냄
        request.addValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)

        // URLSession 생성 (기본 default 세션)
        let session: URLSession = URLSession(configuration: .default)

        // dataTask
        session.dataTask(with: request) { (data, response, error) in
                // http 통신 response 에는 status code 가 함께오는데, 200번대가 성공을 의미.
            let successRange: Range = (200..&lt;300)

            // 통신 성공
            guard let data, error == nil else { return }

            if let response: HTTPURLResponse = response as? HTTPURLResponse{
                print(&quot;status code: \(response.statusCode)&quot;)

                // 요청 성공 (StatusCode가 200번대)
                if successRange.contains(response.statusCode){

                    // decode
                    guard let userInfo: ResponseData = try? JSONDecoder().decode(ResponseData.self, from: data) else { return }
                    print(userInfo)

                } else { // 요청 실패 (Status code가 200대 아님)
                    print(&quot;요청 실패&quot;)
                }
            }

        }.resume()
    }
}

// 데이터 구조체 정의
struct UserData: Codable {
    let id: Int
    let email: String
    let firstName: String
    let lastName: String
    let avatar: URL

    // JSON 키와 구조체 프로퍼티 간의 매핑을 위해 CodingKeys 열거형 정의
    enum CodingKeys: String, CodingKey {
        case id
        case email
        case firstName = &quot;first_name&quot;
        case lastName = &quot;last_name&quot;
        case avatar
    }
}

// Support 구조체 정의
struct SupportData: Codable {
    let url: URL
    let text: String
}

// 최상위 구조체 정의
struct ResponseData: Codable {
    let data: UserData
    let support: SupportData
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/nemo_/post/2dd71ad3-dd46-410c-abb4-a3bbd0aaa984/image.png" alt="">
→ 서버에서 받아온 값이 잘 print 되는 것 확인.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[CoreData와 UserDefaults]]></title>
            <link>https://velog.io/@nemo_/CoreData%EC%99%80-UserDefaults</link>
            <guid>https://velog.io/@nemo_/CoreData%EC%99%80-UserDefaults</guid>
            <pubDate>Fri, 20 Dec 2024 07:06:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="crud-개념">CRUD 개념</h3>
</blockquote>
<hr>
<h4 id="crud-create-read-update-delete-란">CRUD (Create, Read, Update Delete) 란?</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/5dab45ae-b16d-4465-9899-99e724267af2/image.png" alt=""></p>
<p>-&gt; 일반적인 개발론에서 CRUD 라는 용어는 자주 사용합니다. 뜻은 다음과 같습니다.</p>
<ul>
<li>Create = 데이터 생성</li>
<li>Read = 데이터 읽기</li>
<li>Update = 데이터 업데이트 (쓰기)</li>
<li>Delete = 데이터 삭제</li>
</ul>
<p>전화번호 앱에서 일어나는 CRUD 에 대해 생각해봅시다.</p>
<ul>
<li>C = 새로운 전화번호를 등록한다.</li>
<li>R = 저장된 전화번호 데이터를 조회한다.</li>
<li>U = 저장된 전화번호를 수정한다.</li>
<li>D = 전화번호를 삭제한다.</li>
</ul>
<p>데이터 CRUD 는 네이티브 (앱) 내부에서도 일어날 수 있고, 서버에서도 일어날 수 있습니다.</p>
<blockquote>
<h3 id="coredata">CoreData</h3>
</blockquote>
<hr>
<h4 id="coredata-란">CoreData 란?</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/c4d69363-ced1-4a1f-817a-12cb63ad96c3/image.png" alt=""></p>
<ul>
<li><code>CoreData</code> 는 앱에서 기기의 <strong><code>디스크</code></strong>에 데이터를 읽고 쓸 수 있게 돕는 프레임워크.</li>
<li>Swift 로 기기 내 디스크에 데이터를 저장할 수 있는 대표적인 방법으로는 <code>CoreData</code> 와 <code>UserDefaults</code> 가 있다.</li>
</ul>
<h4 id="1-coredata-프로젝트-생성">(1) CoreData 프로젝트 생성</h4>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/8b8cfd13-e945-4956-806a-1d77ccfeb13d/image.png" alt=""></p>
<ul>
<li>프로젝트를 생성할 때 Storage → CoreData 를 선택.</li>
</ul>
<h4 id="2-entity-생성">(2) Entity 생성</h4>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/97247dc9-ccc0-4973-a0fd-c32ca42d7453/image.png" alt=""></p>
<ul>
<li>아래 Add Entity 버튼을 클릭해서 <code>Entity</code> 만들기. Entity 는 저장될 데이터들의 집합.</li>
<li>우리는 <code>PhoneBook</code> 이라는 <code>Entity</code> 를 만들어보겠습니다.
<img src="https://velog.velcdn.com/images/nemo_/post/ae21a130-39b0-4d80-8854-068f04548041/image.png" alt=""></li>
<li><code>Entity</code> 이름은 <code>PhoneBook</code></li>
<li>Attribute 로는 <code>name</code> , <code>phoneNumber</code> 를 추가합니다.</li>
<li><code>전화번호부</code> 라는 데이터 집합 안에 <code>이름</code> 과 <code>전화번호</code> 라는 속성이 부여된 것.
<img src="https://velog.velcdn.com/images/nemo_/post/6a439128-93d8-4eef-ae01-0c220b20ffb9/image.png" alt=""></li>
<li>오른쪽 인스펙터 영역 중 <code>Codegen</code> 개념<ul>
<li><code>Code Generator</code> 의 줄임말. Entity 를 어떤 형식의 코드로 생성할 것인지 선택하는 속성.</li>
<li><code>Manual/None</code> = Entity 의 서브 클래스를 자동으로 생성하지 않고 개발자가 클래스 작성.</li>
<li><code>Class Definition</code> = Entity 의 서브 클래스를 자동으로 생성.</li>
<li><code>Category/Extension</code> = Entity 클래스와 함께 extension 을 위한 파일까지 생성.</li>
<li>여기서는 <code>Manual/None</code> 으로 생성해보겠습니다.</li>
</ul>
</li>
</ul>
<h4 id="3-code-generate">(3) Code Generate</h4>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/3b3f3593-7a5a-4f4b-b60c-1ce51eb452c2/image.png" alt=""></p>
<ul>
<li>Editor → Create NSManagedObject Subclass 를 클릭해서 코드를 생성.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/nemo_/post/c89ffd3a-cf0c-49d4-a393-8eb2017a5c62/image.png" alt=""></p>
<p>그럼 위와 같이 2개의 파일이 생성됨.</p>
<ul>
<li>PhoneBook+CoreDataClass.swift 간단 설명.<pre><code class="language-swift">import Foundation
import CoreData
</code></pre>
</li>
</ul>
<p>@objc(PhoneBook)
public class PhoneBook: NSManagedObject {</p>
<p>}</p>
<pre><code>
- NSManagedObject는 Core Data 프레임워크에서 관리되는 객체를 나타내는 기본 클래스. 이 클래스는 Core Data 엔티티와의 상호작용을 관리하며, 속성 값의 저장 및 검색을 처리.

- PhoneBook+CoreDataProperties.swift 간단 설명.
```swift
import Foundation
import CoreData


extension PhoneBook {

    @nonobjc public class func fetchRequest() -&gt; NSFetchRequest&lt;PhoneBook&gt; {
        return NSFetchRequest&lt;PhoneBook&gt;(entityName: &quot;PhoneBook&quot;)
    }

    @NSManaged public var name: String?
    @NSManaged public var phoneNumber: String?

}

extension PhoneBook : Identifiable {

}</code></pre><ul>
<li><code>@nonobjc</code> = Objective-C 에서는 동작하지 않고 Swift 에서만 동작하는 메서드임을 명시.</li>
<li><code>fetchRequest()</code> = PhoneBook 에 대한 여러가지 데이터 검색을 도움.</li>
<li><code>@NSManaged</code> = CoreData 에 의해 관리되는 객체를 의미.</li>
<li><code>Identifiable</code> = PhoneBook 타입이 고유하게 식별될 수 있음을 의미.</li>
</ul>
<h4 id="4-nspersistentcontainer-생성">(4) NSPersistentContainer 생성</h4>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/e457ca44-b9ad-4af7-bbe1-123d9d7da774/image.png" alt=""></p>
<ul>
<li><p><code>NSPersistentContainer</code> 는 CoreData에서 데이터를 저장하고 관리하는 데 필요한 핵심 객체.</p>
<p>  → 직역해보면 영구적인 저장 장소.</p>
</li>
<li><p>먼저 <code>NSPersistentContainer</code> 를 생성해줘야하는데, 프로젝트 생성할 때 <code>CoreData</code> 를 사용한다고 체크 해줬으므로, <code>AppDelegate.swift</code> 에 기본적으로 NSPersistentContainer 를 세팅하는 코드가 존재.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/nemo_/post/73c375bf-688f-4266-b498-0910d2fabe08/image.png" alt=""></p>
<ul>
<li><code>AppDelegate.swift</code> 아래쪽에보면 <code>saveContext()</code> 라는 메서드도 자동으로 생성되어있는데, 직역하면 문맥을 저장한다는 뜻.</li>
<li>데이터의 업데이트(추가, 업데이트, 삭제 등)가 일어났으면 <code>saveContext()</code> 를 호출해서 그 문맥을 저장해야 함.</li>
</ul>
<h4 id="4-coredata-를-활용한-crud">(4) CoreData 를 활용한 CRUD.</h4>
<hr>
<ul>
<li>NSPersistentConatiner 를 만들었으므로, ViewController 에서 이를 활용해서 데이터 접근.</li>
<li>CoreData 를 통해 CRUD 의 C, R 을 하는 과정.<pre><code class="language-swift">import UIKit
import CoreData
</code></pre>
</li>
</ul>
<p>class ViewController: UIViewController {</p>
<pre><code>var container: NSPersistentContainer!

override func viewDidLoad() {
    super.viewDidLoad()

    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    self.container = appDelegate.persistentContainer

    createData(name: &quot;Adam&quot;, phoneNumber: &quot;010-1111-2222&quot;)
    readAllData()
}

// AdamCoreData 에 데이터 Create.
func createData(name: String, phoneNumber: String) {
    guard let entity = NSEntityDescription.entity(forEntityName: &quot;PhoneBook&quot;, in: self.container.viewContext) else { return }
    let newPhoneBook = NSManagedObject(entity: entity, insertInto: self.container.viewContext)
    newPhoneBook.setValue(name, forKey: &quot;name&quot;)
    newPhoneBook.setValue(phoneNumber, forKey: &quot;phoneNumber&quot;)

    do {
        try self.container.viewContext.save()
        print(&quot;문맥 저장 성공&quot;)
    } catch {
        print(&quot;문맥 저장 실패&quot;)
    }
}

// AdamCoreData 에서 데이터 Read.
func readAllData() {
    do {
        let phoneBooks = try self.container.viewContext.fetch(PhoneBook.fetchRequest())

        for phoneBook in phoneBooks as [NSManagedObject] {
            if let name = phoneBook.value(forKey: &quot;name&quot;) as? String,
               let phoneNumber = phoneBook.value(forKey: &quot;phoneNumber&quot;) as? String {
                print(&quot;name: \(name), phoneNumber: \(phoneNumber)&quot;)
            }
        }

    } catch {
        print(&quot;데이터 읽기 실패&quot;)
    }
}</code></pre><p>}</p>
<pre><code>![](https://velog.velcdn.com/images/nemo_/post/628f1c1a-f0e4-48e7-b804-9668fe0b7926/image.png)

→ 디스크에 저장했으므로, 데이터를 저장한 코드를 삭제한 뒤 앱을 다시켜도 데이터가 남아있음을 확인 가능.

💻 **여기서 잠깐 리팩토링을 해봅시다.**
코드를 좀 더 나은 방향으로 개선하는 것을 리팩토링이라고 합니다.
- PhoneBook 클래스를 이렇게 수정해봅시다.

```swift
@objc(PhoneBook)
public class PhoneBook: NSManagedObject {
    public static let className = &quot;PhoneBook&quot;
    public enum Key {
        static let name = &quot;name&quot;
        static let phoneNumber = &quot;phoneNumber&quot;
    }
}
</code></pre><p>→ <code>static</code> 프로퍼티는 그 타입에 대고 호출을 할 수 있는 프로퍼티입니다.</p>
<p>→ <code>PhoneBook.className</code> → <code>PhoneBook</code> 이라는 클래스 타입에 대고 점을찍고 호출.</p>
<ul>
<li><strong>그럼 이렇게 했을때 장점..? 🤔</strong><ol>
<li>자동완성 기능을 사용할 수 있게 된다.</li>
<li>직접 손으로 <code>“phoneNumber”</code> 라고 타자를 치지 않기 때문에 실수할 일이 적어진다 → 휴먼 에러를 줄인다.</li>
<li>값을 수정해야할일이 생겼을때 전체 다 일일이 안고쳐도되고 이곳 한군데만 고치면 된다.</li>
<li>성격이 같은 프로퍼티끼리 모아서 관리할 수 있다.</li>
</ol>
</li>
</ul>
<h4 id="--리팩토링-후-c-r-과정">- 리팩토링 후 C, R 과정</h4>
<pre><code class="language-swift">import UIKit
import CoreData

class ViewController: UIViewController {

    var container: NSPersistentContainer!

    override func viewDidLoad() {
        super.viewDidLoad()

        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        self.container = appDelegate.persistentContainer
        createData(name: &quot;Adam&quot;, phoneNumber: &quot;010-1111-2222&quot;)
        readAllData()
    }

    func createData(name: String, phoneNumber: String) {
        guard let entity = NSEntityDescription.entity(forEntityName: PhoneBook.className, in: self.container.viewContext) else { return }
        let newPhoneBook = NSManagedObject(entity: entity, insertInto: self.container.viewContext)
        newPhoneBook.setValue(name, forKey: PhoneBook.Key.name)
        newPhoneBook.setValue(phoneNumber, forKey: PhoneBook.Key.phoneNumber)

        do {
            try self.container.viewContext.save()
            print(&quot;문맥 저장 성공&quot;)
        } catch {
            print(&quot;문맥 저장 실패&quot;)
        }
    }

    func readAllData() {
        do {
            let phoneBooks = try self.container.viewContext.fetch(PhoneBook.fetchRequest())

            for phoneBook in phoneBooks as [NSManagedObject] {
                if let name = phoneBook.value(forKey: PhoneBook.Key.name) as? String,
                   let phoneNumber = phoneBook.value(forKey: PhoneBook.Key.phoneNumber) {
                    print(&quot;name: \(name), phoneNumber: \(phoneNumber)&quot;)
                }
            }
        } catch {
            print(&quot;데이터 읽기 실패&quot;)
        }
    }
}</code></pre>
<h4 id="--coredata-를-통해-crud-의-u-를-하는-과정">- CoreData 를 통해 CRUD 의 U 를 하는 과정.</h4>
<pre><code class="language-swift">func updateData(currentName: String, updateName: String) {

    // 수정할 데이터를 찾기 위한 fetch request 생성
    let fetchRequest = PhoneBook.fetchRequest()
    fetchRequest.predicate = NSPredicate(format: &quot;name == %@&quot;, currentName) // 예시: 이름이 &quot;Adam&quot;인 데이터 수정

    do {
        // fetch request 실행
        let result = try self.container.viewContext.fetch(fetchRequest)

        // 결과 처리
        for data in result as [NSManagedObject] {
            // 데이터 수정
            data.setValue(updateName, forKey: PhoneBook.Key.name) // 이름을 &quot;Adam&quot;에서 &quot;Abel&quot;로 수정

            // 변경 사항 저장
            try self.container.viewContext.save()
            print(&quot;데이터 수정 완료&quot;)
        }

    } catch {
        print(&quot;데이터 수정 실패&quot;)
    }
}</code></pre>
<h4 id="--coredata-를-통해-crud-의-d-를-하는-과정">- CoreData 를 통해 CRUD 의 D 를 하는 과정.</h4>
<pre><code class="language-swift">func deleteData(name: String) {
    // 삭제할 데이터를 찾기 위한 fetch request 생성
    let fetchRequest = PhoneBook.fetchRequest()
    fetchRequest.predicate = NSPredicate(format: &quot;name == %@&quot;, name)

    do {
        // fetch request 실행
        let result = try self.container.viewContext.fetch(fetchRequest)

        // 결과 처리
        for data in result as [NSManagedObject] {
            // 삭제
            // CRUD 의 D.
            self.container.viewContext.delete(data)
            print(&quot;삭제된 데이터: \(data)&quot;)
        }

        // 변경 사항 저장
        try self.container.viewContext.save()
        print(&quot;데이터 삭제 완료&quot;)

    } catch {
        print(&quot;데이터 삭제 실패: \(error)&quot;)
    }
}</code></pre>
<blockquote>
<h3 id="userdefaults">UserDefaults</h3>
</blockquote>
<hr>
<ul>
<li><code>UserDefaults</code> 또한 <code>디스크</code>에 데이터를 저장할 수 있게 돕는 도구.</li>
<li>CoreData 보다 사용성이 간단.</li>
<li><code>key</code> 와 <code>value</code> 를 이용해서 값을 저장.</li>
<li>대량의 데이터를 담는데에는 <code>CoreData</code> 가, 비교적 단순한 데이터를 담는 데에는 <code>UserDefaults</code> 가 적절.</li>
</ul>
<h4 id="userdefaults-의-crud">UserDefaults 의 CRUD</h4>
<hr>
<ul>
<li><code>UserDefaults.standard.set()</code> 메서드를 통해서 <code>Create</code>, <code>Update</code></li>
<li><code>UserDefaults.standard.string(forKey: &quot;&quot;)</code> 메서드를 통해서 <code>Read</code> (각 타입에 맞는 메서드사용)<ul>
<li>bool 타입 <code>Read</code>: <code>UserDefaults.standard.bool(forKey: &quot;&quot;)</code></li>
<li>Int 타입 <code>Read</code>: <code>UserDefaults.standard.integer(forKey: &quot;&quot;)</code></li>
</ul>
</li>
<li><code>UserDefaults.standard.removeObject(forKey: &quot;&quot;)</code> 메서드를 통해서 <code>Delete</code></li>
</ul>
<pre><code class="language-swift">import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create
        UserDefaults.standard.set(&quot;010-1111-2222&quot;, forKey: &quot;phoneNumber&quot;)

        // Read
        let phoneNumber = UserDefaults.standard.string(forKey: &quot;phoneNumber&quot;)
        print(&quot;저장된 전화번호: \(phoneNumber)&quot;)

        // Update
        // 같은 키에다가 set 을 하면 됨.
        UserDefaults.standard.set(&quot;010-6666-7777&quot;, forKey: &quot;phoneNumber&quot;)
        let newPhoneNumber = UserDefaults.standard.string(forKey: &quot;phoneNumber&quot;)
        print(&quot;바뀐 전화번호: \(newPhoneNumber)&quot;)

        // Delete
        UserDefaults.standard.removeObject(forKey: &quot;phoneNumber&quot;)
        print(&quot;전화번호가 남아있는가: \(UserDefaults.standard.string(forKey: &quot;phoneNumber&quot;))&quot;)
    }
}</code></pre>
<ul>
<li>Int, String 과 같은 원시타입이 아닌 Struct 나 Class 타입을 저장하기 위해서는 json 인코딩 과정이 필요.</li>
</ul>
<h4 id="userdefaults-를-활용해서-간단한-포스트잇-앱-만들기">UserDefaults 를 활용해서 간단한 포스트잇 앱 만들기</h4>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/642ab51e-a6eb-4920-a0b6-08f4df332904/image.png" alt=""></p>
<p>포스트잇 앱 만들기</p>
<ul>
<li><code>UserDefaults</code> 는 디스크에 데이터를 저장하기 때문에, 포스트잇에 적은 텍스트를 UserDefaults 에 저장 해둔다면, 앱을 종료해도 데이터가 소멸되지 않습니다.</li>
<li>메모를 작성하고, 앱을 종료했다가 다시 실행해도 메모가 남는 포스트잇 앱을 만들어봅니다.</li>
</ul>
<pre><code class="language-swift">import UIKit
import SnapKit

class ViewController: UIViewController {

    private let label: UILabel = {
        let label = UILabel()
        label.text = &quot;포스트잇&quot;
        label.font = .boldSystemFont(ofSize: 30)
        label.textColor = .black
        return label
    }()

    private let textView: UITextView = {
        let textView = UITextView()
        textView.text = UserDefaults.standard.string(forKey: &quot;memo&quot;)
        textView.layer.cornerRadius = 10
        textView.backgroundColor = UIColor(red: 75/255, green: 253/255, blue: 30/355, alpha: 1.0)
        textView.textColor = .black
        textView.font = .boldSystemFont(ofSize: 30)
        return textView
    }()

    private lazy var button: UIButton = {
        let button = UIButton()
        button.setTitle(&quot;적용&quot;, for: .normal)
        button.backgroundColor = .red
        button.setTitleColor(.white, for: .normal)
        button.titleLabel?.font = .boldSystemFont(ofSize: 20)
        button.layer.cornerRadius = 10
        button.addTarget(self, action: #selector(buttonTapped), for: .touchDown)
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        [
            label,
            textView,
            button
        ].forEach { view.addSubview($0) }

        view.backgroundColor = .white

        label.snp.makeConstraints {
            $0.top.equalToSuperview().offset(100)
            $0.centerX.equalToSuperview()
        }

        textView.snp.makeConstraints {
            $0.top.equalTo(label.snp.bottom).offset(100)
            $0.centerX.equalToSuperview()
            $0.height.width.equalTo(200)
        }

        button.snp.makeConstraints {
            $0.top.equalTo(textView.snp.bottom).offset(50)
            $0.width.equalTo(60)
            $0.height.equalTo(40)
            $0.centerX.equalToSuperview()
        }
    }

    @objc
    private func buttonTapped() {
        UserDefaults.standard.set(textView.text, forKey: &quot;memo&quot;)
        print(&quot;저장 완료&quot;)
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[메모리 관리 이해]]></title>
            <link>https://velog.io/@nemo_/%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@nemo_/%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Fri, 13 Dec 2024 12:07:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="메모리와-디스크-기본-개념">메모리와 디스크 기본 개념</h3>
</blockquote>
<hr>
<h4 id="메모리와-디스크는-모두-컴퓨터나-스마트폰에서-데이터의-저장-및-처리를-담당하지만-목적과-특성이-다르다">메모리와 디스크는 모두 컴퓨터나 스마트폰에서 데이터의 저장 및 처리를 담당하지만 목적과 특성이 다르다.</h4>
<p>▪️ <strong>메모리</strong></p>
<ul>
<li><p>일반적으로 RAM 을 말하는 경우가 많다.</p>
</li>
<li><p>맥북에서도 몇 GB 짜리 RAM 을 사용하는지 볼 수 있다.</p>
</li>
<li><p>RAM 은 휘발성 메모리이다. 즉, 데이터를 영구적으로 저장하지 않는다. 일시적인 저장에 사용한다.</p>
<p>  → 앱 실행중에 메모리에 저장된 데이터들은 앱을 종료하면 함께 삭제된다. (휘발된다)</p>
<p>  → 앱도 결국 데이터 덩어리이기 때문에, 실행을 시키면 메모리에 올라간다.</p>
<p>  → 그렇기 때문에 메모리에 저장된 데이터는 앱이 메모리에서 내려올 때 같이 내려오게 되는 것.</p>
<p>  → RAM 의 용량이 클 수록, 동시에 실행시킬 수 있는 앱의 총량이 높아진다고 생각할 수 있다.</p>
</li>
<li><p>디스크보다 속도가 빠르다. (CPU 가 디스크보다 메모리에 더 빨리 접근할 수 있다.)</p>
</li>
<li><p>디스크에 비해 용량이 작다. (보통 8GB, 16GB, 32GB)</p>
</li>
<li><p>EEPROM 과 같은 비휘발성 메모리도 있다. 아이폰은 이곳에 장치의 일련번호 및 하드웨어 정보를 저장한다.</p>
</li>
</ul>
<hr>
<p>▪️ <strong>디스크</strong></p>
<ul>
<li><p>영구적인 데이터를 저장하는 곳. 비휘발성 장치.</p>
<p>  → 앱 실행중에 디스크에 저장된 데이터들은 앱을 종료해도 디스크에 남는다.</p>
</li>
<li><p>파일, 문서, 프로그램 등 상대적으로 용량이 큰 정보들을 담을 수 있다.</p>
</li>
<li><p>메모리에 비해 속도가 느리다.</p>
</li>
<li><p><code>UserDefaults</code>, <code>CoreData</code> 를 활용해서 디스크에 데이터를 저장할 수 있다.</p>
</li>
</ul>
<p><del>💻 <strong>다음 주어진 상황들에서, 메모리를 활용하는게 좋을지 디스크를 활용하는게 좋을지 생각해봅시다.</strong></del> (추후 수정 예정)</p>
<p><del>1. 전화번호부 앱에서 친구의 이름, 전화번호 정보 데이터.
2. 카운터 앱 개발할 때 사용했던 <code>number</code> 변수.
3. 스테이지가 있는 게임 앱에서 유저가 몇 스테이지까지 클리어했는지 정보.
4. 유튜브나 인스타그램 같은 SNS 앱에서 추천 창에 뜬 이미지와 동영상 정보들.</del></p>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/679b8a36-56ef-4176-b0b9-07f895d52b4e/image.png" alt=""></p>
<p>💻 비유와 함께 개발할 때 메모리 관리가 왜 중요한지 이해해봅시다.</p>
<ul>
<li>일류 요리사가 있습니다.</li>
<li>조리대에 수많은 식재료들이 있고, 열심히 요리를 하고 있습니다.</li>
<li>조리대의 크기가 클수록 한꺼번에 올려놓을 수 있는 식재료의 양이 많아지겠죠.</li>
<li>조리대의 크기는 메모리 (RAM) 의 크기에 비유해서 생각할 수 있습니다. RAM 의 크기가 클 수록 한꺼번에 올려놓고 처리할 수 있는 데이터의 양이 많아지는 것과 같습니다. 요리사는 CPU 입니다.</li>
<li>매우 넓은 조리대가 있고, 요리사는 열심히 요리를 하느라 정신이 없어서, 더 이상 사용하지 않는 식재료를 정리하면서 동시에 요리하기가 힘듭니다.</li>
<li>하지만 더 이상 필요하지 않은 식재료가 조리대의 자리를 차지하면 정작 필요한 재료들을 새로 올려놓을 때 좋지 않겠죠.</li>
<li>하지만 더 이상 필요하지 않은 식재료가 조리대의 자리를 차지하면 정작 필요한 재료들을 새로 올려놓을 때 좋지 않겠죠.</li>
<li>이때 센스있는 조교가 나타납니다. 이 조교는 조리대를 슥 훑어보고 “지금부터 양배추와 토마토는 더 이상 사용하지 않는군.” 하고 알아서 조리대에서 양배추와 토마토를 정리해줍니다.</li>
<li>그렇게 함으로써 요리사는 조리대를 더 효율적으로 사용할 수 있게 됩니다.</li>
<li>이 조교는 Garbage Collector (가비지 컬렉터) 입니다.</li>
</ul>
<hr>
<p>💻 Garbage Collector
<img src="https://velog.velcdn.com/images/nemo_/post/94ca31ea-bdb1-420f-9b57-9836554c2445/image.png" alt=""></p>
<p><strong>Garbage Collector</strong> 는 직역하면 쓰레기 청소부. 메모리에서 필요없는 것들을 정리해주는 역할을 합니다. 사용하지 않는 데이터들이 메모리에 올라와 공간을 차지하고 있는 것은 매우 비효율적입니다. 따라서 좋은 개발자는 메모리 관리를 신경써서 잘 할 줄 알아야 합니다.</p>
<p>사용하지 않는 메모리가 쌓이고 쌓여서 메모리에 부담이 되는 상황을 <strong>메모리 누수 (Memory Leak)</strong> 이라고 합니다.</p>
<p><strong>Java</strong> 에서는 개발자가 직접 명시적으로 메모리 관리를 하지 않더라도 기본적으로 메모리 관리를 돕는 GC 라는 시스템이 있습니다.</p>
<p><strong>GC 가 동작하는 방식</strong>을 간단하게만 소개하면, <strong>런타임</strong>에 메모리 영역을 슥 훑어보며 사용중인 것들을 표시 (Mark) 하고, 표 되지 않은 모든 것들을 정리해버리는 Mark-and-Sweep 방식을 사용합니다. </p>
<p>🤔 왜 갑자기 Java 의 메모리 관리 시스템을 공부하나요?</p>
<p>일반적인 메모리 관리 개념의 근간이 되는 시스템이며, 개발자라면 필수 교양으로 알아야 하는 내용이기 때문에 간단하게 공부해보았습니다.</p>
<hr>
<blockquote>
<h3 id="reference-counting">Reference Counting</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nemo_/post/82eb11c8-caa2-41dd-9544-ee77d8128913/image.png" alt=""></p>
<p>메모리를 할당 받은 객체를 인스턴스라고 합니다. </p>
<p>예를들어 아래 코드에서 myClass 는 인스턴스가 된 것이죠.</p>
<pre><code class="language-swift">class MyClass {}

// 메모리를 할당받음. 인스턴스.
let myClass = MyClass()</code></pre>
<p>인스턴스는 하나 이상의 참조자(소유자=owner) 가 있어야 메모리에 유지가 됩니다. 소유자가 없다면 즉시 메모리에서 제거가 됩니다. 이때 인스턴스를 참조하고 있는 소유자의 개수를 <code>reference count</code> 라고 합니다.</p>
<p><code>reference count &gt; 0</code> 이면 메모리에 살아있고, <code>reference count = 0</code>  이면 메모리에서 삭제됩니다.</p>
<p>그렇기 때문에, 더 이상 사용하지 않을 인스턴스의 reference coutn 가 0보다 크지 않도록 주의를 해야합니다.</p>
<pre><code class="language-swift">class MyClass {
    init() {
        print(&quot;MyClass 생성&quot;)
    }
    deinit {
        print(&quot;MyClass 소멸&quot;)
    }
}

// RC = 1
var myClass: MyClass? = MyClass()

// RC = 2
var myClass2 = myClass

// RC = 2-1 = 1
myClass = nil</code></pre>
<p><del>오늘의 스스로 숙제!
클래스의 deinit 소멸자 메서드는 메모리에서 해제될때 호출됩니다. 위 코드에서 deinit 의 프린트가 찍힐까? Reference Count 의 개념을 생각하며 결과에 대한 이유를 생각해보자</del></p>
<hr>
<blockquote>
<h3 id="arc-와-mrc">ARC 와 MRC</h3>
</blockquote>
<ul>
<li><p>ARC = Automatic Reference Counting</p>
</li>
<li><p>MRC = Manual Reference Counting</p>
</li>
<li><p><strong>ARC</strong></p>
<ul>
<li>ARC 는 Swift 의 메모리 관리 시스템. Java 에 GC 가 있다면 Swift 에는 ARC 가 있음.</li>
<li>Reference Count 를 자동으로 계산. (Automatic)<ul>
<li>객체가 생성될 때 RC 가 1 로 설정</li>
<li>객체가 다른 변수나 속성에 할당되어 참조될때마다 RC 가 1 씩 증가</li>
<li>객체에 대한 참조가 해제될때마다 RC 가 감소</li>
<li>RC 0 이 되면 더 이상 사용되지 않는 것으로 간주되어 메모리에서 해제.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>MRC</strong></p>
<ul>
<li>MRC 는 Objective-C 에서 사용하는 메모리 관리 시스템.</li>
<li>Reference Count 를 개발자가 코드로 직접 계산. (Manual)<ul>
<li>객체가 생성될때 개발자가 명시적으로 메모리 할당</li>
<li>객체를 다른 변수나 속성에 할당되어 참조될때마다 개발자가 명시적으로 RC 증가</li>
<li>객체에 대한 참조가 해제될때마다 개발자가 명시적으로 RC 감소</li>
<li>RC 가 0 이되면 개발자가 명시적으로 메모리에서 해제.</li>
</ul>
</li>
</ul>
<p>🙋🏻‍♂️ 그렇다면 ARC 는 자동으로 RC 카운트를 해서 메모리 관리를 해주는 좋은 시스템이니, 개발자는 메모리 관리에 대해 신경쓰지 않아도 되나요?</p>
</li>
</ul>
<p>→ 그렇지 않습니다. ARC 로 잡아내지 못하는 메모리 누수 상황이 발생할 수 있기 때문에, 개발자는 메모리 관리 방법을 반드시 알아야 합니다.</p>
<blockquote>
<h3 id="약참조와-강참조">약참조와 강참조</h3>
</blockquote>
<ul>
<li><p><strong>약참조</strong></p>
<ul>
<li>Reference Count 를 증가시키지 않으면서 참조하는 것.</li>
<li>weak 키워드를 붙여서 약참조를 할 수 있다.</li>
</ul>
<ul>
<li><strong>강참조</strong><ul>
<li>Reference Count 를 증가시키면서 참조하는 것.</li>
<li>일반적인 참조 방식을 말한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<h3 id="클로저의-캡처링-개념">클로저의 캡처링 개념</h3>
</blockquote>
<h4 id="swift-의-클로저-안에서-값을-사용하는-방법-중에는-캡처링-이-있습니다">Swift 의 클로저 안에서 값을 사용하는 방법 중에는 캡처링 이 있습니다.</h4>
<pre><code class="language-swift">class Adam {
    let mbti = &quot;ENTJ&quot;
    init() {
        print(&quot;클래스 생성&quot;)
    }
    deinit {
        print(&quot;클래스 소멸&quot;)
    }
}

// adam rc = 1
var adam: Adam? = Adam()

// 클로저 내부에서 adam 캡처. rc 1 증가. adam rc = 2
let printMbti: () -&gt; () = { [adam] in
    guard let adam else { return }
    print(&quot;adam&#39;s mbti = \(adam.mbti)&quot;)
}

printMbti()

// adam rc = 2-1 = 1
adam = nil</code></pre>
<p><code>printMbti</code> 라는 클로저를 선언했고, 클로저 내부에서 클로저 외부의 <code>adam</code> 이라는 객체를 가져다 쓰고 싶으면 값을 <code>캡처</code> 해야합니다. 이때 <code>[ ]</code> 로 감싸면 값을 캡처링해서 클로저 내부에서 사용할 수 있게 됩니다.</p>
<p><strong>🌟 클로저 내부에서 클래스의 값을 캡처하면, Reference Count 가 증가합니다.</strong></p>
<p>위 예시를 따라해보면 <code>adam</code> 의 <code>deinit</code> 소멸자가 호출되지 않습니다. 클로저에서 값을 캡처해 rc 가 증가했기 때문입니다.</p>
<p>위 코드를 개선해서, 메모리 누수가 발생하지 않는 상황을 만들려면 다음과 같이 코드를 작성해야 합니다.</p>
<pre><code class="language-swift">class Adam {
    let mbti = &quot;ENTJ&quot;
    init() {
        print(&quot;클래스 생성&quot;)
    }
    deinit {
        print(&quot;클래스 소멸&quot;)
    }
}

// adam rc = 1
var adam: Adam? = Adam()

// 클로저 내부에서 adam 캡처.
// weak 참조 했으므로 rc 가 증가하지 않음. adam rc = 1
let printMbti: () -&gt; () = { [weak adam] in
    guard let adam else { return }
    print(&quot;adam&#39;s mbti = \(adam.mbti)&quot;)
}

printMbti()

// adam rc = 1-1 = 0
adam = nil</code></pre>
<p>위 코드를 따라서 실행해보면, 메모리가 해제되고 <code>deinit</code> <code>”클래스 소멸”</code> 이 호출되는 것을 확인할 수 있습니다.</p>
<hr>
<blockquote>
<h3 id="순환-참조-circular-reference">순환 참조 (Circular Reference)</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nemo_/post/c688922f-3264-4100-acc8-745304c12f28/image.png" alt="">
A 가 B 를 참조하고 (A→B),
B 가 A 를 참조해서 (B→A), 서로가 서로를 참조하는 상황을 순환 참조라고합니다.
일반적으로 순환 참조는 메모리 누수를 발생시키는 대표적인 사례입니다.
아래 예시를 보고 순환 참조 개념을 이해해봅시다.</p>
<pre><code class="language-swift">class Person {
    var pet: Dog?
    init() {
        print(&quot;Person 클래스 생성&quot;)
    }
    deinit {
        print(&quot;Person 클래스 소멸&quot;)
    }
}

class Dog {
    var owner: Person?
    init() {
        print(&quot;Dog 클래스 생성&quot;)
    }
    deinit {
        print(&quot;Dog 클래스 소멸&quot;)
    }
}

// person rc = 1
var person: Person? = Person()
// dog rc = 1
var dog: Dog? = Dog()

// dog rc = 2
person?.pet = dog
// person rc = 2
dog?.owner = person

// person rc = 1
person = nil
// dog rc = 1
dog = nil</code></pre>
<p><strong>→ 따라서, 개발자는 개발할 때 순환참조가 발생하는 상황이 아닌지 점검할 줄 알아야 합니다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ViewController 생명주기]]></title>
            <link>https://velog.io/@nemo_/ViewController-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0</link>
            <guid>https://velog.io/@nemo_/ViewController-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0</guid>
            <pubDate>Mon, 02 Dec 2024 11:01:45 GMT</pubDate>
            <description><![CDATA[<hr>
<blockquote>
<h3 id="uiviewcontroller-개념">UIViewController 개념</h3>
</blockquote>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/aba7461c-e176-4e90-bacb-35e34f970e8b/image.png" alt=""></p>
<blockquote>
<p>UIKit 앱의 뷰 계층을 관리하는 개체입니다.</p>
</blockquote>
<ul>
<li>한 개의 페이지는 반드시 한 개의 <code>UIViewController</code> 를 가짐.</li>
<li><code>UIViewController</code> 내부에 <code>UIView</code>, <code>UIButton</code>, <code>UIScrollView</code> 등 UIKit 의 UI 클래스들을 배치하며 화면을 구성한다.
<a href="https://developer.apple.com/documentation/uikit/uiviewcontroller">링크텍스트</a></li>
</ul>
<blockquote>
<p>ViewController 생명주기</p>
</blockquote>
<hr>
<ul>
<li><strong>iOS 의 대표적인 생명주기 2가지</strong><ol>
<li>앱 생명주기 (<code>App Lifecycle</code>) → [1-3. Xcode 빌드] 강의에서 공부했음.</li>
<li>ViewController 생명주기 (<code>ViewController Lifecycle</code>)</li>
</ol>
</li>
</ul>
<ul>
<li>ViewController Lifecycle
<img src="https://velog.velcdn.com/images/nemo_/post/3c0ffa0f-f6d8-4e7b-9330-db90b60621ea/image.png" alt=""></li>
</ul>
<p><img src="https://velog.velcdn.com/images/nemo_/post/ab2f3bc6-a6b4-4fde-bad1-06b45ecc2bd7/image.png" alt=""></p>
<blockquote>
<p>ViewController Lifecycle 메서드 호출 확인</p>
</blockquote>
<hr>
<p>▪️ ViewController 에서 버튼을 클릭하면 AdamViewController 로 이동하도록 구현합니다.</p>
<ul>
<li>ViewController 의 라이프사이클 메서드에 print() 문으로 로그를 남기고, 직접 어떤 타이밍에 어떤 라이프사이클이 호출되는지 확인합니다.</li>
</ul>
<h4 id="1-viewdidload-viewdidappear">1) viewDidLoad ~viewDidAppear</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/9ed11167-3eed-444f-b3cb-2e3cb9a5d42f/image.png" alt=""></p>
<h4 id="2-viewwilldisappear--viewdiddisappear">2) viewWillDisappear ~ viewDidDisappear</h4>
<p>  <img src="https://velog.velcdn.com/images/nemo_/post/d6ca7cc5-9139-4248-8507-da5f8b9a539a/image.png" alt=""></p>
<h4 id="3-viewwillappear--viewdidappear">3) viewWillAppear ~ viewDidAppear</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/63369a03-0796-470b-bb15-34a44f37b30c/image.png" alt=""></p>
<h4 id="scenedelegateswift">SceneDelegate.swift</h4>
<pre><code class="language-swift">//
//  SceneDelegate.swift
//

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UINavigationController(rootViewController: ViewController())
        window.makeKeyAndVisible()
        self.window = window
    }
    // ...
}</code></pre>
<h4 id="viewcontrollerswift">ViewController.swift</h4>
<p><del>NSLayoutConstraint 로 구현</del></p>
<pre><code class="language-swift">//
//  ViewController.swift
//

import UIKit

class ViewController: UIViewController {

    let button = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
        print(&quot;viewDidLoad&quot;)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print(&quot;viewWillAppear&quot;)
    }

    override func viewIsAppearing(_ animated: Bool) {
        super.viewIsAppearing(animated)
        print(&quot;viewIsAppearing&quot;)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        print(&quot;viewDidAppear&quot;)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        print(&quot;viewWillDisappear&quot;)
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        print(&quot;viewDidDisappear&quot;)
    }

    private func configureUI() {
        view.addSubview(button)
        view.backgroundColor = .white

        button.setTitle(&quot;페이지 이동&quot;, for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchDown)
        button.backgroundColor = .red
        button.setTitleColor(.white, for: .normal)
        button.titleLabel?.font = .boldSystemFont(ofSize: 30)
        button.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            button.widthAnchor.constraint(equalToConstant: 200),
            button.heightAnchor.constraint(equalToConstant: 120),
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    @objc
    private func buttonTapped() {
        self.navigationController?.pushViewController(AdamViewController(), animated: true)
    }
}</code></pre>
<h4 id="adamviewcontrollerswift">AdamViewController.swift</h4>
<pre><code class="language-swift">//
//  AdamViewController.swift
//
//

import UIKit

class AdamViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .orange
        print(&quot;AdamViewController viewDidLoad&quot;)
    }
}</code></pre>
<h4 id="viewcontroller-를-볼때마다-색상을-랜덤하게-변경하기">ViewController 를 볼때마다 색상을 랜덤하게 변경하기</h4>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/71912688-2769-4478-b622-de6f19e64ee8/image.png" alt=""></p>
<pre><code class="language-swift">override func viewWillAppear(_ animated: Bool) {
    print(&quot;viewWillAppear&quot;)

    self.view.backgroundColor = UIColor(
        red: .random(in: 0...1),
        green: .random(in: 0...1),
        blue: .random(in: 0...1),
        alpha: 1.0
    )

    self.button.backgroundColor = UIColor(
        red: .random(in: 0...1),
        green: .random(in: 0...1),
        blue: .random(in: 0...1),
        alpha: 1.0
    )
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[카운터 앱 개발]]></title>
            <link>https://velog.io/@nemo_/%EC%B9%B4%EC%9A%B4%ED%84%B0-%EC%95%B1-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@nemo_/%EC%B9%B4%EC%9A%B4%ED%84%B0-%EC%95%B1-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Fri, 22 Nov 2024 06:07:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="앱-요구사항">앱 요구사항</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nemo_/post/0d182bb7-087a-4c85-8028-92e90ade3a0b/image.png" alt=""></p>
<hr>
<h4 id="카운터-앱-개발하기">카운터 앱 개발하기</h4>
<p><del>앱의 요구사항</del></p>
<ol>
<li>숫자를 띄울 라벨</li>
</ol>
<table>
<thead>
<tr>
<th>속성</th>
<th align="left">요구사항</th>
</tr>
</thead>
<tbody><tr>
<td>숫자라벨</td>
<td align="left">Int 형.0 부터 시작</td>
</tr>
<tr>
<td>textColor</td>
<td align="left">white</td>
</tr>
<tr>
<td>font</td>
<td align="left">boldSystem 폰트. size = 45</td>
</tr>
<tr>
<td>textAlignment</td>
<td align="left">center</td>
</tr>
<tr>
<td>width</td>
<td align="left">80</td>
</tr>
<tr>
<td>constraint</td>
<td align="left">superView와 center가 갖게 설정.</td>
</tr>
</tbody></table>
<ol start="2">
<li>감소, 증가 버튼</li>
</ol>
<table>
<thead>
<tr>
<th align="center">속성</th>
<th align="center">요구사항</th>
</tr>
</thead>
<tbody><tr>
<td align="center">backgroundColor</td>
<td align="center">감소 버튼은 red, 증가 버튼은 blue.</td>
</tr>
<tr>
<td align="center">textColor</td>
<td align="center">white</td>
</tr>
<tr>
<td align="center">width</td>
<td align="center">80</td>
</tr>
<tr>
<td align="center">height</td>
<td align="center">30</td>
</tr>
<tr>
<td align="center">cornerRadius</td>
<td align="center">8</td>
</tr>
<tr>
<td align="center">constraint</td>
<td align="center">centerY 는 모두 숫자 라벨과 같게 설정. 감소 버튼은 라벨로부터 왼쪽으로 32 떨어지게 설정. 증가 버튼은 라벨로부터 오른쪽으로 32 떨어지게 설정.</td>
</tr>
</tbody></table>
<ol start="3">
<li>증가 버튼을 누르면 숫자가 +1 , 감소 버튼을 누르면 숫자가 -1 되어 보이도록 구현.</li>
</ol>
<h3 id="스토리보드로-카운터-앱-개발">스토리보드로 카운터 앱 개발</h3>
<hr>
<pre><code class="language-swift">import UIKit

class ViewController: UIViewController {

    private var number: Int = 0
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var minusButton: UIButton!
    @IBOutlet weak var plusButton: UIButton!
    @IBAction func minusButtonTapped(_ sender: Any) {
        self.number -= 1
        label.text = &quot;\(self.number)&quot;
    }
    @IBAction func plusButtonTapped(_ sender: Any) {
        self.number += 1
        label.text = &quot;\(self.number)&quot;
    }
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}</code></pre>
<h3 id="코드베이스로-카운터-앱-개발">코드베이스로 카운터 앱 개발</h3>
<hr>
<p><del>SnapKit 활용</del></p>
<pre><code class="language-swift">import UIKit
import SnapKit

class ViewController: UIViewController {

    private var number: Int = 0
    let label = UILabel()
    let minusButton = UIButton()
    let plusButton = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .black
        label.text = &quot;\(number)&quot;
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 45)
        label.textAlignment = .center
        minusButton.setTitle(&quot;감소&quot;, for: .normal)
        minusButton.backgroundColor = .red
        minusButton.setTitleColor(.white, for: .normal)
        minusButton.layer.cornerRadius = 8
        minusButton.addTarget(self, action: #selector(minusButtonTapped), for: .touchDown)
        plusButton.setTitle(&quot;증가&quot;, for: .normal)
        plusButton.backgroundColor = .blue
        plusButton.setTitleColor(.white, for: .normal)
        plusButton.layer.cornerRadius = 8
        plusButton.addTarget(self, action: #selector(plusButtonTapped), for: .touchDown)

        [label, minusButton, plusButton]
            .forEach { view.addSubview($0) }

        label.snp.makeConstraints {
            $0.width.equalTo(80)
            $0.center.equalToSuperview()
        }

        minusButton.snp.makeConstraints {
            $0.centerY.equalTo(label.snp.centerY)
            $0.width.equalTo(60)
            $0.height.equalTo(30)
            $0.trailing.equalTo(label.snp.leading).offset(-32)
        }

        plusButton.snp.makeConstraints {
            $0.centerY.equalTo(label.snp.centerY)
            $0.width.equalTo(60)
            $0.height.equalTo(30)
            $0.leading.equalTo(label.snp.trailing).offset(32)
        }
    }

    @objc
    private func minusButtonTapped() {
        self.number -= 1
        label.text = &quot;\(number)&quot;
    }

    @objc
    private func plusButtonTapped() {
        self.number += 1
        label.text = &quot;\(number)&quot;
    }
}
</code></pre>
<hr>
<h3 id="응용---초기화-버튼-만들기">응용 - 초기화 버튼 만들기</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/63dff3c4-f087-425a-9977-01797b5f0bea/image.png" alt=""></p>
<pre><code class="language-swift">import UIKit
import SnapKit

class ViewController: UIViewController {

    private var number: Int = 0
    let label = UILabel()
    let minusButton = UIButton()
    let plusButton = UIButton()
    let resetButton = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .black
        label.text = &quot;\(number)&quot;
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 45)
        label.textAlignment = .center
        minusButton.setTitle(&quot;감소&quot;, for: .normal)
        minusButton.backgroundColor = .red
        minusButton.setTitleColor(.white, for: .normal)
        minusButton.layer.cornerRadius = 8
        minusButton.addTarget(self, action: #selector(minusButtonTapped), for: .touchDown)
        plusButton.setTitle(&quot;증가&quot;, for: .normal)
        plusButton.backgroundColor = .blue
        plusButton.setTitleColor(.white, for: .normal)
        plusButton.layer.cornerRadius = 8
        plusButton.addTarget(self, action: #selector(plusButtonTapped), for: .touchDown)
        resetButton.setTitle(&quot;초기화&quot;, for: .normal)
        resetButton.backgroundColor = .gray
        resetButton.setTitleColor(.white, for: .normal)
        resetButton.layer.cornerRadius = 8
        resetButton.addTarget(self, action: #selector(resetButtonTapped), for: .touchDown)

        [label, minusButton, plusButton, resetButton]
            .forEach { view.addSubview($0) }

        label.snp.makeConstraints {
            $0.width.equalTo(80)
            $0.center.equalToSuperview()
        }

        minusButton.snp.makeConstraints {
            $0.centerY.equalTo(label.snp.centerY)
            $0.width.equalTo(60)
            $0.height.equalTo(30)
            $0.trailing.equalTo(label.snp.leading).offset(-32)
        }

        plusButton.snp.makeConstraints {
            $0.centerY.equalTo(label.snp.centerY)
            $0.width.equalTo(60)
            $0.height.equalTo(30)
            $0.leading.equalTo(label.snp.trailing).offset(32)
        }

        resetButton.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.width.equalTo(80)
            $0.height.equalTo(30)
            $0.top.equalTo(label.snp.bottom).offset(100)
        }
    }

    @objc
    private func minusButtonTapped() {
        self.number -= 1
        label.text = &quot;\(number)&quot;
    }

    @objc
    private func plusButtonTapped() {
        self.number += 1
        label.text = &quot;\(number)&quot;
    }

    @objc
    private func resetButtonTapped() {
        self.number = 0
        label.text = &quot;\(number)&quot;
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코드 베이스 UI]]></title>
            <link>https://velog.io/@nemo_/%EC%BD%94%EB%93%9C-%EB%B2%A0%EC%9D%B4%EC%8A%A4-UI</link>
            <guid>https://velog.io/@nemo_/%EC%BD%94%EB%93%9C-%EB%B2%A0%EC%9D%B4%EC%8A%A4-UI</guid>
            <pubDate>Thu, 21 Nov 2024 14:26:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="스토리보드-vs-코드베이스">스토리보드 vs 코드베이스</h3>
</blockquote>
<ul>
<li><p>코드베이스란 스토리보드 같은 인터페이스 빌더를 사용하지 않고, 코드 작성으로만 UI 구성을 하는 것.</p>
</li>
<li><p>스토리보드에서 하던 뷰의 <code>size</code>, <code>constraint</code>  및 모든 속성들을 코드로 작성한다.</p>
</li>
<li><p>팀끼리 협업할 때, github에 코드를 올리고 서로 코드 리뷰를 하게 되는데, 스토리보드로 작성한 UI 를 github에 올린 것보다, swift로 작성한 UI 코드를 github에 올린 것이 가독성이 더 좋음.</p>
</li>
<li><p>다만 스토리보드처럼 눈에 보이는 상태로 UI를 구성하는 것이 아니기 때문에, 코드 베이스로 UI를 작성하면 반드시 실행시켜서 확인해야 한다는 단점이 존재
<img src="https://velog.velcdn.com/images/nemo_/post/dfad055d-cbd8-4c60-8daf-9ad6329c3666/image.png" alt=""></p>
</li>
<li><p>좌: 스토리보드 UI를 github에 올린 모습.</p>
</li>
<li><p>우: 코드베이스 UI를 github에 올린 모습.</p>
<p>  → Swift로 UI가 작성되었기 때문에 코드 파악하기가 용이.</p>
</li>
</ul>
<blockquote>
<h4 id="스토리보드-삭제">스토리보드 삭제</h4>
</blockquote>
<p>-&gt; 스토리보드를 일절 사용하지 않고, 코드만으로 UI를 구성하기 위해 프로젝트에서 스토리보드를 완전히 삭제합니다.</p>
<ol>
<li><p>Main이라는 이름의 스토리보드 삭제. Move to Trash로 삭제할 것.
<img src="https://velog.velcdn.com/images/nemo_/post/5789017b-eaac-4f2e-9a87-b37a5f44d6b9/image.png" alt=""></p>
</li>
<li><p>into.plist라는 파일에서 ctrl+F로 검색 → main 검색해서 Storyboard Name 항목 삭제.
<img src="https://velog.velcdn.com/images/nemo_/post/8b9d8243-8c2f-4bce-a411-eae451b1cb5e/image.png" alt=""></p>
</li>
<li><p>프로젝트 파일에서 TARGETS 선택 후 → Build Settings로 이동 → ctrl+F로 main 검색 → UIKit Main Storyboard File Base Name 항목 삭제.
<img src="https://velog.velcdn.com/images/nemo_/post/4e3b2b0f-c256-42c3-9bd6-6d8123665626/image.png" alt=""></p>
</li>
<li><p>앱에게 맨 처음 시작할 뷰를 알려줘야 하므로 SceneDelegate.swift 파일에 다음과 같은 코드 작성.
<img src="https://velog.velcdn.com/images/nemo_/post/4fe38159-0ff7-4807-b583-525306b06185/image.png" alt=""></p>
</li>
</ol>
<h4 id="코드-및-설명">코드 및 설명.</h4>
<pre><code class="language-swift">// SceneDelegate.swift

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
        // 윈도우. 앱에 반드시 한 개는 필요한 가장 근본이 되는 뷰. 이 위에 뷰가 쌓이기 시작.
    var window: UIWindow?

        // 앱을 시작할때 세팅해줄 코드를 작성하는 곳.
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // UIWindow 객체 생성.
        guard let windowScene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: windowScene)

        // window 에게 루트 뷰 지정. 
        window.rootViewController = ViewController()

         // 이 메서드를 반드시 작성해줘야 윈도우가 활성화 됨.
        window.makeKeyAndVisible()
        self.window = window
    }</code></pre>
<ol start="5">
<li>실행해 봤을때 컴파일 에러 없이 잘 실행된다면 스토리보드 삭제 및 코드 베이스 UI 작성 준비 완료.</li>
</ol>
<blockquote>
<h3 id="nslayoutconstraint">NSLayoutConstraint</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nemo_/post/b014c6e8-3217-4aa0-89ee-3fbdf9e54bf4/image.png" alt=""></p>
<ul>
<li>NSLayoutConstraint로 Constraint 작성 가능.</li>
<li>Constraint는 뷰와 뷰 사이의 제약조건을 의미. 오토 레이아웃에서 중요한 개념.</li>
<li><code>leadingAnchor</code> <code>trailingAnchor</code> <code>topAnchor</code> <code>bottomAnchor</code> <code>widthAnchor</code> <code>heightAnchor</code></li>
<li><code>centerXAnchor</code> : 뷰에 가로선 그었을 때 그 중간을 의미.</li>
<li><code>centerYAnchor</code> : 뷰에 세로선 그었을 때 그 중간을 의미.</li>
<li><code>NSLayoutConstraint.activate([제약조건들])</code> : 제약조건들을 넣고 코드 작성하면 제약조건 활성화.</li>
</ul>
<blockquote>
<h3 id="uilabel">UILabel</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nemo_/post/202e0aa8-a72c-41b1-b52d-e14f187622b8/image.png" alt=""></p>
<p>▪️ <strong>NSLayoutConstraint를 이용해서 UILabel 그려보기.</strong></p>
<ul>
<li><code>let label = UILabel()</code> 코드로 라벨을 선언.</li>
<li><code>configureUI()</code>라는 메서드를 정의하고, 그 안에 UI를 세팅하는 코드들을 담습니다.</li>
<li><code>view</code>는 ViewController가 기본적으로 갖고 있는 기본 view를 의미.</li>
<li>view에 label을 추가해야 합니다. <code>view.addSubview(label)</code></li>
<li>코드로 여러 가지 속성들을 부여. <code>text</code>, <code>textColor</code> 등.</li>
<li><code>label.translatesAutoresizingMaskIntoConstraints = false</code> 이 코드는 오토 레이아웃을 활성화시키기 위해 작성 필요.</li>
<li><code>NSLayoutConstraint.activate</code> 안에 제약조건들을 작성.<pre><code class="language-swift">import UIKit
</code></pre>
</li>
</ul>
<p>class ViewController: UIViewController {</p>
<pre><code>let label = UILabel()

override func viewDidLoad() {
    super.viewDidLoad()
    configureUI()
}

private func configureUI() {
    view.backgroundColor = .white
    label.text = &quot;안녕하세요&quot;
    label.textColor = .black
    label.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(label)
    NSLayoutConstraint.activate([
        label.widthAnchor.constraint(equalToConstant: 80),
        label.heightAnchor.constraint(equalToConstant: 40),
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    ])
}</code></pre><p> }</p>
<pre><code>&gt;### UIButton

![](https://velog.velcdn.com/images/nemo_/post/fba20805-c7aa-40f4-84fb-f4187ef8db93/image.png)

▪️ **NSLayoutConstraint를 이용해서 UIButton 그려보기.**

- `let button = UIButton()` 코드로 버튼을 선언.
- `setTitle`, `setTitleColor`, `backgroundColor` 등 속성을 코드로 부여.
- 버튼뿐 아니라 모든 뷰들의 테두리를 둥글게 하고 싶다면 `layer.cornerRadius` 속성 사용.
```swift
import UIKit

class ViewController: UIViewController {

    let button = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .white
        // 버튼 타이틀 지정.
        button.setTitle(&quot;Click&quot;, for: .normal)
        // 버튼 타이틀 컬러 지정.
        button.setTitleColor(.white, for: .normal)
        // 버튼 색상 지정.
        button.backgroundColor = .red
        button.translatesAutoresizingMaskIntoConstraints = false
            // 버튼 테두리 둥글게 지정.
        button.layer.cornerRadius = 10

        view.addSubview(button)
        NSLayoutConstraint.activate([
            button.widthAnchor.constraint(equalToConstant: 120),
            button.heightAnchor.constraint(equalToConstant: 60),
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
 }</code></pre><blockquote>
<h3 id="uiimageview">UIImageView</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nemo_/post/42039e75-adeb-42c7-9c2f-f74e8086a795/image.png" alt=""></p>
<p>▪️ <strong>NSLayoutConstraint를 이용해서 UIImageView 그려보기.</strong></p>
<ul>
<li><code>let imageView = UIImageView()</code> 코드로 이미지 뷰를 선언.</li>
<li><code>image</code>, <code>backgroundColor</code>, <code>contentMode</code> 등 속성을 코드로 부여.</li>
<li>전에 배웠듯, 미리 이미지 파일을 프로젝트에 추가해야 함.<pre><code class="language-swift">import UIKit
</code></pre>
</li>
</ul>
<p>class ViewController: UIViewController {</p>
<pre><code>let imageView = UIImageView()

override func viewDidLoad() {
    super.viewDidLoad()
    configureUI()
}

private func configureUI() {
    view.backgroundColor = .white
    imageView.image = UIImage(named: &quot;cat&quot;)
    imageView.backgroundColor = .black
    imageView.contentMode = .scaleAspectFit
    imageView.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(imageView)
    NSLayoutConstraint.activate([
        imageView.widthAnchor.constraint(equalToConstant: 300),
        imageView.heightAnchor.constraint(equalToConstant: 300),
        imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    ])
}</code></pre><p>}</p>
<pre><code>&gt;### SnapKit

![](https://velog.velcdn.com/images/nemo_/post/84f818c1-5d3a-4a31-aefd-97c2d8df49ae/image.png)
SnapKit 이란?

SnapKit 이란 코드베이스로 UI를 작성할 때, 조금 더 간결한 문법을 사용하도록 도와주는 서드파티 라이브러리. NSLayoutConstraint 을 사용할 때보다 편하게 코드를 작성할 수 있게 도와준다.
🧑🏻‍💻 현업에서 가장 많이 사용하는 필수 라이브러리 중 하나.

&gt;### Swift Package Manager (SPM)

![](https://velog.velcdn.com/images/nemo_/post/df72365d-9bad-4d41-8073-306cb64eba86/image.png)
- `Swift Package Manager` 란, 프로젝트에 서드파티 라이브러리를 가져와서 사용할 수 있도록 도와주는 도구.
- `Cocoapods`, `Carthage` 등 다른 도구도 있지만, `SPM` 이 애플에서 지원하는 퍼스트 파티 도구.
- `SPM` 을 통해서 `SnapKit` 을 프로젝트에 추가해 봅시다.

**&lt;프로젝트에 SPM으로 서드파티 라이브러리 추가하는 방법&gt;**

1. 프로젝트 파일에서 TARGETS 선택 → General → Frameworks, Libraries 영역에서 십자(+) 버튼 클릭
![](https://velog.velcdn.com/images/nemo_/post/f3d40322-df5b-4ef9-938b-17b4d63d0fb6/image.png)

2. Add Other… 클릭 후 Add Package Dependency 클릭.
![](https://velog.velcdn.com/images/nemo_/post/1da6fd83-aeca-40f9-ab27-635bc5032831/image.png)

3. 구글에 사용할 라이브러리 검색 → github 들어가서 Code → HTTPS 복사.
![](https://velog.velcdn.com/images/nemo_/post/ad02468e-5281-43bd-9760-7f3cebf39ced/image.png)

4. 검색창에 사용할 라이브러리의 주소 붙여 넣기 → Add Package.
![](https://velog.velcdn.com/images/nemo_/post/df9f9952-4427-4cb5-8e47-c718b381484c/image.png)

5. 1. 네비게이터 영역에 Package Dependencies에 SnapKit 생긴 것 확인. 코드에 `import SnapKit` 이 가능해진 것 확인.
![](https://velog.velcdn.com/images/nemo_/post/ce94868b-b275-438e-8b64-b2e28a6b8ffd/image.png)

[링크텍스트](https://github.com/SnapKit/SnapKit)

&gt;### UILabel

![](https://velog.velcdn.com/images/nemo_/post/7ea21ec1-27d7-4fde-a364-ac283b98bd80/image.png)

▪️ **SnapKit을 이용해서 UILabel 그려보기.**

- `let label = UILabel()` 코드로 라벨을 선언.
- 라벨의 속성을 부여하는 코드는 NSConstraintLayout 때와 동일.
- `label.translatesAutoresizingMaskIntoConstraints = false` 이 코드가 `SnapKit`에서는 필요하지 않음.
- `NSLayoutConstraint.activate`로 작성하던 제약조건들을 `SnapKit` 문법으로 작성.
- **`inset`**: 슈퍼뷰의 경계로부터 내부 여백을 설정합니다.
- **`offset`**: 특정 뷰나 위치로부터 외부 여백을 설정합니다.

```swift
import UIKit
import SnapKit

class ViewController: UIViewController {

    let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .white
        label.text = &quot;안녕하세요&quot;
        label.textColor = .black

        view.addSubview(label)
        label.snp.makeConstraints {
            $0.width.equalTo(80)
            $0.height.equalTo(40)
            $0.centerX.equalToSuperview()
            $0.centerY.equalToSuperview()
        }
    }
}</code></pre><p>아래처럼 작성할 수도 있지만, Swift 문법에서 클로저의 인자는 $0으로 축약할 수 있기 때문에 축약.</p>
<pre><code class="language-swift">// (1) 축약하지 않은 코드.
label.snp.makeConstraints { make in
    make.width.equalTo(80)
    make.height.equalTo(40)
    make.centerX.equalToSuperview()
    make.centerY.equalToSuperview()
}

// (2) 축약할 수 있으니 이렇게 축약합시다.
label.snp.makeConstraints {
    $0.width.equalTo(80)
    $0.height.equalTo(40)
    $0.centerX.equalToSuperview()
    $0.centerY.equalToSuperview()
}</code></pre>
<p>같은 내용의 NSLayoutConstraint 와 비교했을 때 SnapKit 코드가 가독성이 더 좋음. 
구성해야 할 레이아웃 종류들이 많아질수록 체감이 커짐.
translatesAutoresizingMaskIntoConstraints를 신경 쓰지 않아도 됨. </p>
<pre><code class="language-swift">// (1) NSLayoutConstraint 로 작성한 코드.
NSLayoutConstraint.activate([
    label.widthAnchor.constraint(equalToConstant: 80),
    label.heightAnchor.constraint(equalToConstant: 40),
    label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])

// (2) SnapKit 으로 작성한 코드.
label.snp.makeConstraints {
    $0.width.equalTo(80)
    $0.height.equalTo(40)
    $0.centerX.equalToSuperview()
    $0.centerY.equalToSuperview()
}</code></pre>
<blockquote>
<h3 id="uibutton">UIButton</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nemo_/post/e1bbcd3f-e504-4b97-a550-2dfb327ee61c/image.png" alt=""></p>
<pre><code class="language-swift">import UIKit
import SnapKit

class ViewController: UIViewController {

    let button = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .white
        // 버튼 타이틀 지정.
        button.setTitle(&quot;Click&quot;, for: .normal)
        // 버튼 타이틀 컬러 지정.
        button.setTitleColor(.white, for: .normal)
        // 버튼 색상 지정.
        button.backgroundColor = .red
        // 버튼 테두리 둥글게 지정.
        button.layer.cornerRadius = 10

        view.addSubview(button)
        button.snp.makeConstraints {
            $0.width.equalTo(120)
            $0.height.equalTo(60)
            $0.centerX.equalToSuperview()
            $0.centerY.equalToSuperview()
        }
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/nemo_/post/808426c8-b1b0-4db3-a2ed-33667d83cf62/image.png" alt=""></p>
<p>💡 I<strong>BAction 에서 했었던, 버튼 클릭 이벤트 추가하는 방법.</strong></p>
<ul>
<li><code>button.addTarget(**self**, action: **#selector**(buttonClicked), for: .touchDown)</code>
addTarget 메서드 사용.</li>
<li><code>#selector()</code> 내부에 버튼 클릭 시 어떤 로직을 수행할 건지 담긴 메서드 작성.</li>
<li><code>for:</code> 버튼의 어떤 이벤트에 로직을 수행할 것인지.</li>
<li><code>touchDown</code> : 사용자가 버튼을 터치하는 순간 발생.</li>
<li><code>touchUpInside</code>: 사용자가 버튼을 터치한 후 손가락을 떼는 순간 발생.</li>
</ul>
<pre><code class="language-swift">import UIKit
import SnapKit

class ViewController: UIViewController {

    let button = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .white
        // 버튼 타이틀 지정.
        button.setTitle(&quot;Click&quot;, for: .normal)
        // 버튼 타이틀 컬러 지정.
        button.setTitleColor(.white, for: .normal)
        // 버튼 색상 지정.
        button.backgroundColor = .red
        // 버튼 테두리 둥글게 지정.
        button.layer.cornerRadius = 10
        // 버튼 클릭 이벤트 추가.
        button.addTarget(self, action: #selector(buttonClicked), for: .touchDown)

        view.addSubview(button)
        button.snp.makeConstraints {
            $0.width.equalTo(120)
            $0.height.equalTo(60)
            $0.centerX.equalToSuperview()
            $0.centerY.equalToSuperview()
        }
    }

    // #selector() 안에 넣기 위해서는 @objc 키워드 붙여야 함.
    @objc 
    private func buttonClicked() {
        print(&quot;버튼이 클릭되었음.&quot;)
    }
}</code></pre>
<blockquote>
<h3 id="uiimageview-1">UIImageView</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nemo_/post/f09eaa0b-a200-40c5-b52c-9c51bc1deeb3/image.png" alt=""></p>
<pre><code class="language-swift">import UIKit
import SnapKit

class ViewController: UIViewController {

    let imageView = UIImageView()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .white
        imageView.image = UIImage(named: &quot;cat&quot;)
        imageView.backgroundColor = .black
        imageView.contentMode = .scaleAspectFit

        view.addSubview(imageView)

        imageView.snp.makeConstraints {
            $0.width.equalTo(300)
            $0.height.equalTo(300)
            $0.centerX.equalToSuperview()
            $0.centerY.equalToSuperview()
        }
    }
}
</code></pre>
<ul>
<li>SnapKit을 이용해서 UIImageView와 UILabel을 제약조건과 함께 배치해 봅시다.
<img src="https://velog.velcdn.com/images/nemo_/post/4f37656c-5fbf-4c4d-b0ae-e4628ca69cd4/image.png" alt=""><pre><code class="language-swift">import UIKit
import SnapKit
</code></pre>
</li>
</ul>
<p>class ViewController: UIViewController {</p>
<pre><code>let imageView = UIImageView()
let label = UILabel()

override func viewDidLoad() {
    super.viewDidLoad()
    configureUI()
}

private func configureUI() {
    view.backgroundColor = .white
    imageView.image = UIImage(named: &quot;cat&quot;)
    imageView.backgroundColor = .black
    imageView.contentMode = .scaleAspectFit
    label.text = &quot;고양이&quot;
    label.textColor = .black
    label.font = UIFont.boldSystemFont(ofSize: 30)

    [imageView, label]
        .forEach { view.addSubview($0) }

    imageView.snp.makeConstraints {
        $0.width.equalTo(300)
        $0.height.equalTo(300)
        $0.centerX.equalToSuperview()
        $0.centerY.equalToSuperview()
    }

    label.snp.makeConstraints {
        $0.centerX.equalToSuperview()
        $0.top.equalTo(imageView.snp.bottom).offset(16)
    }
}</code></pre><p>}</p>
<pre><code>
- SnapKit을 이용해서 다음과 같은 UI를 구성해 봅시다.
![](https://velog.velcdn.com/images/nemo_/post/b981e3f8-ee26-479a-b4f9-99aa55a2f041/image.png)
```swift
import UIKit
import SnapKit

class ViewController: UIViewController {

    let imageView = UIImageView()
    let imageView2 = UIImageView()
    let label = UILabel()
    let label2 = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .white
        imageView.image = UIImage(named: &quot;cat&quot;)
        imageView.backgroundColor = .black
        imageView.contentMode = .scaleAspectFit
        label.text = &quot;고양이&quot;
        label.textColor = .black
        label.font = UIFont.boldSystemFont(ofSize: 30)
        imageView2.image = UIImage(named: &quot;dog&quot;)
        imageView2.backgroundColor = .black
        imageView2.contentMode = .scaleAspectFit
        label2.text = &quot;강아지&quot;
        label2.textColor = .black
        label2.font = UIFont.boldSystemFont(ofSize: 30)

        [imageView, label, imageView2, label2]
            .forEach { view.addSubview($0) }

        imageView.snp.makeConstraints {
            $0.width.equalTo(160)
            $0.height.equalTo(160)
            $0.leading.equalToSuperview().inset(16)
            $0.centerY.equalToSuperview()
        }

        label.snp.makeConstraints {
            $0.centerX.equalTo(imageView.snp.centerX)
            $0.top.equalTo(imageView.snp.bottom).offset(16)
        }

        imageView2.snp.makeConstraints {
            $0.width.equalTo(160)
            $0.height.equalTo(160)
            $0.trailing.equalToSuperview().inset(16)
            $0.centerY.equalToSuperview()
        }

        label2.snp.makeConstraints {
            $0.centerX.equalTo(imageView2.snp.centerX)
            $0.top.equalTo(imageView2.snp.bottom).offset(16)
        }
    }
}
</code></pre><blockquote>
<h3 id="ui-디버깅-팁">UI 디버깅 팁</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nemo_/post/96f29559-a424-4c67-aa70-ee6417a0d11b/image.png" alt=""></p>
<p>코드 실행 중에 (런타임에) 화살표 표시한 버튼을 누르면 뷰의 계층구조를 파악하며 디버깅할 수 있음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스토리보드 UI]]></title>
            <link>https://velog.io/@nemo_/%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B3%B4%EB%93%9C-UI-j0rvxib9</link>
            <guid>https://velog.io/@nemo_/%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B3%B4%EB%93%9C-UI-j0rvxib9</guid>
            <pubDate>Wed, 20 Nov 2024 12:24:06 GMT</pubDate>
            <description><![CDATA[<hr>
<h3 id="iboutlet--ibaction">IBOutlet &amp; IBAction</h3>
<hr>
<h4 id="iboutlet">IBOutlet</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/82228d52-8308-4061-a5ad-4fd481735fec/image.png" alt=""></p>
<ul>
<li><code>IBOutlet</code> 은 Interface Builder 와 연결된 객체.</li>
<li>UI 객체를 스토리보드에서 <code>ctrl</code> 을 누른 채로 <code>Drag &amp; Drop</code>  하면 <code>IBOutlet</code> 생성 가능.</li>
<li>혹은 마우스 우클릭으로 <code>Drag &amp; Drop</code> 해도 됨.</li>
<li><strong>`@IBOutlet</strong> <strong>weak</strong> <strong>var</strong> imageView: UIImageView!`</li>
<li><strong><code>weak</code></strong>는 약한 참조를 의미 → 숙련 과정에서 자세히 공부.</li>
<li>스토리보드와 UI 객체의 연결고리 역할.</li>
<li>IB는 <code>Interface Builder</code>의 줄임말.</li>
</ul>
<hr>
<h3 id="ibaction">IBAction</h3>
<h4 id="ibaction-개념">IBAction 개념</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/d5e06d43-d58f-4805-9161-917c9601f8e3/image.png" alt=""></p>
<ul>
<li><p><code>IBAction</code> 은 Interface Builder와 연결된 Action.</p>
</li>
<li><p><code>IBOutlet</code> 생성할 때처럼 UI 객체를 스토리보드에서 <code>ctrl</code> 을 누른 채로 <code>Drag &amp; Drop</code> .</p>
<p>  → <code>Connection</code> 항목에서 <code>Action</code> 선택.</p>
<p>  → 마찬가지로 마우스 우클릭으로 <code>Drag &amp; Drop</code> 해도 됨.</p>
</li>
<li><p>스토리보드와 UI 액션 (이벤트)의 연결고리 역할.</p>
</li>
<li><p>IB는 마찬가지로 <code>Interface Builder</code>의 줄임말.</p>
</li>
</ul>
<p>💡 ex1) IBAction으로 버튼이 클릭 되었다는 print 문을 찍어보자.
<img src="https://velog.velcdn.com/images/nemo_/post/3ba02f34-e1be-4415-ba96-ef8564cdbcb9/image.png" alt="">
버튼이 클릭될 때마다 IBAction에 작성된 로직 실행됨.
<del>예제 코드</del></p>
<pre><code class="language-swift">import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var button: UIButton!
    @IBAction func buttonAction(_ sender: Any) {
        print(&quot;버튼이 클릭 되었음.&quot;)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}</code></pre>
<p>💡 ex2) 변수를 이용해서, 버튼이 몇 번 클릭 되었는지 세도록 해보자.
<img src="https://velog.velcdn.com/images/nemo_/post/57203497-d438-4cff-89c1-6946a1e7f37b/image.png" alt="">
<del>예제 코드</del></p>
<pre><code class="language-swift">import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var button: UIButton!
    @IBAction func buttonAction(_ sender: Any) {
        self.number += 1
        print(&quot;버튼이 \(number)회 클릭 되었음.&quot;)
    }
    private var number: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}</code></pre>
<p>💡 ex3) 버튼을 클릭하면, 강아지 사진으로 바뀌도록 구현해보자.
<img src="https://velog.velcdn.com/images/nemo_/post/8c0bb48c-10ab-44bc-bcb6-1e2132c8e6db/image.png" alt=""></p>
<p>⚠️ 먼저 Image asset 디렉토리에 dog.jpeg를 넣어야 한다.
<del>예제 코드</del></p>
<pre><code class="language-swift">import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var button: UIButton!
    @IBAction func buttonAction(_ sender: Any) {
        self.imageView.image = UIImage(named: &quot;dog&quot;)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[UIKit]]></title>
            <link>https://velog.io/@nemo_/UIKit-m65lazzf</link>
            <guid>https://velog.io/@nemo_/UIKit-m65lazzf</guid>
            <pubDate>Tue, 19 Nov 2024 11:31:15 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="uikit-기초-개념">UIKit 기초 개념</h2>
<hr>
<blockquote>
<h3 id="ui-와-ux-의-차이">UI 와 UX 의 차이</h3>
</blockquote>
<h4 id="ui-user-interface">UI (User Interface)</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/f7399f92-72d7-4b8e-98c8-6f9504e9a94b/image.png" alt=""></p>
<ul>
<li><p>Interface는 상호작용 할 수 있는 창구, 
상호작용할 수 있는 매개체를 의미한다. 
 ex) TV 와 상호작용하려면 리모콘 버튼들(인터페이스) 필요.
  ex) 전자레인지의 작동 버튼들 (인터페이스).</p>
</li>
<li><p>UI는 앱에서 유저와 상호작용하는 구성 요소(창구)들.
  ex) 타이틀, 배터리 잔량, 텍스트 입력 창,이미지 뷰, 스위치 버튼, 아이콘, 버튼, 스크롤 뷰 등.</p>
</li>
<li><p>앱을 구성하는 모든 시각적 요소들은 UI 가 될 수 있다.</p>
</li>
</ul>
<hr>
<h4 id="ux-user-experience">UX (User Experience)</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/448a6f9a-595e-4c15-8aad-26a8bbae450e/image.png" alt=""></p>
<ul>
<li>User Experience는 유저 경험을 의미한다.</li>
<li>앱을 개발하는데, <code>버튼1</code>과 <code>버튼2</code>를 유저가 순서대로 눌러야 하는 상황이 연출된다고 가정.</li>
<li>좌/우 예시 중 어떤 게 유저 경험에 좋고 나쁠까?</li>
<li>좌측 예시가 유저의 손가락을 덜 움직이게 하기 때문에 유저 경험에 더 좋다.</li>
<li>이럴 때 <code>UX 가 더 좋다</code>고 표현한다. 그리고 앱을 서비스하는 회사에서 이 사소한 차이가 매출의 차이를 낼 수 있게 된다.</li>
</ul>
<hr>
<h3 id="uikit-개념">UIKit 개념</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/a6ae4103-e1c2-4ab4-8a9b-1e3b0fdeecb4/image.png" alt=""></p>
<p>✅ <strong>UIKit</strong></p>
<ul>
<li>iOS 앱에서 필요한 UI 요소들을 제공하는 프레임워크</li>
<li>애플이 제공하는 프레임워크</li>
<li>UIKit 은 앞서 말한 예시의 UI 구성요소를 이미 다 제공하고 있다.</li>
</ul>
<p><code>버튼 = UIButton</code> , <code>제목 표시 = UILabel</code> , <code>텍스트 입력창 = UITextView</code>,</p>
<p><code>스크롤 뷰 = UIScrollView</code> , <code>이미지 뷰 = UIImageView</code> , 등등..</p>
<p>💡- 프레임워크(Framework)란?</p>
<p>UIKit이 UI 프레임워크라고 자연스럽게 설명하고 지나갔지만, 프레임워크란 무엇일까.
Framework라는 말 그대로 직역을 해보면
Frame = 틀, work = 작업. 즉 작업을 하는 틀(뼈대)을 의미합니다. </p>
<p>“이런 이런 맥락 안에서 개발을 해라”라는 틀을 제공해 주고,
그걸 사용하는 개발자는 그 틀 안에서 개발을 하게 됩니다.</p>
<p>다시 한번 UIKit 을 이해해 보면, iOS 개발자가 Swift로 UI를 개발할 수 있는 틀을 제공하는 도구로 이해할 수 있습니다.</p>
<hr>
<h3 id="uikit-vs-swiftui">UIKit vs SwiftUI</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/b89b154f-bfdd-4467-9b54-3eadf128be3a/image.png" alt=""></p>
<p>▪️ <strong>UIKit vs SwiftUI</strong></p>
<ul>
<li>SwiftUI는 UIKit 과 같은 UI 개발 프레임워크.</li>
<li>iOS 13 이상 버전부터는 SwiftUI로 개발 가능.</li>
<li>즉, UIKit 이후에 SwiftUI 가 등장.</li>
<li>SwiftUI는 선언적 프로그래밍을 하게 되고, UIKit 은 명령적 프로그래밍을 하게 된다.<ul>
<li>선언적 프로그래밍: UI의 상태에 대해 선언하고, 상태가 변경되면 자동으로 UI를 업데이트.</li>
<li>명령적 프로그래밍: UI의 상태를 명령으로 정의하고, 변화에 따른 업데이트를 직접 처리.</li>
</ul>
</li>
<li>SwiftUI는 아래 사진과 같은 Preview를 보면서 개발 가능.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/nemo_/post/87644112-4eee-433d-9044-302aed36f887/image.png" alt=""></p>
<p>🤔 <strong>왜 UIKit 을 SwiftUI 보다 먼저 공부하나요 ?</strong></p>
<ul>
<li>앞서 말한 것처럼, SwiftUI는 iOS 13 버전부터 지원되기 때문에, 비교적 최신에 나온 프레임워크.</li>
<li>SwiftUI보다 UIKit이 개발자에게 사용된 기간이 길다. 역사가 탄탄한 프레임워크.</li>
<li>시중 자료 중 UIKit으로 작성된 코드가 SwiftUI로 작성된 코드보다 훨씬 많다. 공부 자료가 많다.</li>
<li>현재 서비스 중인 앱들은 iOS 13 보다 낮은 버전을 지원하는 앱이 굉장히 많다. 
→ SwiftUI로 개발을 한 앱은 사용할 수 있는 유저 풀이 UIKit으로 개발한 앱보다 줄어들게 된다. 이는 서비스를 운영하는 회사 입장에선 주의 깊게 고려할 요소.</li>
<li>아직은 취업시장에서 원하는 기술 스택이 UIKit의 비율이 더 높다.</li>
<li>SwiftUI도 물론 굉장히 좋은 기술입니다 :)</li>
</ul>
<hr>
<h3 id="cocoa-framework">Cocoa Framework</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/fadb6207-e2f4-4fe2-b0a1-82f7d14325a2/image.png" alt=""></p>
<p>Cocoa Framework
iOS 공부를 하다 보면 Cocoa라는 단어를 많이 보게 됨.
Cocoa Framework는 애플에서 iOS, macOS 등의 애플 운영체제용 어플리케이션을 제작할 때 사용하는 프레임워크를 말한다.
Cocoa Framework 중 iOS를 위한 프레임 워크를 Cocoa Touch라고 한다.
UIKit, Foundation 이 Cocoa Touch 안에 포함된다.</p>
<hr>
<h3 id="import-foundation-vs-import-uikit">import Foundation vs import UIKit</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/78b83597-4c70-40b1-bf3e-773d34981281/image.png" alt=""></p>
<ul>
<li>NewFile로 새로운 파일을 생성할 때, <code>Swift File</code> / <code>Cocoa Touch Class</code>를 선택해서 생성할 수 있음.</li>
<li><code>Swift File</code>로 생성하면 <code>import Foundation</code>이 되어있는 상태로 생성.</li>
<li><code>Cocoa Touch Class</code>로 생성하면 <code>import UIKit</code>이 되어있는 상태로 생성.</li>
<li>앞선 개념에서 배웠듯, UIKit 은 UI 를 위한 프레임워크.</li>
<li>하지만 Swift로 반드시 UI 관련 코드만 작성하게 되는 것은 아니다. UI와 무관한 코드를 작성할 수 있다.
ex) 단순 연산, 비즈니스 로직, 네트워크 관련 로직 등..</li>
<li>그런 경우 <code>import UIKit</code>을 하는게 아니라 <code>import Foundation</code>을 한다.</li>
<li>Foundation 은 Swift 를 위한 기초 프레임워크이지만 UI 관련 프레임워크가 생략된 것.</li>
<li>파일의 맨 처음에 <code>import Foundation</code>이 되어있냐, <code>import UIKit</code>이 되어있냐에 따라 그 파일에 작성된 코드의 의도와 범위를 유추해볼 수 있게 됨.</li>
</ul>
<hr>
<h3 id="safe-area">Safe Area</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/61333b26-840b-43b2-ba34-40385e66b53d/image.png" alt=""></p>
<ul>
<li>아이폰에는 Safe Area라는 보이지 않는 영역이 존재.</li>
<li>Safe Area는 앱의 컨텐츠가 안정적으로 보이기 위한 영역</li>
<li>아이폰 기기마다 모습이 조금씩 다르기 때문에 Safe Area 가 필요.</li>
<li>예를 들어 노치가 있는 기기도 있고, 없는 기기도 있는데 이런 기기 특성과 무관하게 안정적으로 앱의 컨텐츠를 노출시켜주기 위한 가이드 영역.</li>
</ul>
<hr>
<h3 id="autolayout--constraint">AutoLayout &amp; Constraint</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/75c98aec-91ef-4ab8-b9ed-1e45738e853f/image.png" alt=""></p>
<p>💡 <strong>AutoLayout</strong></p>
<ul>
<li>AutoLayout 이란 기기 사이즈에 맞춰 자동으로 사이즈를 맞춰주는 것.</li>
<li>AutoLayout 을 사용하지 않을 경우, iPhone 15에서 노출된 사진의 위치와 iPad에서 노출된 사진의 위치가 의도와 맞지 않게 다르게 된다.</li>
<li>마지막 사진은 AutoLayout 을 적용시킨 사진.</li>
</ul>
<p>💡 <strong>Constraint</strong></p>
<ul>
<li><p>Constraint는 뷰와 뷰 사이의 제약조건을 의미한다.</p>
<ul>
<li><p><code>left / right / top / bottom</code></p>
<p>  각각 <code>좌 / 우 / 위 / 아래</code> 제약 조건을 의미.</p>
</li>
<li><p><code>leading / trailing</code></p>
<p>  직역하면 <code>앞 / 뒤</code> 제약 조건인데, 지역화된 조건이기 때문에</p>
<p>  왼쪽에서 오른쪽으로 읽는 게 자연스러운 지역 조건의 기기에서는 </p>
<p>  <code>leading = left, trailing = right</code>로 대응되고, 반대의 경우엔</p>
<p>  <code>leading = right, trailing = left</code>로 대응된다.</p>
</li>
</ul>
</li>
</ul>
<ul>
<li>위 고양이 사진에서 기기의 <code>leading / trailing / top / bottom</code> Constraint를 지정해두었기 때문에 기기가 달라지더라도 원하는 레이아웃으로 조정된다.</li>
</ul>
<hr>
<h3 id="margin-padding">Margin, Padding</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/fe46b773-219a-4b3f-80d3-ab42d328c064/image.png" alt=""></p>
<ul>
<li><strong>Margin</strong>은 Border 바깥쪽을 차지. 
주변 요소와 거리를 두기 위한 영역.</li>
<li><strong>Padding</strong>은 Content와 Border 사이의 여백을 나타내는 영역. Content 영역이 배경색이나 배경 이미지를 가질 때, 이 Padding 영역까지도 영향을 미침.</li>
</ul>
<hr>
<p>스토리보드에서 cmd + shift + L을 누르면 UIKit의 구성요소들을 사용할 수 있습니다.
혹은 툴 바 영역의 십자 버튼(+) 을 눌러도 같은 동작을 할 수 있습니다.
<img src="https://velog.velcdn.com/images/nemo_/post/6296ffa9-7516-42db-990f-276167a95d4e/image.png" alt=""></p>
<pre><code>Label 을 사용하고 싶다 → Label 을 드래그해서 스토리보드로 옮김 → 스토리보드에 Label 생성</code></pre><h4 id="uitableview">UITableView</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/0e423432-b6da-4d34-8cdc-e96f3d675ae6/image.png" alt=""></p>
<ul>
<li>테이블 형태의 뷰</li>
<li>각 테이블 항목은 <code>UITableViewCell</code>이라고 한다.</li>
</ul>
<h4 id="uicollectionview">UICollectionView</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/e37c2d83-fabd-46f1-97d3-272dfe0fe6f5/image.png" alt=""></p>
<ul>
<li><code>UITableView</code> 보다 복잡한 구성을 할 수 있는 뷰.</li>
<li>각 컬렉션 뷰의 항목은 <code>UICollectionViewCell</code>이라고 한다.</li>
</ul>
<h4 id="uialertcontroller">UIAlertController</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/d9be224a-78df-4974-9d24-b4e41d5a0c32/image.png" alt=""></p>
<ul>
<li>알럿을 띄우는 뷰.</li>
<li>앱을 맨 처음 설치하고 위치 정보, 사진 접근 등 권한을 물을 때도 알럿을 사용한다.</li>
</ul>
<h4 id="uitextview">UITextView</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/c0123925-509e-4f62-b933-b2cb0d278df5/image.png" alt=""></p>
<ul>
<li>텍스트를 입력하는 뷰.</li>
<li>검색 창이나, 아이디 입력 창에 많이 사용한다.</li>
</ul>
<h4 id="uipageviewcontroller">UIPageViewController</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/9078fc3c-0c46-444e-a788-5eb7ef41a25c/image.png" alt=""></p>
<ul>
<li>페이지를 넘기듯 넘길 수 있는 뷰.</li>
<li>인스타그램에서도 페이지를 넘기는듯한 페이지 뷰를 사용하고 있다.</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Xcode 빌드]]></title>
            <link>https://velog.io/@nemo_/Xcode-%EB%B9%8C%EB%93%9C</link>
            <guid>https://velog.io/@nemo_/Xcode-%EB%B9%8C%EB%93%9C</guid>
            <pubDate>Mon, 18 Nov 2024 12:13:43 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="빌드하기-전-기초-개념">빌드하기 전 기초 개념</h2>
<hr>
<h3 id="컴파일--빌드">컴파일 &amp; 빌드</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/00fba1d7-85df-4803-ab57-f267c2b3214f/image.png" alt=""></p>
<blockquote>
<p>💡 - 컴파일(Compile)</p>
</blockquote>
<p>- 컴파일이란 어떤 언어의 코드 전체를 다른 언어로 바꿔주는 과정이다.</p>
<p>즉, Swift에서 컴파일이란,
사람이 이해하기 쉬운 High Level 언어인 Swift로 작성한 코드를
컴퓨터가 이해하기 쉬운 바이너리 파일로 변환하는 과정.</p>
<blockquote>
<p>💡 - 빌드 (Build)</p>
</blockquote>
<p>소스 코드 파일을 실행 가능한 소프트웨어 산출물로 변환하는 과정이다.
컴파일만 해서는 실행 가능한 결과가 나오지 않는다.
프로젝트에 포함한 리소스(이미지, 컬러 등 Swift 코드가 아닌 것들)까지 모두 말아서
실행 가능한 결과를 만든다.</p>
<hr>
<h3 id="앱-생명주기-app-lifecycle">앱 생명주기 (App Lifecycle)</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/99e1900a-cafd-4827-bebb-c7792093fd33/image.png" alt=""></p>
<p>▪️ Unattached (= Not Running)</p>
<p>앱을 실행 중이지 않은 상태.</p>
<p>▪️ Foreground Inactive</p>
<p>앱을 실행했지만 사용자로부터 이벤트를 받을 수 없는 상태. </p>
<p>앱을 완전히 활성화하기 이전 단계.</p>
<p>▪️ <strong>Foregound Active</strong></p>
<p>앱을 실행했고 사용자로부터 이벤트를 받을 수 있는 상태.</p>
<p>가장 일반적인 앱을 사용하고 있는 상태.</p>
<p>▪️ <strong>Background</strong></p>
<p>앱을 실행한 뒤 백그라운드로 넘어간 상태.</p>
<p>홈버튼을 눌러 밖으로 나갔을 때의 상태.</p>
<p>그래도 메모리에 올라가있는 상태이다. </p>
<p>ex) 멜론 앱이 홈 화면에서도 음악을 계속 재생하는 상태</p>
<p>▪️ <strong>Suspend</strong></p>
<p>백그라운드 상태에서 앱이 특별한 작업을 필요로 하지 않을 경우 접어드는 상태</p>
<p>OS 가 판단하여 Background → Suspend 상태로 변환시킨다.</p>
<p>ex) 게임 앱을 동시에 30개 켜면 (기기 메모리에 부담스러운 상황을 연출하면), 비교적 초반에 실행한 앱들은 백그라운드에서 Suspend 상태로 넘어가, 돌아갔을 때 앱이 재실행되는 경우가 있다.</p>
<hr>
<h3 id="appdelegate--scenedelegate">AppDelegate &amp; SceneDelegate</h3>
<hr>
<h4 id="appdelegate">AppDelegate</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/c7836b23-9b39-4401-b77a-0872d60c3820/image.png" alt=""></p>
<ul>
<li>프로젝트를 생성하면 자동으로 생성되는 클래스.</li>
<li>앱의 생명주기와 관련된 다양한 메서드들을 제공하는 클래스.</li>
</ul>
<p>ex 1) didFinishLaunchingWithOptions (위 캡처화면에서 보이는 메서드)</p>
<pre><code class="language-swift">func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&gt; Bool {
    // Override point for customization after application launch.
    return true
}</code></pre>
<p>이 메서드는 앱이 실행할 준비를 마친 초기 상황에 실행된다.</p>
<p>따라서 앱을 실행한 극 초반에 세팅해야 할 코드들을 이곳에 작성하면 된다.</p>
<p>ex 2) <code>didBecomeActive</code> </p>
<p>이 메서드는 앱의 상태가 inActive에서 Active로 변하기 직전에 실행된다.</p>
<p>따라서 앱이 Active한 상태가 되기 전에 작성하고 싶은 코드들을 작성한다.</p>
<p>예를 들어 앱이 백그라운드에 갔다가 돌아올 때마다 화면에 보이는 정보들을 업데이트하고 싶다. 이런 경우에 이 메서드를 활용할 수 있다.</p>
<hr>
<h4 id="scenedelegate">SceneDelegate</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/dcc62b99-60a2-4a4e-b7da-057a00806ddf/image.png" alt=""></p>
<ul>
<li>프로젝트를 생성하면 자동으로 생성되는 클래스.</li>
<li>Scene 을 다루는 클래스. iOS13부터 등장.</li>
<li>멀티 윈도우 환경에서 다중 Scene의 개념이 생겨났다.</li>
<li>아이패드에서 가로 모드, 화면 분할을 하면 각 분할된 화면이 Scene 을 의미.</li>
<li>그전에는 AppDelegate가 SceneDelgate의 역할까지 모두 수행했다.</li>
</ul>
<hr>
<h3 id="퍼스트파티--서드파티">퍼스트파티 &amp; 서드파티</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/d41e2624-0654-4f3d-9da5-f411a5bbd6e4/image.png" alt=""></p>
<p>▪️ <strong>퍼스트 파티 (제1자)</strong></p>
<p>iOS 개발은 애플이 주관하기 때문에, 애플이 제공하는 툴들을 퍼스트 파티라고 한다.</p>
<p>ex) UIKit, URLSession 등</p>
<p>▪️ <strong>서드 파티 (제3자)</strong></p>
<p>애플이 오피셜 하게 만든 것이 아닌, 제3자가 제공하는 툴들을 서드 파티라고 한다.</p>
<p>ex) SnapKit (UI를 편하게 짜도록 돕는 라이브러리)</p>
<p>Alamofire (URLSession 을 편하게 사용하도록 돕는 라이브러리)</p>
<p>KingFisher (이미지 로드를 편하게 하도록 돕는 라이브러리)</p>
<p>Swinject (DI 및 ServiceLocating 을 편하게 하도록 돕는 라이브러리)</p>
<p>CocoaPods, SPM(Swift Package Manager), Carthage 등을 이용해서 서드파티 라이브러리를 프로젝트에 받아올 수 있다.</p>
<hr>
<h3 id="디버깅-llldb-개념">디버깅, llldb 개념</h3>
<hr>
<h4 id="디버깅">디버깅</h4>
<p>디버깅이란?</p>
<p>디버깅(Debugging)이란 소프트웨어에서 발생하는 오류(bug)를 찾아내고 수정하는 과정. 
프로그램이 예상대로 작동하지 않을 때 그 이유를 찾아내고, 그 문제를 해결하는 작업.</p>
<hr>
<p>print() 문으로 디버깅하기</p>
<pre><code class="language-swift">// ViewController.swift 

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let randomNumber = getRandomNumber()
        print(&quot;randomNumber = \(randomNumber)&quot;)
    }

    // 0 부터 100 중 랜덤한 수를 리턴하는 함수
    func getRandomNumber() -&gt; Int {
        return Int.random(in: 0...100)
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/nemo_/post/9fe00497-6af1-4daa-a556-8af8e1778795/image.png" alt=""></p>
<hr>
<h3 id="oslog-로그-찍기">OSLog 로그 찍기</h3>
<pre><code class="language-swift">import UIKit
import OSLog

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        os_log(.default, &quot;(default) randomNumber = \(self.getRandomNumber())&quot;)
        os_log(.debug, &quot;(debug) randomNumber = \(self.getRandomNumber())&quot;)
        os_log(.info, &quot;(info) randomNumber = \(self.getRandomNumber())&quot;)
        os_log(.error, &quot;(error) randomNumber = \(self.getRandomNumber())&quot;)
        os_log(.fault, &quot;(fault) randomNumber = \(self.getRandomNumber())&quot;)
    }

    // 0 부터 100 중 랜덤한 수를 리턴하는 함수
    func getRandomNumber() -&gt; Int {
        return Int.random(in: 0...100)
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/nemo_/post/2e19372a-fb31-4381-8dcd-1930888aaf9f/image.png" alt=""></p>
<hr>
<h4 id="브레이크포인트-찍기">브레이크포인트 찍기</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/580975bc-2359-4b8e-86b4-b29145019937/image.png" alt=""></p>
<ul>
<li>16 번 라인 숫자를 클릭해서 파란색 브레이크 포인트 생성.</li>
<li>브레이크 포인트를 삭제하려면 드래그해서 밖에다가 버리면 됨.</li>
<li>아래 디버그 영역에 <code>randomNumber = (Int)</code> 75 출력된 것 확인.</li>
<li>아래 디버그 영역에서 재생 버튼 누르면 브레이크가 풀리며 다시 원래 흐름대로 이어서 실행됨.</li>
</ul>
<hr>
<h3 id="lldb">lldb</h3>
<p>💡- lldb 란?</p>
<p>Low Level Debuger의 줄임말. 디버그 영역 중 오른쪽 영역에 lldb를 사용해서 디버깅을 할 수 있다.
브레이크 포인트와 함께 사용하면 효과가 좋다.</p>
<p><img src="https://velog.velcdn.com/images/nemo_/post/dcb8cebe-258a-478c-9794-88331f5bb3f2/image.png" alt=""></p>
<ul>
<li><p><code>po</code> 명령어: print object의 줄임말로, 객체에 대한 값을 출력한다. 가장 많이 사용하게 될 명령어.</p>
<p>  위 캡처 사진처럼 맥락 안에서 어느 정도 자동완성도 지원을 해준다.</p>
<p>  <code>po randomNumber &gt; 100</code>처럼 계산식을 입력해서 그에 대한 결과값도 받을 수 있다.</p>
</li>
<li><p><code>cmd + K</code>를 눌러서 디버그 창을 깨끗하게 비울 수 있다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/nemo_/post/03b220a7-37b5-4983-b37e-11eb2bd1fd9b/image.png" alt=""></p>
<ul>
<li><p><code>expression</code> 명령어: 변수를 선언할 수 있게 해준다.</p>
<p>  <code>expression let $x = 100</code> : x = 100 선언.</p>
<p>  <code>expression randomNumber</code> : lldb 영역 내에서 randomNumber를 $R0 이라는 변수로 선언.
  새롭게 선언할 때마다 $R1, $R2 로 자동 선언.</p>
<p>  <code>po $R0 + $x</code> : 두 값을 더한 값 출력</p>
<hr>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Xcode 시작]]></title>
            <link>https://velog.io/@nemo_/Xcode-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@nemo_/Xcode-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Fri, 15 Nov 2024 06:55:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="xcode-이해">Xcode 이해</h3>
</blockquote>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/6685a2a6-4c69-4369-b384-a7ca6a8728ea/image.png" alt=""></p>
<ul>
<li>Xcode는 애플 소프트웨어 개발을 위한 IDE.</li>
<li>iOS, macOS, watchOS, tvOS 등 개발 가능.</li>
<li>Swift, Objective-C로 개발 가능.</li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/89db29fa-1acd-4295-9271-b7d8ea4d624e/image.png" alt=""></p>
<ul>
<li><strong>Xcode release notes.</strong></li>
<li>release note 란 새로운 버전이 출시(release) 되었을 때 이 버전에 대한 정보를 정리한 노트.</li>
<li>Xcode 버전 별로 지원하는 iOS, macOS 버전이 다르므로 주의.</li>
<li>맥북 왼쪽 위 사과 버튼 → 이 mac에 관하여를 보면 나의 macOS 버전을 볼 수 있음.
<a href="https://developer.apple.com/documentation/xcode-release-notes">링크텍스트</a></li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/nemo_/post/422832c5-317c-481d-a8de-1f8f5550bc46/image.png" alt=""></p>
<ul>
<li><strong>Xcodes = 다양한 Xcode 버전을 담는 프로그램.</strong></li>
<li>여러 개의 프로젝트를 동시에 진행 중이고, 각각의 프로젝트에서 모두 다른 Xcode 버전이 필요한 상황이라면?
<a href="https://www.xcodes.app/">링크텍스트</a></li>
</ul>
<hr>
<blockquote>
<h3 id="xcode-151-다운로드">Xcode 15.1 다운로드</h3>
</blockquote>
<h4 id="step-1">STEP 1</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/d74059f5-d3fe-437f-aa29-d1a2a8decf1d/image.png" alt=""></p>
<ul>
<li><strong>먼저, 지원하는 버전 확인.</strong></li>
<li>Xcode 15.1 Release Notes의 Overview를 읽어보면, macOS 버전이 Ventura 13.5 이후 버전이어야 함이 명시되어 있음.</li>
<li>먼저 본인 맥북의 macOS를 <code>Ventura 13.5</code> 이상 or <code>Sonoma</code>로 맞추어야 합니다.</li>
</ul>
<hr>
<h4 id="step-2">STEP 2</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/4f5651d6-17aa-427d-a40e-8119183338f0/image.png" alt=""></p>
<ul>
<li><a href="https://developer.apple.com/download/all/">다운로드 링크</a>로 이동.</li>
<li>검색창에 <code>Xcode 15.1</code> 검색</li>
<li>View Details → Xcode 15.1 (.xip) 클릭해서 다운로드</li>
<li>Xcode 15.1 (.xip 파일) 다운로드.</li>
</ul>
<hr>
<h4 id="step3">STEP3</h4>
<p><img src="https://velog.velcdn.com/images/nemo_/post/72219534-341e-4dd6-8013-1389fa67c494/image.png" alt=""></p>
<ul>
<li>파일 압축 해제 하면 다운로드 완료.</li>
<li>Xcode 실행 모습</li>
<li>Version 15.1 확인</li>
</ul>
<hr>
<blockquote>
<h3 id="xcode-둘러보기">Xcode 둘러보기</h3>
</blockquote>
<h3 id="프로젝트-생성">프로젝트 생성</h3>
<p>Create New Project -&gt; App → ProductName 입력 → Next → 프로젝트 위치 설정 → 생성 완료
<img src="https://velog.velcdn.com/images/nemo_/post/2bcd1b9f-8c20-4e44-866f-518fa7db22ce/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>항목</th>
<th align="left">설명</th>
<th align="center">비고</th>
</tr>
</thead>
<tbody><tr>
<td>Product Name</td>
<td align="left">생성할 프로젝트 이름</td>
<td align="center">ex) MyTestApp, MyTestProject</td>
</tr>
<tr>
<td>Team</td>
<td align="left">애플 개발자 인증서 선택</td>
<td align="center">없을 경우 None 선택. 인증서가 있어야 실제 기기로 테스트하거나 앱스토어에 배포 가능</td>
</tr>
<tr>
<td>Organization Identifier</td>
<td align="left">조직 or 도메인 이름 입력</td>
<td align="center">한 회사에서 여러 개의 앱을 낼 수 있음.한 개발자가 여러 개의 앱을 낼 수 있음 - com.adam.app1 - com.adam.app2</td>
</tr>
<tr>
<td>Bundle Identifier</td>
<td align="left">앱 고유 아이디</td>
<td align="center">- com.adam.MyTestApp</td>
</tr>
<tr>
<td>Interface</td>
<td align="left">User Interface 선택</td>
<td align="center">여기서는 Storyboard 선택</td>
</tr>
<tr>
<td>Storage</td>
<td align="left">Core Data 사용 여부</td>
<td align="center">이 강의에서는 None 선택</td>
</tr>
<tr>
<td>Include Tests</td>
<td align="left">테스트 코드 작성 여부</td>
<td align="center">테스트 코드란, 내 코드를 테스트(검증)하기 위한 코드</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>Apple Developer Certificates - 애플 개발자 인증서란? 🤔</strong></p>
<ul>
<li>내가 애플의 인증을 받고 앱스토어에 앱을 낼 수 있는 개발자라는 증명서</li>
<li>Apple Developer Program에 등록하면 받을 수 있음. 매년 99달러 (약 13만 원)</li>
<li>실기기로 테스트하려면 인증서 필요</li>
<li>앱스토어에 앱을 배포하려면 인증서 필요</li>
</ul>
</blockquote>
<hr>
<h3 id="organization-identifier--bundle-identifier-개념">Organization Identifier &amp; Bundle Identifier 개념</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/b37a7404-5342-44ff-ac80-74879a55d477/image.png" alt=""></p>
<ul>
<li><p><strong>Organization Identifier</strong></p>
<ul>
<li><code>com.coupang</code></li>
<li><code>com.kakao</code></li>
</ul>
</li>
<li><p><strong>Bundle Identifier</strong></p>
<ul>
<li><code>com.coupang.Coupang</code></li>
<li><code>com.coupang.coupang-eats</code></li>
<li><code>com.coupang.play</code></li>
</ul>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[iOS개요]]></title>
            <link>https://velog.io/@nemo_/iOS%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@nemo_/iOS%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Thu, 14 Nov 2024 11:24:21 GMT</pubDate>
            <description><![CDATA[<p><del>내일만 버티면 금요일이다.이걸 보는 모두 화이팅..</del></p>
<h3 id="ios-개발에-대한-기본적인-이해">iOS 개발에 대한 기본적인 이해</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/4c453637-5054-4830-ad47-5df582ffaf40/image.png" alt=""></p>
<ul>
<li><p>애플은 기기에 따른 다양한 운영체제들을 지원 중.</p>
</li>
<li><p>(아이폰 - iOS),  (맥북 - macOS),  (애플워치 - watchOS)</p>
</li>
<li><p>(애플 TV - tvOS), (비전 pro - visionOS)</p>
</li>
<li><p>모두 Objective-C, Swift로 개발 가능.</p>
</li>
<li><p>iOS 개발을 하기 위해서는 반드시 macOS 기반으로 개발.
<img src="https://velog.velcdn.com/images/nemo_/post/f756df6d-3a49-4ac8-a5c2-4a43f7f6720c/image.png" alt=""></p>
</li>
<li><p>iOS 개발자가 된다는 건, 개발뿐만 아니라 애플이 제공하는 기기와 운영체제들에 대한 깊은 이해를 가지게 된다는 것.</p>
</li>
<li><p>앱을 출시하기 위해서는 앱스토어 심사과정 필요.</p>
</li>
<li><p>HIG (애플의 앱 디자인 가이드) 등 애플의 철학 이해 필요.</p>
</li>
<li><p>나아가서는  iOS 와 macOS 버전에 따른 변화 파악.
<a href="https://developer.apple.com/design/human-interface-guidelines">링크텍스트</a>
<a href="https://developer.apple.com/kr/wwdc24/">링크텍스트</a>
<del>들어가지마세요. 책임안져요.</del>
<img src="https://velog.velcdn.com/images/nemo_/post/c71b0fbf-0511-4ce7-bbb7-92d27a9f69e2/image.png" alt=""></p>
</li>
<li><p><strong>네이티브 앱</strong></p>
<p>  각 OS 환경에 최적화된 네이티브 언어로 개발된 앱.</p>
<p>  -Swift로 개발한 iOS 앱.
  -Kotlin으로 개발한 Android 앱.</p>
<p>  ex) 카카오, 쿠팡, 당근 마켓, 티맵, 쏘카, 스노우 등</p>
</li>
<li><p><strong>하이브리드 앱</strong></p>
<p>  Android, iOS 플랫폼 모두 돌아가도록 개발한 앱.</p>
<p>  -React Native, JavaScript로 개발.
  -Flutter - Dart로 개발.</p>
<p>  ex) 페이스북, 인스타그램 등</p>
</li>
<li><p><strong>웹 앱</strong></p>
<p>  웹 기술을 사용해 웹 사이트를 모바일 앱 형태로 개발.</p>
<p>  ex) 네이버, 크롬 등</p>
<blockquote>
<p><strong>🙋🏻‍♂️ Android, iOS 둘 다 가능한 하이브리드 앱개발이 네이티브 앱개발보다 무조건 좋은 거 아닌가요?</strong></p>
</blockquote>
</li>
</ul>
<p>→ 🙅🏻‍♂️ 각각의 장단점이 존재합니다.</p>
<p>하이브리드 앱은 Android, iOS 앱에서 모두 돌아가도록 한 번에 개발을 할 수 있지만, 이를 다른 말로 옮겨보면, 두 운영체제를 동시에 관통하는 일반적인 코드를 작성하게 되기 때문에, 반대로 각각의 고유한 특성을 활용하는 데에는 한계가 있다는 것을 의미.</p>
<blockquote>
<p>ex1) 카메라 기능을 사용하는 SNOW 같은 앱을 개발한다면?</p>
</blockquote>
<p> 안드로이드 폰의 카메라와 아이폰의 카메라는 근본적으로 다를 수밖에 없기 때문에, 하이브리드 앱으로는 각 카메라 하드웨어의 깊은 곳까지 파고드는 기능을 사용하는 데에는 한계 존재.</p>
<blockquote>
<p>ex2) 기기의 GPS 기능을 자주 활용하는 Tmap 같은 네비게이션 앱을 개발한다면?</p>
</blockquote>
<p>하이브리드 앱보다 네이티브 앱이 플랫폼 친화적이기 때문에 GPS 활용 수행 능력이 좋음.</p>
<p><img src="https://velog.velcdn.com/images/nemo_/post/4a4c94bb-3451-4f39-a8f5-cc42aaa8fd27/image.JPG" alt=""></p>
<h3 id="서버-개발-안드로이드-개발과의-비교">서버 개발, 안드로이드 개발과의 비교</h3>
<ul>
<li>네모의 앱 로그인 기능을 구현한다고 했을 때.<ul>
<li>서버: 데이터베이스에 유저 정보를 저장. 유저가 아이디와 비밀번호를 입력해서 로그인 요청을 했을 때 정합성 파악.</li>
<li>iOS: 로그인 페이지의 텍스트 뷰, 버튼 구현. 유저가 입력한 아이디와 비밀번호를 가지고 서버에 로그인 요청.  유저 로그인 정보 기기에 저장.</li>
<li>Android: 같은 내용을 Kotlin으로 구현.</li>
</ul>
</li>
</ul>
<p>서버 개발자는 앱스토어의 심사 개념이라든지, 애플의 디자인 철학이 어떻게 된다든지 이런 건 자세하게 몰라도 됨.</p>
<p>안드로이드 개발자도 마찬가지. 각 개발자는 각자의 영역에 대해서 집중. </p>
<p>iOS 개발자로 회사에 들어간다라는건 이런 부분들을 맡아서 전문성 있게 잘 해내준다는 걸 의미.</p>
<h3 id="효율적인-공부-방법">효율적인 공부 방법</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/ea559d72-31bb-468b-97f7-422520e1109e/image.png" alt=""></p>
<ul>
<li>개발 입문 단계에서는 검색으로 해결 못하는 것이 없음. <del>반박시 죄송합니다.</del></li>
<li>Google 과 StackOverflow를 활용.</li>
<li>검색 키워드에 “iOS”보단 “Swift”를 붙이는 게 좋음.</li>
<li>뭔가 동작을 잘 안 할 때 “issue” or “not work” 검색.</li>
<li>키워드 끝에 “doc”을 붙이면 관련 애플 공식 문서 노출.
<a href="https://stackoverflow.com/">링크텍스트</a></li>
</ul>
<p><strong>앱에서 버튼을 구현할 일이 생겼다. 근데 나는 아무것도 할 줄 모른다.</strong></p>
<ul>
<li><strong>CASE 1</strong>
UIButton 관련 문서를 정독하고, 관련 서적 찾아 UIButton의 모든 속성들에 대해 공부한 뒤 코드를 작성.
(BackgroundColor, Title Color, Button State, 테두리 만드는 법, shadow 등 등 너무 많음.)</li>
<li><strong>CASE 2</strong> 
구글에 “Swift 버튼 구현” 검색하고 따라 해본 뒤 직접 해보면서 이해.</li>
</ul>
<p>→ CASE 2가 더 효율적. 직접 코드를 작성하시고 부딪혀보시면서 몸으로 느끼면서 공부하는 것이 효율적.</p>
<h3 id="cs-공부-중요한가요">CS 공부. 중요한가요?</h3>
<p><img src="https://velog.velcdn.com/images/nemo_/post/8d3b700b-89d2-472c-a496-ff82df04af0a/image.png" alt=""></p>
<ul>
<li>CS 공부, 중요하긴 중요하다.</li>
<li>하지만 지금 당장 몰라도 충분히 iOS 개발을 할 수 있다.</li>
<li>구글에 “개발자 CS 면접 준비”라고 검색하면 좋은 자료들 多.</li>
<li>iOS 개발자는 운영체제, 자료구조 &gt; 네트워크 &gt; 그 외 순으로 중요.</li>
<li>전공서 사서 공부하기 X.</li>
</ul>
<p><del>다 알면 좋지만, 현재로써는 다 알기보다는 중요도에 맞춰서 하는걸 추천.
데이터 베이스가 서버를 공부할 경우 추천하지만, iOS는 비교적 중요도가 낮아서 오히려 운영체제(메모리를 관리하는 방법), 자료구조(데이터 다루는 기본적 구조)를 공부하면 좋다.</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 문법종합 - 메모리]]></title>
            <link>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-%EB%A9%94%EB%AA%A8%EB%A6%AC</link>
            <guid>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-%EB%A9%94%EB%AA%A8%EB%A6%AC</guid>
            <pubDate>Wed, 13 Nov 2024 12:11:38 GMT</pubDate>
            <description><![CDATA[<h3 id="12-메모리로-보는-자료구조">12. 메모리로 보는 자료구조</h3>
<ul>
<li>CPU는 컴퓨터의 중앙처리장치로, 프로그램의 연산을 실행하고 처리하는 하드웨어이며, 메모리에 저장된 데이터를 직접 처리하는 역할을 합니다.</li>
<li>앞에서 예시로 들었던 var, let, 함수 등은 메모리에 할당되어 있어요.</li>
<li>메모리란?</li>
<li>메모리는 프로그램이 실행될 때 데이터를 저장하는 임시 공간입니다.</li>
<li>채팅앱을 사용한다고 가정해보죠!<ul>
<li>친구 목록이나 주고받은 채팅들이 화면에 보여진다는 것은 핸드폰 어딘가에 정보들이 저장되어 있다는 것을 의미합니다.</li>
<li>이 저장되어 있는 영역을 메모리(RAM)이라고 합니다.</li>
<li>아이폰에서 앱을 누르면 iOS는 앱을 위해 메모리에 영역을 설정해줍니다.<ul>
<li>실행한 앱은 여기 영역만 쓰세요~ 라고 영역을 정해주는 거죠!</li>
<li>나중에 우리가 작성한 코드도 메모리에 저장됩니다!</li>
<li>우리가 사용하는 변수나 상수의 값들도 메모리에 저장돼요!</li>
<li>메모리에는 영역이 나뉘어져 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 문법종합 - trailing closure]]></title>
            <link>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-trailing-closure</link>
            <guid>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-trailing-closure</guid>
            <pubDate>Tue, 12 Nov 2024 12:45:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="10-2-trailing-closure-트레일링-클로저">10-2. trailing closure (트레일링 클로저)</h3>
</blockquote>
<ul>
<li>클로저에는 경량화 방법이 많이 존재하는데 그 중 하나인 트레일링 클로저에 대해서 알아볼게요!</li>
<li>함수를 호출할 때 마지막 파라미터가 클로저일 때 괄호를 닫은 뒤 작성할 수 있습니다.</li>
<li>장점</li>
<li>클로저를 함수 호출 외부에 작성해서 클로저의 구현부가 길어지면 가독성이 읽기 편합니다.</li>
</ul>
<pre><code class="language-swift">func trailingTest(key: String, closure: () -&gt; Void) {
    print(key)
    closure()
}

trailingTest(key: &quot;Key name&quot;, closure: { // 원래는 이렇게 만들어야 하지만 아래처럼 가능
    print(&quot;Hello&quot;)
})

trailingTest(key: &quot;Hello&quot;) { 
    print(&quot;World&quot;)
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 문법종합 - 클로저]]></title>
            <link>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9-%ED%81%B4%EB%A1%9C%EC%A0%80</link>
            <guid>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9-%ED%81%B4%EB%A1%9C%EC%A0%80</guid>
            <pubDate>Mon, 11 Nov 2024 11:47:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="10-1-클로저">10-1. 클로저</h3>
</blockquote>
<h4 id="코드스니펫-클로저-구현-코드">[코드스니펫] 클로저 구현 코드</h4>
<pre><code class="language-swift">let closure: (Int, String) -&gt; Void = { intValue, stringValue in 
                                // in 키워드를 사용하여 앞에는 파라미터 이름을 뒤에는 구현부를 작성합니다.
    print(intValue)
    print(stringValue)
}
// ⬆️ 오랜만에 한국말로 읽어보기! 
// closure 상수의 타입은 클로저로 Int, String 타입의 파라미터가 있고 Return은 없습니다.
// 할당값은 클로저의 구현입니다.

// 파라미터의 이름을 생략하면 구현부에서 $0로 사용할 수 있습니다.
// 파라미터의 이름을 생략했기 때문에 in 키워드도 생략합니다. 
let closure2: (Int) -&gt; Void = { 
    print($0)
}

let closure3: () -&gt; Void = {
    print(&quot;Hello&quot;)
}

// 파라미터의 이름을 value라고 사용하고 Int값을 리턴합니다.
let closure4: (Int) -&gt; Int = { value in
    return value * 2
}

let closure5: (String, Int) -&gt; Int = {
    return $0.count + $1
}

let optionalClosure: ((Int) -&gt; Int)? = { value in
    return value * 2
}

// 파라미터가 없을 때는 in을 사용하면 안됩니다. 
let emptyParameterClosure: () -&gt; Void = {
    print(&quot;World&quot;)
}</code></pre>
<hr>
<ul>
<li><strong>익명 함수</strong> 라고 불리는 클로저는 함수와 유사하게 특정 작업을 수행하는 코드블록 입니다.</li>
<li>함수의 4가지 요소(<strong>이름, 파라미터, 반환타입, 구현부</strong>)에서 <strong>이름</strong> 을 제외한 <strong>파라미터, 반환타입, 구현부</strong> 로 구성되어 있습니다.</li>
<li><strong>데이터타입</strong>으로 클로저를 사용할 수 있습니다.<ul>
<li>변수에 할당하여 재사용할 수 있습니다.</li>
<li>함수의 파라미터로 전달할 수 있습니다.</li>
</ul>
</li>
<li>클로저가 함수 안에 포함된 것이 아니라 클로저 안에 함수가 포함되어 있습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/nemo_/post/17bb8b0b-8f67-48a4-9e3d-62e57e492073/image.png" alt=""></p>
<ul>
<li>코드의 재사용성을 높이고, 비동기 처리, 컬렉션 연산 등의 장점이 있습니다.</li>
</ul>
<hr>
<pre><code class="language-타입으로">// 타입으로 사용할 때 표현방법
(파라미터 데이터 타입) -&gt; 리턴 타입 </code></pre>
<ul>
<li><p>파라미터의 타입은 없을 때 비워두고, 리턴 타입이 없는경우 <strong>Void</strong> 라고 명시합니다.</p>
</li>
<li><p>파라미터가 없을때도 <strong>()</strong> 괄호는 있어야 합니다!</p>
</li>
<li><p>데이터 타입으로 사용할 수 있기 때문에 <strong>Optional</strong> 타입으로 사용할 수 있습니다.
이 때 전체를 <strong>()</strong> 로 감싼 후 <strong>?</strong> 를 입력해야 합니다.</p>
</li>
</ul>
<pre><code class="language-swift">let closure: (Int, String) -&gt; Void // 파라미터는 튜플(Int, String) 타입이고
                                                                     // 반환타입은 없습니다.
                                                                     // 만약 -&gt; Void가 생략되었다면 튜플과 같죠!?

let closure2: (Int) -&gt; Void        // 파라미터는 1개로 Int이며 반환타입은 없습니다.

let closure3: () -&gt; Void           // 파라미터는 없고 반환타입은 없습니다.
                                                                     // 파라미터는 ()로 감싸고 있어서 Void를 생략할 수 있어요.

let closure4: (Int) -&gt; Int         // 파라미터는 1개이고 Int 타입이며, 반환값은 Int 입니다. 
let closure5: (String, Int) -&gt; Int // 파라미터는 튜플(String, Int) 타입이고
                                                                     // 반환타입은 Int 입니다.

let optionalClosure: ((Int) -&gt; Int)? // 옵셔널 타입입니다.
                                                                         // 만약 ()가 없었다면 리턴값이 Int? 라고 판단됩니다!</code></pre>
<h3 id="클로저-구현하는-방법">클로저 구현하는 방법</h3>
<ul>
<li>클로저는 익명함수 이므로, 구현할 때 <strong>in</strong> 키워드를 사용하여 파라미터와 구현부를 구분해야 합니다.</li>
</ul>
<pre><code class="language-swift">// 기본적인 구현 방법
// 중괄호로 시작하고 파라미터와 구현부 부분을** in ** 키워드를 사용하여 나눕니다.
{ 파라미터 이름 in  
    // 구현부
    // 리턴
}</code></pre>
<ul>
<li><strong>in</strong> 키워드를 사용하여 앞에는 파라미터 이름을 뒤에는 구현부를 분리하여 작성합니다.</li>
<li>파라미터 이름은 생략 가능하며 0번째 파라미터 <strong>$0</strong> 으로 사용할 수 있습니다. (<strong>$0, $1, $2</strong> …) 파라미터 이름을 생략할 때는 <strong>in</strong> 키워드까지 생략해야 합니다.</li>
<li>파라미터가 없다면 <strong>in</strong> 을 생략해야 합니다.</li>
<li>파라미터 이름은 사용하실 이름을 자유롭게 작성하면 됩니다.</li>
</ul>
<p>[코드스니펫] 클로저 구현 코드</p>
<pre><code class="language-swift">let closure: (Int, String) -&gt; Void = { intValue, stringValue in 
                                // in 키워드를 사용하여 앞에는 파라미터 이름을, 뒤에는 구현부를 작성합니다.
    print(intValue)
    print(stringValue)
}
// ⬆️ 오랜만에 한국말로 읽어보기! 
// closure 상수의 타입은 클로저로 Int, String 타입의 파라미터가 있고 Return은 없습니다.
// 할당값은 클로저의 구현입니다.

// 파라미터의 이름을 생략하면 구현부에서 $0로 사용할 수 있습니다.
// 파라미터의 이름을 생략했기 때문에 in 키워드도 생략합니다. 
let closure2: (Int) -&gt; Void = { 
    print($0)
}

let closure3: () -&gt; Void = {
    print(&quot;Hello&quot;)
}

// 파라미터의 이름을 value라고 사용하고 Int값을 리턴합니다.
let closure4: (Int) -&gt; Int = { value in
    return value * 2
}

let closure5: (String, Int) -&gt; Int = {
    return $0.count + $1
}

let optionalClosure: ((Int) -&gt; Int)? = { value in
    return value * 2
}

// 파라미터가 없을 때는 in을 사용하면 안됩니다. 
let emptyParameterClosure: () -&gt; Void = {
    print(&quot;World&quot;)
}</code></pre>
<h3 id="클로저를-사용호출하는-방법">클로저를 사용(호출)하는 방법</h3>
<ul>
<li>클로저를 변수나 상수에 저장했다면 함수처럼 이름뒤에 괄호를 사용하면 됩니다.</li>
<li>옵셔널 변수에 저장한 클로저는 <strong>변수이름?()</strong> 형식으로 사용하면 됩니다.</li>
<li>구현하고 즉시 호출한 결과값을 저장하고 싶다면 <strong>{}</strong> 뒤에 <strong>괄호()</strong> 를 사용하면 됩니다.</li>
</ul>
<hr>
<pre><code class="language-swift">// 변수에 저장한 클로저 호출
let closure: (Int, String) -&gt; Void = { intValue, stringValue in
    print(intValue)
    print(stringValue)
}

closure(10, &quot;20&quot;)

/* 출력 값
10
20
*/</code></pre>
<hr>
<pre><code class="language-swift">// 옵셔널 변수에 저장한 클로저 호출

let optionalClosure: ((Int) -&gt; Int)? = { 
    return $0 * 2
}

print(optionalClosure?(10)) // 옵셔널 상수 optionalClosure의 클로저를 호출합니다.
                                                      // 상수의 타입이 optional이기 때문에 결과값이 Optional로 나옵니다.

/* 출력 값
Optional(20)
*/

// 옵셔널 변수에 저장한 클로저 호출

let optionalClosure: ((Int) -&gt; Int)? = { 
    return $0 * 2
}

print(optionalClosure?(10)) // 옵셔널 상수 optionalClosure의 클로저를 호출합니다.
                                                      // 상수의 타입이 optional이기 때문에 결과값이 Optional로 나옵니다.

/* 출력 값
Optional(20)
*/
</code></pre>
<hr>
<pre><code class="language-swift">// 구현하고 바로 호출

// 클로저를 구현하고 바로 실행하여 결과값을 helloClosure 상수에 할당합니다.
let helloClosure = {
    return &quot;안녕하세요! 구현하자마자 바로 실행해보겠습니다!&quot;
}()

print(helloClosure) // 안녕하세요! 구현하자마자 바로 실행해보겠습니다!

</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 심화 - 숫자 야구]]></title>
            <link>https://velog.io/@nemo_/Swift-%EC%8B%AC%ED%99%94-%EA%B3%BC%EC%A0%9C-%EC%88%AB%EC%9E%90-%EC%95%BC%EA%B5%AC</link>
            <guid>https://velog.io/@nemo_/Swift-%EC%8B%AC%ED%99%94-%EA%B3%BC%EC%A0%9C-%EC%88%AB%EC%9E%90-%EC%95%BC%EA%B5%AC</guid>
            <pubDate>Fri, 08 Nov 2024 01:09:36 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="배경">배경</h1>
<p>Level2의 &quot;오류가 날 수 있는 예외처리 상황에 대해 고민해보기 + 구현하기&quot;라는 문항을 보고, 예외처리와 오류를 어떻게 효과적으로 다룰 수 있을지 고민하며 야구 게임을 구현했습니다. 게임 내에서 발생할 수 있는 다양한 예외 상황을 처리하기 위해 여러 방안을 고려했으며, 이를 코드로 구현해보았습니다.</p>
<hr>
<h1 id="발단">발단</h1>
<p>게임에서 사용자가 입력하는 값이나 예상치 못한 상황에서 오류가 발생할 수 있습니다. 예를 들면:</p>
<ul>
<li>사용자가 잘못된 범위의 숫자를 입력할 경우
: 특정 연산에서 예기치 않게 잘못된 상태가 발생할 경우 (예: 점수 계산, 게임 종료 조건 등)</li>
<li>게임이 여러 번 반복되면서 상태가 꼬이는 경우
: 이런 예외 상황들을 미리 처리하지 않으면 게임의 흐름이 중단되거나 예상치 못한 오류가 발생할 수 있습니다.</li>
</ul>
<hr>
<h1 id="전개">전개</h1>
<p>게임을 구현하면서 몇 가지 문제에 직면했습니다:</p>
<p>사용자의 잘못된 입력 처리: 사용자가 숫자가 아닌 값을 입력하거나, 범위를 벗어난 값을 입력할 경우 이를 처리해야 했습니다.
게임 종료 조건 처리: 게임이 끝난 후 계속해서 입력을 받는 오류가 발생할 수 있었습니다.
불필요한 상태 변경: 게임이 진행되는 도중, 사용자가 잘못된 입력을 했을 때 상태가 변경되지 않도록 해야 했습니다.
이를 해결하기 위해서는 예외를 처리하는 조건문을 추가하고, 사용자가 입력하는 값에 대해 더 철저히 검증해야 했습니다. 또한, 게임의 상태를 관리하는 방법을 개선하여 불필요한 상태 변경을 방지해야 했습니다.</p>
<hr>
<h1 id="위기">위기</h1>
<p>게임 진행 중에 예외 처리 코드가 너무 많아져 코드의 가독성이 떨어졌다.
게임 종료 조건을 정확하게 처리하지 못해, 게임이 끝난 후에도 계속 입력을 받는 문제가 발생
Int 타입의 입력값에 대한 예외 처리가 미흡하여 프로그램이 크래시가 날 위험이 있다.</p>
<hr>
<h1 id="절정">절정</h1>
<p>게임의 흐름을 관리하기 위해 게임 상태를 명확히 구분하였고, 예외 처리를 간결하게 처리할 수 있는 방법을 도입했습니다. try-catch 구문을 활용하여 예외를 처리하고, 사용자가 잘못된 입력을 했을 때 즉시 오류 메시지를 출력하고 게임을 종료하거나 입력을 다시 받도록 했습니다.</p>
<p>또한, 게임 종료 후에는 더 이상 입력을 받지 않도록 상태를 관리했습니다. 이를 통해 코드의 가독성을 크게 개선하고, 예외 상황에서 게임이 끊김 없이 정상적으로 동작하도록 만들었습니다.</p>
<hr>
<h1 id="결말">결말</h1>
<p>아래와 같이 게임을 구현할 수 있었고, 예외처리와 입력 검증을 철저히 하여 게임이 더 안정적이고 예측 가능하게 되었습니다. 예외가 발생할 경우, 적절한 오류 메시지를 출력하고 게임이 정상적으로 종료되도록 처리했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 문법종합 - 함수]]></title>
            <link>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-%ED%95%A8%EC%88%98-sb0ave36</link>
            <guid>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-%ED%95%A8%EC%88%98-sb0ave36</guid>
            <pubDate>Thu, 07 Nov 2024 12:57:57 GMT</pubDate>
            <description><![CDATA[<h2 id="8-함수">8. 함수</h2>
<p>/코드스니펫/ 함수의 기본 사용방법</p>
<pre><code class="language-swift">// 함수의 기본 사용방법

// 함수의 정의(선언)
func 함수이름(파라미터 이름: 파라미터 타입) -&gt; 반환타입 {
  // 구현부 
    return 반환 타입의 값
}

// 파라미터가 여러개인 경우
func 함수이름(파라미터1 이름: 파라미터1 타입, 파라미터2 이름: 파라미터2 타입) -&gt; 반환타입 {
  // 구현부 
    return 반환 타입의 값
}

// 파라미터가 없는 경우 
func 함수이름() -&gt; 반환 타입 {
  // 구현부 
    return 반환 타입의 값
}

// 반환값이 없는 경우 
func 함수이름(파라미터 이름: 파라미터 타입) {
    // 구현부
    // 반환타입이 없기때문에 return이 없어도 됩니다.
    // 반환타입이 없는데 중간에 함수를 탈출하고 싶을 때 return을 사용할 수 있습니다.
}

// 파라미터가 없고, 반환값도 없는 경우
func 함수이름() {
    // 구현부
}</code></pre>
<p>함수는 재사용이 가능한 코드의 집합으로, 특정 작업을 수행하는 코드 블록입니다.
<img src="https://velog.velcdn.com/images/nemo_/post/1a008bdd-733a-4c1b-a495-ece7ada57dac/image.png" alt=""></p>
<ul>
<li>함수는 INPUT 을 받아서 작업을 수행하고, OUTPUT을 반환합니다.</li>
<li>동일한 입력에는 동일한 출력이 반환됩니다!</li>
</ul>
<hr>
<ul>
<li>함수는 function이라고 불리며, 코드를 구조적이고 효율적으로 작성하는데 도움을 줍니다.</li>
<li>동일한 작업을 반복해서 작성할 필요 없게 도와줍니다.</li>
<li>함수는 선언과 사용으로 나눌 수 있습니다.<ul>
<li>함수의 선언 : 함수를 정의하는 과정입니다.<ul>
<li><strong>func</strong> 키워드를 사용하여 시작합니다.</li>
<li>함수 선언의 4가지 요소<ol>
<li>함수의 이름</li>
<li>파라미터(이름, 타입)<ul>
<li>파라미터는 함수에서 사용될 INPUT의 이름과 데이터타입</li>
</ul>
</li>
<li>반환 타입<ul>
<li>함수에서 결과값으로 반환할 데이터 타입</li>
</ul>
</li>
<li>구현부<ul>
<li>실제 작업을 수행하는 코드 블록</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
<li>함수의 사용 : 정의한 함수를 호출하여 실제로 실행하는 과정입니다.<ul>
<li>함수의 이름뒤에 <strong>()</strong> 를 사용하여 함수를 실행합니다.</li>
<li><strong>()</strong> 안에는 파라미터를 넣을 수 있습니다.</li>
<li>함수를 사용하여 나온 결과값을 변수나 상수에 할당할 수 있습니다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>[코드스니펫] 함수의 기본 사용방법</p>
<pre><code class="language-swift">// 함수의 기본 사용방법

// 함수의 정의(선언)
func 함수이름(파라미터 이름: 파라미터 타입) -&gt; 반환타입 {
  // 구현부 
    return 반환 타입의 값
}

// 파라미터가 여러개인 경우
func 함수이름(파라미터1 이름: 파라미터1 타입, 파라미터2 이름: 파라미터2 타입) -&gt; 반환타입 {
  // 구현부 
    return 반환 타입의 값
}

// 파라미터가 없는 경우 
func 함수이름() -&gt; 반환 타입 {
  // 구현부 
    return 반환 타입의 값
}

// 반환값이 없는 경우 
func 함수이름(파라미터 이름: 파라미터 타입) {
    // 구현부
    // 반환타입이 없기때문에 return이 없어도 됩니다.
    // 반환타입이 없는데 중간에 함수를 탈출하고 싶을 때 return을 사용할 수 있습니다.
}

// 파라미터가 없고, 반환값도 없는 경우
func 함수이름() {
    // 구현부
}</code></pre>
<p>[코드스니펫] 함수의 기본 실습</p>
<pre><code class="language-swift">// 실제로 사용해 봅시다!

// 함수의 선언
func sayHello(name: String) -&gt; String { // sayHello 이름의 함수를 정의합니다. 파라미터는 name이며 타입은 String이고, 함수의 반환값의 데이터타입은 String입니다.
    return &quot;\(name)님 안녕하세요!&quot; // 파라미터 name을 사용하여 문자열을 만들어 반환합니다.
}
// 함수의 사용
sayHello(name: &quot;Swift 기초반&quot;) // 결과값은 문자열로 &quot;Swift 기초반님 안녕하세요!&quot; 입니다.



// 함수의 선언
func printHello() {
    print(&quot;안녕하세요!&quot;)
}
// 함수의 사용
printHello()


func add(a: Int, b: Int) -&gt; Int {
    return a + b
}

let result = add(a: 3, b: 5) // add 함수를 사용하여 받은 결과값을 result 상수에 할당합니다. 
print(result) // 8</code></pre>
<h4 id="함수-파라미터-이름의-분리">함수 파라미터 이름의 분리</h4>
<p>함수의 구현부에서 사용하는 이름과 함수 호출할 때 사용하는 이름을 다르게 설정할 수 있습니다.
기본적으로 함수의 선언부에서 정한 파라미터의 이름은 함수의 구현부와 호출부에서 동일하게 사용됩니다.</p>
<pre><code class="language-swift"> func sayHello(name: String) {
        print(&quot;\(name)님 안녕하세요.&quot;)
}

sayHello(name: &quot;수강생&quot;) 

// sayHello 함수를 정의하는 곳에서 파라미터의 이름이 name이면 
// sayHello 함수를 호출하는 곳에서도 파라미터의 이름은 name을 사용합니다.</code></pre>
<p>Swift에서는 구현부에서 사용하는 파라미터의 이름과 호출부에서 사용하는 파라미터의 이름을 다르게 할 수 있습니다.</p>
<ul>
<li>첫번째 이름: 함수를 호출할 때 사용하는 이름</li>
<li>두번째 이름: 함수 구현부에서 사용하는 이름</li>
</ul>
<pre><code class="language-swift">// 스페이스로 띄우고 파라미터 이름을 2개 입력하면 호출, 구현부 에서 사용하는 이름을 정할 수 있습니다.
func sayHello(to name: String) { 
    print(&quot;\(name)님 안녕하세요.&quot;)
}

// 함수를 정의한 곳에서 파라미터의 이름이 2개 있을 때 첫번째 값이 호출할때 사용하는 이름입니다.
sayHello(to: &quot;수강생&quot;) 


// _ 를 사용하여 파라미터의 이름을 생략할 수 있습니다.
// _는 보통 생략하는데 사용됩니다.
// 위에와 동일하게 정의하는 곳에서 파라미터 이름이 2개로 들어가 있습니다.(_, name)
func sayBye(_ name: String) {
    print(&quot;\(name)님 안녕히가세요.&quot;)
}

sayBye(&quot;수강생&quot;)</code></pre>
<p><strong>Swift Convention Guide No.5</strong> → 함수의 코드 스타일</p>
<ul>
<li>함수의 이름은 <strong>소문자</strong>로 시작해서 <strong>Camel Case</strong>로 맞춰주세요.</li>
<li>파라미터의 이름은 <strong>소문자</strong>로 시작해서 <strong>Camel Case</strong>로 맞춰주세요.</li>
<li><strong>-&gt;</strong> 연산자 앞뒤로 공백을 넣습니다.</li>
</ul>
<pre><code class="language-swift">// ✅ 함수의 이름은 messageToBirthday로 message to birthday 의 Camel Case
// ✅ 파라미터의 이름은 isTodayBirthDay로 소문자 Camel Case
func messageToBirthday(isTodayBirthDay: Bool) -&gt; String {
    // 구현부
}

// ❌ 함수의 이름이 소문자로 시작하지만 Camel Case가 아니여서 읽기 힘들어요.
// ❌ 파라미터의 이름이 소문자로 시작하지만 Camel Case가 아니여서 읽기 힘들어요.
func messagetobirthday(istodaybirthday: Bool) -&gt; String {
    // 구현부
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 문법종합 - 조건문]]></title>
            <link>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-%EC%A1%B0%EA%B1%B4%EB%AC%B8-cgc7y0gz</link>
            <guid>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-%EC%A1%B0%EA%B1%B4%EB%AC%B8-cgc7y0gz</guid>
            <pubDate>Wed, 06 Nov 2024 11:10:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="6-조건문">6. 조건문</h3>
</blockquote>
<hr>
<p>*<em>- if문 실습
*</em>var age = 15 </p>
<pre><code class="language-swift">if age &gt;= 20 {
    print(&quot;20살이 넘은 성인입니다&quot;)
} else if age &gt;= 17 {
    print(&quot;20살 미만이고 17살 이상인 고등학생입니다.&quot;)
} else if age &gt;= 14 {
    print(&quot;17살 미만이고 14살 이상인 중학생입니다.&quot;)
} else {
    print(&quot;어린이 입니다!&quot;)
}

// 위의 코드를 한줄씩 번역해 볼게요!
let age = 15  // age 상수에 15를 할당합니다.

if age &gt;= 20 { // 만약 age가 20 이상이면 아래 코드블록을 실행합니다.
    print(&quot;20살이 넘은 성인입니다&quot;)
} else if age &gt;= 17 { // 만약 age가 20 미만이고 17이상이면 아래 코드블록을 실행합니다.
    print(&quot;20살 미만이고 17살 이상인 고등학생입니다.&quot;)
} else if age &gt;= 14 { // 만약 age가 17 미만이고 14이상이면 아래 블록을 실행합니다.
    print(&quot;17살 미만이고 14살 이상인 중학생입니다.&quot;)
} else { // 위의 조건들이 모두 false이면 else 의 code block을 실행합니다.
    print(&quot;어린이 입니다!&quot;)
}


if 10 &gt; 20 { // 만약 10 이 20보다 크다면 아래 코드블록을 실행합니다.
    print(&quot;10은 20보다 큽니다!!!&quot;)
} else { // 만약 10이 20보다 크지 않다면 아래 코드블록을 실행합니다.
    print(&quot;10은 20보다 작습니다!!!&quot;)
}</code></pre>
<hr>
<p><strong>- 조건문 실습 1</strong></p>
<pre><code class="language-swift">let score = 80

switch score {
    case 90...100:
        print(&quot;A 등급 입니다!&quot;)
    case 80..&lt;90:
        print(&quot;B 등급 입니다.&quot;)
    case 70..&lt;80:
        print(&quot;C 등급 입니다.&quot;)
    default:
        print(&quot;D 등급 입니다.&quot;)
}

// 번역

let score = 80 // score 이름의 상수에 80을 할당합니다.

switch score { // score를 switch 코드블록에 넣습니다. 
    case 90...100: // score가 90이상 100점 이하일 때 아래 코드를 실행합니다.
        print(&quot;A 등급 입니다!&quot;)
    case 80..&lt;90: // score가 80이상 90미만일 때 아래 코드를 실행합니다.
        print(&quot;B 등급 입니다.&quot;)
    case 70..&lt;80: // score가 70이상 80미만일 때 아래 코드를 실행합니다.
        print(&quot;C 등급 입니다.&quot;)
    default: // score가 위에 조건문에서 true가 하나도 없었다면 아래 코드를 실행합니다.
        print(&quot;D 등급 입니다.&quot;)
}</code></pre>
<hr>
<p><strong>- 조건문 실습 2</strong></p>
<pre><code class="language-swift">**var score = 50

switch score {
    case 90...:
        break
    default:
        print(&quot;공부를 더 하세요!&quot;)        
}

// 출력 
// &quot;공부를 더 하세요!&quot;

switch score {
case 50...:
    print(&quot;50점 이상이시군요&quot;)
    fallthrough
case 50:
    print(&quot;딱 50점이시네요?&quot;)
default:
    print(&quot;공부를 더 하세요!&quot;)
}

// 출력 
// &quot;50점 이상이시군요&quot;
// &quot;딱 50점이시네요?&quot; 


// 번역
var score = 50 // score 라는 변수에 값 50을 할당합니다.

switch score { // score를 switch 코드블록에 넣습니다.
    case 90...: // score가 90보다 크다면 아래 코드를 실행합니다.
        break     // 아무런 행동을 하지 않고 코드블록에서 탈출합니다.
    default:   // score가 위의 조건에 맞지 않는다면 아래 코드를 실행합니다.
        print(&quot;공부를 더 하세요!&quot;)         
}

// 출력 
// &quot;공부를 더 하세요!&quot;

switch score { // score를 switch 코드블록에 넣습니다.
case 50...: // score가 50 이상이면 아래 코드를 실행합니다.
    print(&quot;50점 이상이시군요&quot;)
    fallthrough // 아래의 case도 확인합니다.
case 50: // score가 50이면 아래 코드를 실행합니다.
    print(&quot;딱 50점이시네요?&quot;)
default: // score가 위의 조건에 맞는게 없다면 아래 코드를 실행합니다.
    print(&quot;공부를 더 하세요!&quot;)
}</code></pre>
<p>프로그램의 흐름을 제어하는 문법으로, 특정 조건이 true 인지 false인지에 따라 코드블록을 실행하거나 건너뛸 수 있습니다.</p>
<h3 id="--if">**- if</h3>
<p>**</p>
<ul>
<li>조건이 true 이면 코드 블록을 실행하고, false 이면 else 의 코드블록이 실행됩니다.</li>
<li>if 와 else 중간에 조건을 추가하려면 else if 로 조건을 추가할 수 있습니다.</li>
</ul>
<p><strong>- if문 실습</strong></p>
<pre><code class="language-swift">if 10 &gt; 20 { // 만약 10 이 20보다 크다면 아래 코드블록을 실행합니다.
    print(&quot;10은 20보다 큽니다!!!&quot;)
} else { // 만약 10이 20보다 크지 않다면 아래 코드블록을 실행합니다.
    print(&quot;10은 20보다 작습니다!!!&quot;)
}


var age = 15 

if age &gt;= 20 {
    print(&quot;20살이 넘은 성인입니다&quot;)
} else if age &gt;= 17 {
    print(&quot;20살 미만이고 17살 이상인 고등학생입니다.&quot;)
} else if age &gt;= 14 {
    print(&quot;17살 미만이고 14살 이상인 중학생입니다.&quot;)
} else {
    print(&quot;어린이 입니다!&quot;)
}

/*
// 위의 코드를 한줄씩 번역해 볼게요!
var age = 15  // age 상수에 15를 할당합니다.

if age &gt;= 20 { // 만약 age가 20 이상이면 아래 코드블록을 실행합니다.
    print(&quot;20살이 넘은 성인입니다&quot;)
} else if age &gt;= 17 { // 만약 age가 20 미만이고 17 이상이면 아래 코드블록을 실행합니다.
    print(&quot;20살 미만이고 17살 이상인 고등학생입니다.&quot;)
} else if age &gt;= 14 { // 만약 age가 17 미만이고 14 이상이면 아래 블록을 실행합니다.
    print(&quot;17살 미만이고 14살 이상인 중학생입니다.&quot;)
} else { // 위의 조건들이 모두 false이면 else의 code block을 실행합니다.
    print(&quot;어린이 입니다!&quot;)
}
*/</code></pre>
<hr>
<p><strong>- Guard</strong></p>
<ul>
<li>guard 는 조건이 false일 때 코드를 더이상 실행시키지 않고 코드블록을 빠져나가는 조건문입니다.</li>
<li>조건이 true일 경우 코드를 계속 진행하고, false일 경우 else 코드 블록에서 탈출해야합니다.</li>
<li>else 블록은 필수입니다.</li>
</ul>
<pre><code class="language-swift">guard 조건 else { 
    // 조건이 false 일 때 실행되는 코드블록  
    // return, break, continue, throw등 코드블록을 종료하는 구문이 필요
    return 
}

// 만약 위에서 조건이 false라면 아래 코드는 실행되지 않습니다.
// ealry exit (빠른 탈출)이라고도 불립니다.

// 조건이 true이면 아래의 코드를 게속 진행합니다.
</code></pre>
<hr>
<p>// 반복문에서 사용하는 guard </p>
<pre><code class="language-swift">for i in 0...10 {
    print(i)
    guard i &lt; 5 else { break }  
}

/* 출력 값
0
1
2
3
4
5
*/

// 한국말로 풀어보기
for i in 0...10 { // 0부터 10까지 반복합니다. 현재값은 i에 넣습니다.
    print(i) 
    guard i &lt; 5 else { break }   // i가 5보다 작다면 true이기 때문에 계속 진행합니다.
                                                             // i가 5보다 크다면 false이기 때문에 else 코드블록을 실행합니다.
                                                             // else 코드블록에서는 for 반복문을 탈출하는 break 
}</code></pre>
<p><strong>if vs guard</strong></p>
<ul>
<li><strong>if</strong> 조건문은 <strong>true</strong> 일 때 코드블록을 실행하고, false 일 때 다른처리를 할 수 있습니다.</li>
<li><strong>guard</strong> 조건문은 false일 때 즉시 탈출하는 방식입니다.</li>
<li><strong>if</strong> 조건문은 코드블록을 명시적으로 작성해야 하지만 <strong>guard</strong> 는 <strong>true</strong> 일 때 코드블록이 없고 계속 진행합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/nemo_/post/cbcef066-dbe1-4517-ac88-057ed7a9f129/image.png" alt=""></p>
<p>- <strong>guard</strong> 는 아래의 더이상 코드를 진행하지 못하게 하는 경비원 같은 존재</p>
<p><img src="https://velog.velcdn.com/images/nemo_/post/307ec18a-d770-4daa-ac40-42afad9707e3/image.png" alt=""></p>
<p>- <strong>if</strong>는 코드블록에 들어갈 수 있는 티켓
놀이공원에 입장하려면 티켓이 필요해요.
그리고 놀이공원 안에서만 사용할 수 있다고 생각하면 된다.</p>
<p><strong>Switch</strong> </p>
<ul>
<li>하나의 값을 여러 경우와 비교하는 조건문입니다.</li>
<li>모든 경우의 조건을 처리해야 합니다.</li>
<li><strong>case</strong>, <strong>default</strong> 를 사용해서 조건들에 맞는 코드를 실행할 수 있습니다.</li>
<li><strong>case</strong> 에서 조건을 확인하여 조건이 맞다면 코드를 실행하고 switch 코드블록에서 빠져나갑니다.</li>
<li><strong>case</strong> 에서 맞는 조건이 없다면 <strong>default</strong> 에서 구현한 코드가 실행됩니다.</li>
<li><strong>case</strong>, <strong>default</strong> 조건 뒤에는 <strong>:</strong> 을 사용하여 동작을 구현해야 합니다.</li>
<li><strong>case</strong> 에서 실행하고 싶은 코드가 없다면 <strong>break</strong> 키워드를 사용해야 합니다.<ul>
<li>아무런 코드가 없으면 빌드오류가 발생합니다.</li>
</ul>
</li>
<li><strong>case</strong> 에서 조건이 없다면 <strong>default</strong> 코드를 실행합니다.</li>
<li><strong>case</strong> 에서 조건이 맞아서 실행된 후 아래의 조건도 확인하고 싶다면 <strong>fallthrough</strong> 키워드를 사용해야 합니다.</li>
</ul>
<p><strong>- 조건문 실습 1</strong></p>
<pre><code class="language-swift">let score = 80

switch score {
    case 90...100:
        print(&quot;A 등급 입니다!&quot;)
    case 80..&lt;90:
        print(&quot;B 등급 입니다.&quot;)
    case 70..&lt;80:
        print(&quot;C 등급 입니다.&quot;)
    default:
        print(&quot;D 등급 입니다.&quot;)
}

// 번역
let score = 80 // score 이름의 상수에 80을 할당합니다.

switch score { // score를 switch 코드블록에 넣습니다. 
    case 90...100: // score가 90이상 100점 이하일 때 아래 코드를 실행합니다.
        print(&quot;A 등급 입니다!&quot;)
    case 80..&lt;90: // score가 80이상 90미만일 때 아래 코드를 실행합니다.
        print(&quot;B 등급 입니다.&quot;)
    case 70..&lt;80: // score가 70이상 80미만일 때 아래 코드를 실행합니다.
        print(&quot;C 등급 입니다.&quot;)
    default: // score가 위에 조건문에서 true가 하나도 없었다면 아래 코드를 실행합니다.
        print(&quot;D 등급 입니다.&quot;)
}</code></pre>
<hr>
<p><strong>- 조건문 실습 2</strong></p>
<pre><code class="language-swift">var score = 50

switch score {
    case 90...:
        break
    default:
        print(&quot;공부를 더 하세요!&quot;)        
}

// 출력 
// &quot;공부를 더 하세요!&quot;

switch score {
case 50...:
    print(&quot;50점 이상이시군요&quot;)
    fallthrough
case 50:
    print(&quot;딱 50점이시네요?&quot;)
default:
    print(&quot;공부를 더 하세요!&quot;)
}

// 출력 
// &quot;50점 이상이시군요&quot;
// &quot;딱 50점이시네요?&quot; 


// 번역
var score = 50 // score 라는 변수에 값 50을 할당합니다.

switch score { // score를 switch 코드블록에 넣습니다.
    case 90...: // score가 90보다 크다면 아래 코드를 실행합니다.
        break     // 아무런 행동을 하지 않고 코드블록에서 탈출합니다.
    default:   // score가 위의 조건에 맞지 않는다면 아래 코드를 실행합니다.
        print(&quot;공부를 더 하세요!&quot;)         
}

// 출력 
// &quot;공부를 더 하세요!&quot;

switch score { // score를 switch 코드블록에 넣습니다.
case 50...: // score가 50 이상이면 아래 코드를 실행합니다.
    print(&quot;50점 이상이시군요&quot;)
    fallthrough // 아래의 case도 확인합니다.
case 50: // score가 50이면 아래 코드를 실행합니다.
    print(&quot;딱 50점이시네요?&quot;)
default: // score가 위의 조건에 맞는게 없다면 아래 코드를 실행합니다.
    print(&quot;공부를 더 하세요!&quot;)
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 문법종합 - 반복문]]></title>
            <link>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-%EB%B0%98%EB%B3%B5%EB%AC%B8</link>
            <guid>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-%EB%B0%98%EB%B3%B5%EB%AC%B8</guid>
            <pubDate>Tue, 05 Nov 2024 11:32:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="7-반복문">7. 반복문</h3>
</blockquote>
<hr>
<p>반복문은 배열이나 Dictionary 같은 Collection Type이나 범위를 반복적으로 접근할 때 사용합니다.</p>
<ul>
<li>배열은 동일한 데이터 타입의 값을 순서대로 저장하는 Collection Type중 하나입니다.</li>
<li>대괄호를 사용하여 배열을 만들 수 있습니다.</li>
</ul>
<pre><code class="language-swift">let array = [1, 2, 3, 4, 5] 
// [ ] 안에 데이터들을 넣으면 1,2,3,4,5가 들어가 있는 배열이 만들어집니다.

[&quot;봄&quot;, &quot;여름&quot;, &quot;가을&quot;, &quot;겨울&quot;] // 봄, 여름, 가을, 겨울이 저장되어 있는 배열이에요!</code></pre>
<hr>
<h3 id="for-반복문">for 반복문</h3>
<p><strong>for in</strong> 을 사용하면 배열(Collection Type)과 범위의 숫자를 반복할 수 있습니다.</p>
<pre><code class="language-swift">// 컬렉션 타입에 대한 for in 반복문

let names = [&quot;Anna&quot;, &quot;Alex&quot;, &quot;Brian&quot;, &quot;Jack&quot;] 

for name in names {
    print(name)
}

// 출력 값 
// Anna
// Alex
// Brian
// Jack

// 해석 

let names = [&quot;Anna&quot;, &quot;Alex&quot;, &quot;Brian&quot;, &quot;Jack&quot;] // names 상수에 배열로 순서대로 &quot;Anna&quot;, &quot;Alex&quot;, &quot;Brian&quot;, &quot;Jack&quot;을 넣습니다.

for name in names { // names 배열 안에 있는 값들을 반복하여 아래 코드블록을 실행합니다. name에 배열의 데이터를 넣습니다.
    print(name)
}</code></pre>
<hr>
<pre><code class="language-swift">// 숫자의 범위를 사용한 for in 반복문

for index in 0...5 {
    print(index)
}

// 출력 값
// 0
// 1
// 2
// 3
// 4
// 5 

// 한국어 번역
for index in 0...5 { 
  // 0이상 5까지 숫자를 index에 할당하며 반복합니다.
    print(index)
}</code></pre>
<hr>
<h4 id="while-반복문">while 반복문</h4>
<ul>
<li><strong>while</strong> 반복문은 조건이 <strong>true</strong> 인 동안 코드블록을 반복 실행합니다.</li>
<li>조건이 <strong>false</strong>가 되면 반복을 종료합니다.</li>
</ul>
<pre><code class="language-swift">// while 뒤에 조건을 입력하여 사용합니다. 조건이 true이면 코드블록을 실행하고 false이면 코드블록이 실행되지 않고 다음으로 넘어갑니다. 
while 조건 {
        // 반복 실행할 코드
}</code></pre>
<h4 id="코드스니펫-while-반복문-실습">[코드스니펫] while 반복문 실습</h4>
<pre><code class="language-swift">var happyNewYearCount = 10

while happyNewYearCount &gt; 0 {
    print(happyNewYearCount)
    happyNewYearCount -= 1
}

print(&quot;새해 복 많이 받으세요 :)&quot;)

/*
출력 값
10
9
8
7
6
5
4
3
2
1
새해 복 많이 받으세요 :)
*/

// 한국말로 풀어보기.

var happyNewYearCount = 10 
// happyNewYearCount라는 이름의 변수에 10을 할당합니다. 

while happyNewYearCount &gt; 0 { 
        // happyNewYearCount가 0보다 크다면 코드블록을 반복하여 실행합니다.
    print(happyNewYearCount) 
    happyNewYearCount -= 1 // happyNewYearCount에 1을 뺀 값을 할당합니다.
}

print(&quot;새해 복 많이 받으세요 :)&quot;)</code></pre>
<hr>
<pre><code class="language-swift">while true {
    print(&quot;결과가 항상 true여서 영원히 while 코드블록이 실행됩니다 🥲&quot;)
}

반복문에서 흐름제어를 도와주는 break continue(크고 진하게)
</code></pre>
<hr>
<h4 id="break">break</h4>
<ul>
<li>반복문이나 <strong>switch</strong>에서 즉시 종료하고, 코드블록을 빠져나올 때 사용됩니다.</li>
<li>반복문에서 더이상 반복이 필요 없을 때 사용합니다.</li>
<li><strong>switch</strong>에서 특정 <strong>case</strong>에서 아무런 작업을 하지 않거나, 중간에 작업을 종료하고 싶을 때 사용합니다.</li>
</ul>
<pre><code class="language-swift">// for 반복문에서 더이상 반복이 필요없는 경우

for age in 15...99 {
    print(age)

    if age &gt;= 20 {
        break
    }
}

/*
출력값 : 
15
16
17
18
19
20
*/

// 한국말로 풀어보기
for age in 15...99 { 
    // 15이상 99까지 반복하여 코드블록을 실행합니다. 현재 숫자는 age에 넣습니다.
    print(age)

    if age &gt;= 20 { // age가 20이상이면 코드블록을 실행합니다.
        break        // 반복문을 종료합니다.
    }
}</code></pre>
<hr>
<pre><code class="language-swift">// while 반복문에서 더이상 반복이 필요없는 경우

var age = 15
while age &lt; 100 {
    print(age)

    if age &gt;= 20 {
        break
    }

    age += 1
}

/*
출력값 : 
15
16
17
18
19
20
*/

// 한국말로 풀어보기
var age = 15 // age라는 이름의 변수에 값 15를 할당합니다.
while age &lt; 100 { // age가 100 미만일 때 반복적으로 코드블록을 호출합니다.
    print(age)

    if age &gt;= 20 { // 만약 age가 20보다 크거나 같을 때 아래 코드블록을 실행합니다.
        break // 반복문을 종료합니다.
    }

    age += 1 // age에 1을 더한 값을 할당합니다.
}</code></pre>
<hr>
<h4 id="코드스니펫-break문-실습">[코드스니펫] break문 실습</h4>
<pre><code class="language-swift">// 1. 조건문 switch에서 특정 case에서 아무런 행동을 하고싶지 않을 때

var age = 15

switch age {
    case 15: 
        break
    default: 
        print(&quot;15가 아니에요&quot;)
}

// case에서 아무런 행동을 하지 않을 때 코드가 없으면 빌드오류가 발생하기 때문에 break문을 사용합니다.

// 2. 조건문 switch에서 코드 중에 탈출하고 싶을 때

let score = 50
switch score {
case 50...:
    print(&quot;50점 이상이시군요&quot;)
    fallthrough
case 50:
    print(&quot;딱 50점이시네요?&quot;)
    break
    print(&quot;저는 80점인데요~&quot;)
default:
    print(&quot;공부를 더 하세요!&quot;)
}

/*
출력 값:
50점 이상이시군요
딱 50점이시네요?
*/

// 한국말로 풀어보기.
let score = 50 // score 라는 이름의 상수에 값 50을 할당합니다.

switch score { // score 상수를 switch 조건문 코드블록으로 넣습니다.
case 50...: // 50이상일 때 아래의 코드가 호출됩니다.
    print(&quot;50점 이상이시군요&quot;) 
    fallthrough // 아래의 case도 확인합니다.
case 50: // score가 50일 때 아래의 코드를 호출합니다.
    print(&quot;딱 50점이시네요?&quot;)   
    break // switch 코드블록
    print(&quot;저는 80점인데요~&quot;) // 위에서 break를 사용하여 switch에서 탈출하여 실행되지 않는 코드입니다.
default:
    print(&quot;공부를 더 하세요!&quot;)
}</code></pre>
<hr>
<h4 id="continue">continue</h4>
<ul>
<li>반복문 안에서 현재 반복을 건너뛰고 다음 반복으로 넘어갈 때 사용합니다.</li>
</ul>
<pre><code class="language-swift">// for in 에서 continue를 사용하여 반복문을 건너뛰는 방법

for number in 1...5 {
    if number % 2 == 0 {
        continue 
    }

    print(number)
}

/*
출력 값
1
3
5
*/

// 한국말로 풀어보기

for number in 1...5 { // 1이상 5이하까지 반복합니다. 현재 숫자는 number에 넣습니다.
    if number % 2 == 0 { // number 에서 2를 나눴을 때 나머지가 0이면 아래의 코드블록을 실행합니다.
        continue // 다음 반복문으로 바로 이동합니다. 아래의 코드는 실행하지 않습니다.
    }

    print(number)
}</code></pre>
<hr>
<pre><code class="language-swift">// while에서 continue를 사용하여 반복문을 건너뛰는 방법

var number = 0 

while number &lt; 5 {
    number += 1

    if number % 2 == 1 {
        continue
    }

    print(number)    
}

/*
출력 값 
2
4
*/

// 한국말로 풀어보기

var number = 0 // 변수 number에 0 값을 할당합니다.

while number &lt; 5 { // number가 5 미만일 때 반복합니다.
    number += 1 // number에 1을 더한값을 할당합니다.

    if number % 2 == 1 { // number에 2를 나눈 나머지 값이 1일 때 코드블록을 실행합니다.
        continue
    }

    print(number)    
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 문법종합 - 연산자]]></title>
            <link>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-%EC%97%B0%EC%82%B0%EC%9E%90-687fz9ky</link>
            <guid>https://velog.io/@nemo_/Swift-%EB%AC%B8%EB%B2%95%EC%A2%85%ED%95%A9%EB%B0%98-%EC%97%B0%EC%82%B0%EC%9E%90-687fz9ky</guid>
            <pubDate>Mon, 04 Nov 2024 12:20:15 GMT</pubDate>
            <description><![CDATA[<h3 id="5연산자">5.연산자</h3>
<ul>
<li>대입연산자, 산술연산자, 비교연산자, 논리연산자, 범위연산자</li>
</ul>
<hr>
<p><strong>대입연산자</strong></p>
<p><strong>=</strong> 연산자는 값을 변수나 상수에 대입할 때 사용합니다.</p>
<ul>
<li><strong>=</strong> 연산자 기준으로 뒤에 값을 앞에 대입(할당) 합니다.</li>
<li>변수의 데이터 타입이 같다면 다른 값으로 재할당할 수 있습니다.</li>
</ul>
<pre><code class="language-swift">let age = 20 // 해석 : age 라는 상수에 20을 대입(할당)합니다.
// 이렇게 하면 age에는 실제 값이 20이 들어가있습니다.

var name = &quot;내배캠&quot; // 해석 : name이라는 변수에 &quot;내배캠&quot; 이라는 값을 대입(할당)합니다.
var address = &quot;서울&quot; 

name = address // adress에 있는 값 서울을 name 변수에 할당합니다.
print(name) // name변수의 저장 값 &quot;서울&quot;이 출력됩니다. </code></pre>
<hr>
<p><strong>산술 연산자</strong></p>
<h4 id="더하기">더하기</h4>
<ul>
<li><strong>+</strong> : 두 값을 더합니다.</li>
<li><strong>+=</strong> : 기존 값에 더한 후 할당합니다.</li>
</ul>
<pre><code class="language-swift">var result = 10 + 20 // result 변수에 10 + 20의 값 30을 할당합니다.
print(result) // 출력 : 30

result += 3 // result에 3을 더한 후 값을 할당합니다.
print(result) // 출력 : 33

let value = 5
result += value // result에 value 값 더한 후 할당합니다.
print(result) // 38 </code></pre>
<hr>
<h4 id="-나누기">*** 나누기</h4>
<p>- <strong>/</strong> : 두 값을 나눕니다.</p>
<pre><code class="language-swift">var result = 30 / 7 // result 변수에 30 나누기 7의 값인 4를 할당합니다.

print(result) // 출력 : 4</code></pre>
<hr>
<h4 id="-곱하기">* 곱하기</h4>
<p>   - <strong>*** : 두 값을 곱합니다.
    - *</strong>=** : 기존 값에서 곱한 뒤 할당합니다.</p>
<pre><code class="language-swift">var result = 10 * 3 // result 변수에 10 * 3의 값 30을 할당합니다.
print(result) // 출력 : 30

result *= 2 // result 값에 2를 곱한 값을 할당합니다.
print(result) // 출력 : 60</code></pre>
<hr>
<p><strong>* 나머지</strong>
    - <strong>%</strong> : 두 값을 나눈 후의 나머지를 반환합니다.</p>
<pre><code class="language-swift">var result = 10 % 3 // result 변수에 10/3의 나머지값 1을 할당합니다.

print(result) // 출력 : 1</code></pre>
<hr>
<h3 id="비교-연산자">비교 연산자</h3>
<p>**두 값을 비교하여 참(true) 나 거짓(false)를 반환합니다.
예) </p>
<pre><code class="language-swift">let hello = &quot;안녕하세요&quot;
let hi = &quot;안녕&quot;

hello == hi // false &quot;안녕하세요&quot;와 &quot;안녕&quot;은 같지 않아요!
123 == 123 // true 123 과 123은 같은 값이에요.

hello != hi // true &quot;안녕하세요&quot;와 &quot;안녕&quot;은 같지 않아요.
123 != 123 // false 

123 &gt; 123 // false
100 &gt; 120 // false

123 &gt;= 123 // true

123 &lt; 123 // false
123 &lt;= 123 // true
100 &lt; 50 // true
</code></pre>
<hr>
<ul>
<li><strong>==</strong> : 값이 같은지 확인<ul>
<li><strong>a == b</strong> : a와 b가 같은지 확인</li>
<li><strong>1 == 1</strong> : 숫자 1과 숫자 1이 같은지 확인 → <strong>true</strong></li>
</ul>
</li>
<li><strong>!=</strong> : 두 값이 다른지 확인<ul>
<li><strong>a != b</strong> : a와 b가 같지 않은지 확인</li>
<li><strong>1 != 1</strong> : 숫자 1과 숫자 1이 같지 않은지 확인 → <strong>false</strong></li>
</ul>
</li>
<li>(<strong>&gt;</strong>) : 왼쪽의 값이 오른쪽 값보다 더 큰지 확인<ul>
<li><strong>a &gt; b</strong> : a가 b보다 큰지 확인</li>
<li><strong>1 &gt; 2</strong> : 1이 2보다 큰지 확인 → <strong>false</strong></li>
</ul>
</li>
<li><strong>&lt;</strong> : 왼쪽의 값이 오른쪽 값보다 더 작은지 확인<ul>
<li><strong>a &lt; b</strong> : a가 b보다 작은지 확인</li>
<li><strong>1 &lt; 2</strong> : 1이 2보다 작은지 확인 → <strong>true</strong></li>
</ul>
</li>
<li>(<strong>&gt;=</strong>) : 왼쪽의 값이 크거나 같은지 확인<ul>
<li><strong>a &gt;= b</strong> : a가 b보다 크거나 같은지 확인</li>
<li><strong>1 &gt;= 1</strong> : 1이 1보다 크거나 같은지 확인 → <strong>true</strong></li>
</ul>
</li>
<li><strong>&lt;=</strong> : 왼쪽의 값이 작거나 같은지 확인<ul>
<li><strong>a &lt;= b</strong> : a가 b보다 작거나 같은지 확인</li>
<li><strong>1 &lt;= 1</strong> : 1이 1보다 작거나 같은지 확인 → <strong>true</strong></li>
</ul>
</li>
</ul>
<hr>
<p><strong>논리 연산자 (Bool 값을 조작하거나 결합할 때 사용합니다)
*</strong> NOT 연산자
    * ! 키워드를 사용합니다.
        * !true 처럼 느낌표가 앞에 옵니다.
    * Bool 값을 반대로 반환하는 연산자
    * true → false
    * false → true</p>
<pre><code class="language-swift">var allowedEntry = false

print(allowedEntry) // false가 출력됩니다.

print(!allowedEntry) // false의 반대값인 true가 출력됩니다

allowedEntry = !allowedEntry // allowedEntry 변수에 allowedEntry의 값 false 의 반대인 true를 대입합니다.

print(allowedEntry) // true가 출력됩니다.</code></pre>
<hr>
<p><strong>* **AND</strong> 연산자
<em>*    \</em> 두개의 값을 비교하여 두개의 값이 모두 true 일 때 true를 반환하는 연산자
    * 한개가 true이고 한개가 false일 때는 false를 반환합니다.
    * &amp;&amp; 키워드를 사용합니다.</p>
<p>true &amp;&amp; true // true</p>
<p>true &amp;&amp; false // false</p>
<p>false &amp;&amp; false // false </p>
<p>let isHavingPan = false
let isHavingPineApple = true
let pineAppleApplePan = isHavingPan &amp;&amp; isHavingPineApple </p>
<p>print(pineAppleApplePan) 
// false isHavingPineApple이 false이기 때문에 전체값이 false가 됩니다.</p>
<hr>
<p><em>** OR 연산자
**    \</em> 두개의 값을 비교하여 한개만 true일 때 true를 반환하는 연산자
    * 한개가 true 이고 한개가 false일 때 true를 반환합니다.
    * || 키워드를 사용합니다.</p>
<p>true || true // true </p>
<p>true || false // true</p>
<p>false || false // false </p>
<p>let isGreenPan = false</p>
<p>let isYelloPan = true </p>
<p>let isHavingPan = isGreenPan || isYelloPan </p>
<p>print(isHavingPan) // true
// isGreenPan, isYellowPan 둘 중 하나가 true여서 true가 할당됩니다.</p>
<hr>
<p><strong>범위 연산자 (숫자나 값의 범위를 나타낼 때 사용하는 연산자입니다)</strong></p>
<ul>
<li>닫힌 범위 연산자(Closed Range Operator)<ul>
<li>a...b 와 같이 ... 를 사용합니다.</li>
<li>시작값과 마지막값을 모두 포함하는 범위를 정의할 때 사용합니다.</li>
<li>a...b a이상 b이하의 범위를 나타냅니다.</li>
</ul>
</li>
</ul>
<p>(0...3) // 0,1,2,3 의 범위로 0이상 3이하의 범위를 포함합니다.</p>
<p>(10...12) // 10, 11, 12 의 범위로 10이상 12이하의 범위를 포함합니다.</p>
<hr>
<p><em>** 반-열림 범위 연산자(Half-Open Range Operator)
**    
\</em> a..&lt;b 와 같이 ..&lt; 를 사용합니다.
    * 시작값은 포함되어 있고 마지막 값은 포함되어 있지 않은 범위를 정의합니다.
    * a..&lt;b a이상 b미만의 범위를 나타냅니다.</p>
<p>(0..&lt;3) // 0,1,2 의 범위</p>
<p>(10..&lt;15) // 10, 11, 12, 13, 14 의 범위를 포함합니다.</p>
<hr>
<p><em>** 단-방향 범위(One-Sided Ranges)
**    \</em> a... ...b 와 같이 한쪽에만 값을 넣어서 사용합니다.
    * 오로지 한쪽으로만 범위를 갖고 있습니다.</p>
<p>(10...) // 10, 11, 12, 13 ... </p>
<hr>
<p>*<em>삼항 연산자
*</em></p>
<ul>
<li>조건 ? 참일 때 반환값 : 거짓일 때 반환값 형태를 가진 연산자입니다.</li>
</ul>
<p>let age = 20
let isAdult = age &gt; 20 ? &quot;성인 입니다&quot; : &quot;미성년자 입니다&quot;</p>
<p>// 위에 코드를 번역해보면
// age 상수에 20 값을 할당합니다.
// isAdult 상수에 age가 20이면 &quot;성인입니다&quot;를 할당하고 아니면 &quot;미성년자 입니다&quot;를 할당합니다.</p>
<hr>
<p>*<em>Swift Convention Guide No.4 → 연산자 코드 컨밴션
*</em></p>
<ul>
<li>대입 연산자 , 산술 연산자, 비교 연산자 양옆으로 공백을 주세요.</li>
<li>논리연산자 NOT을 제외한 AND, OR 연산자에는 양옆으로 공백을 주세요.</li>
<li>범위 연산자는 공백을 안주셔도 됩니다.</li>
</ul>
<hr>
<p><strong>Code Block(코드블록)</strong></p>
<ul>
<li>Code Block이란 중괄호 { } 감싸진 코드의 묶음을 의미합니다.</li>
<li>여러 명령어를 하나로 묶어서 특정 조건이나 반복문 등에서 실행하는 단위로 사용됩니다.</li>
<li>주로 조건문, 반복문, 클래스, 클로저 등에서 사용하게 됩니다</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>