<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dx._xk7</title>
        <link>https://velog.io/</link>
        <description>프로그래머 아님</description>
        <lastBuildDate>Mon, 23 Sep 2024 21:16:42 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dx._xk7</title>
            <url>https://velog.velcdn.com/images/dx_xk7/profile/e331e75e-4103-42a6-be12-2311bae68fe1/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dx._xk7. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dx_xk7" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Apple SignIn에 대하여(with Firebase) #2]]></title>
            <link>https://velog.io/@dx_xk7/Apple-SignIn%EC%97%90-%EB%8C%80%ED%95%98%EC%97%ACwith-Firebase-2</link>
            <guid>https://velog.io/@dx_xk7/Apple-SignIn%EC%97%90-%EB%8C%80%ED%95%98%EC%97%ACwith-Firebase-2</guid>
            <pubDate>Mon, 23 Sep 2024 21:16:42 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요 오늘은 애플 로그인에 대해여 2번째 포스팅입니다</p>
<p>오늘 다룰 주제는 자동 로그인(Session)을 유지하는 방법에 대해 작성해볼려고 합니다</p>
<h2 id="기본-동작">기본 동작</h2>
<p>Apple 로그인 세션을 유지 할려면 Apple의 인증 상태를 앱 실행시 마다 체크를 해주어야 됩니다. 만료 되지 않은 세션이 있는지 검증을 해야됩니다. 사용자가 로그인을 하면 apple에서는 <code>userIdentifier</code> 가 발급이 됩니다, 이 값은 사용자를 식별하는데 도움이 됩니다.</p>
<p>이 값을 <code>UserDefaults</code> , <code>Coredata</code> 등에 저장해두고, 앱이 실행될 떄마다 이를 사용하여 세션 유효성을 검증할 수 있습니다</p>
<h2 id="구현">구현</h2>
<pre><code class="language-swift">func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
    if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
        let userIdentifier = appleIDCredential.user

        // userIdentifier를 UserDefaults에 저장
        UserDefaults.standard.set(userIdentifier, forKey: &quot;appleUserIdentifier&quot;)

        // 로그인 성공 처리
        print(&quot;Apple ID 로그인 성공, UserIdentifier: \(userIdentifier)&quot;)
    }
}</code></pre>
<p>로그인을 진행하고 로그인이 완료 되면 <code>appleIDCredential.user</code> </p>
<p>을 저장해줍니다.</p>
<p>앱이 실행될때 세션이 유효한지를 확인 하기위해서는</p>
<p><code>Appdelegate</code> 혹은 <code>SceneDelegate</code> 에서 체크를 해주어야 합니다 </p>
<p>저 같은 경우엔 <code>SceneDelegate</code> 에서 체크를 해줍니다</p>
<pre><code class="language-swift">func checkAppleSignInState(completion: @escaping (Bool) -&gt; Void) {
    let appleIDProvider = ASAuthorizationAppleIDProvider()

    // 저장된 사용자 ID 가져오기
    if let userIdentifier = UserDefaults.standard.string(forKey: &quot;appleUserIdentifier&quot;) {

        // Apple ID 상태 확인
        appleIDProvider.getCredentialState(forUserID: userIdentifier) { credentialState, error in
            switch credentialState {
            case .authorized:
                print(&quot;Apple ID 로그인 세션 유효&quot;)
                completion(true) // 로그인된 상태
            case .revoked, .notFound:
                print(&quot;Apple ID 로그인 세션 만료 또는 자격 없음&quot;)
                completion(false) // 로그인되지 않은 상태
            default:
                completion(false)
            }
        }
    } else {
        // 사용자 ID가 없으면 로그인되지 않은 상태로 처리
        completion(false)
    }
}</code></pre>
<p><code>UserDefaults</code> 에서 아까 저장해둔 데이터를 불러와주고 유효성을 체크합니다</p>
<p>저는 여기서 의문점이 생겼습니다. <code>appleIDCredential.user</code> 같은 경우에는 민감한 데이터인데 <code>UserDefaults</code> 에 암호화 없이 저장을 해도 될까 라는 생각이 들어 찾아보니 <code>appleIDCredential.user</code> 는 개인정보(전화번호, 이름, 주민번호)등의 민감한 정보가 아니라 <strong>사용자 식별용</strong> 데이터입니다 그러므로 저는 Userdefaults에 저장해도 된다고 판단 하였습니다 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Apple SiginIn에 대하여(with Firebase)]]></title>
            <link>https://velog.io/@dx_xk7/Apple-SiginIn%EC%97%90-%EB%8C%80%ED%95%98%EC%97%ACwith-Firebase</link>
            <guid>https://velog.io/@dx_xk7/Apple-SiginIn%EC%97%90-%EB%8C%80%ED%95%98%EC%97%ACwith-Firebase</guid>
            <pubDate>Thu, 12 Sep 2024 05:45:51 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요 오늘은 애플 로그인에 대해 글을 써볼려고 합니다. (파베도 써요 ㅎ)</p>
<p>본론으로 들어가기전 Nonce에 대해 설명을 짧게 하도록 하겠습니다.</p>
<h3 id="nonce란">Nonce란</h3>
<p>한번만 사용되는 문자열과 정수열의 난수를 말합니다 </p>
<p>통상적으로 암호화 통신에 사용이 되고 한번만 사용하기 때문에 짧은 시간내에 여러번의 공격</p>
<p>Replay Attack과 CSRF을 예방할 수 있습니다.</p>
<h2 id="1-loginviewcontroller">1. LoginViewController</h2>
<p>클래스 안에</p>
<pre><code class="language-swift">fileprivate var currentNonce: String?</code></pre>
<p>위와 같이 클래스 내에 선언 해주세요.</p>
<ul>
<li><p>로그인을 요청할 때 사용하는 임시 nonce 값(임의의 문자열을 저장합니다)</p>
<p>  해시화 후 apple로 보냅니다.</p>
</li>
</ul>
<h2 id="2-func-startsigninapple">2. <code>func startSignInApple()</code></h2>
<p>이 메서드는 apple 로그인 flow를 시작하는 핵심적인 부분 입니다.</p>
<p>이 메서드 안에는 <code>randomNonceString(length: )</code> , <code>sha256(_: )</code> </p>
<p>두가지 함수가 있습니다.</p>
<h3 id="randomnoncestring-">randomNonceString(: )</h3>
<p>이 메서드는 보안 목적으로 사용하는 Nonce를 무작위로 생성합니다</p>
<p>SecRandomCopyBytes: 시스템의 보안 난수 생성기를 사용해 랜덤한 바이트 배열을 생성합니다.</p>
<h2 id="sha246_-">sha246(_: )</h2>
<p>이 메서드는 문자열을 sha256으로 해시화 합니다</p>
<ul>
<li>Apple 로그인을 요청할 때, nonce를 가냥 보내는 것이 아니라 보안 강화를 위해 해시화한 값을 보내는 것입니다.</li>
</ul>
<h3 id="5-apple-로그인-성공-처리">5. <strong>Apple 로그인 성공 처리</strong></h3>
<p><code>ASAuthorizationControllerDelegate</code> 프로토콜을 채택해 Apple 로그인이 성공했을 때, 또는 실패했을 때 처리할 수 있습니다.</p>
<ul>
<li><code>didCompleteWithAuthorization()</code>: 로그인이 성공하면 호출됩니다. Apple로부터 받은 <code>identityToken</code>을 이용해 Firebase와 연동해 인증합니다.<ul>
<li><code>currentNonce</code>: 로그인 요청 시 생성한 nonce가 유효한지 확인합니다.</li>
<li><code>identityToken</code>: Apple에서 발급한 토큰으로 사용자 인증을 진행합니다.</li>
<li><code>OAuthProvider.appleCredential</code>: Firebase와 연동할 수 있는 인증 정보(credential)를 생성합니다.</li>
<li><code>Auth.auth().signIn</code>: Firebase에 이 인증 정보로 로그인 요청을 보냅니다.</li>
</ul>
</li>
</ul>
<h3 id="6-apple-로그인-실패-처리">6. <strong>Apple 로그인 실패 처리</strong></h3>
<ul>
<li><code>didCompleteWithError()</code>: 로그인이 실패했을 때 호출되며, 에러를 출력합니다</li>
</ul>
<h3 id="전체코드">전체코드</h3>
<pre><code class="language-swift">private extension LoginViewController {

    func startSignInApple() {
        let nonce = randomNonceString()
        currentNonce = nonce
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [ .fullName, .email]
        request.nonce = sha256(nonce)

        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()    }

    @available(iOS 13, *)
    func randomNonceString(length: Int = 32) -&gt; String {
        precondition(length &gt; 0)
        var randomBytes = [UInt8](repeating: 0, count: length)
        let errorCode = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &amp;randomBytes)
        if errorCode != errSecSuccess {
            fatalError(
                &quot;Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)&quot;
            )
        }
        let charset: [Character] =         Array(&quot;0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._&quot;)
        let nonce = randomBytes.map { byte in
            // 필요한 경우 세트에서 무작위로 캐릭터를 선택하여 랩을 감음
            charset[Int(byte) % charset.count]
        }
        return String(nonce)
    }

    func sha256(_ input: String) -&gt; String {
        let inputData = Data(input.utf8)
        let hashedData = SHA256.hash(data: inputData)
        let hashString = hashedData.compactMap {
            String(format: &quot;%02x&quot;, $0)
        }.joined()
        return hashString
    }
}
extension LoginViewController: ASAuthorizationControllerDelegate {
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDcredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            guard let nonce = currentNonce else {
                fatalError(&quot;Invalid state: 로그인 콜백이 수신되었지만 로그인 요청이 전송되지 않았습니다.&quot;)
            }
            guard let appleIDToken = appleIDcredential.identityToken else {
                print(&quot;식별토큰을 가져올 수 없습니다.&quot;)
                return
            }
            guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
                print(&quot;데이터에서 토큰 문자열을 직렬화 할 수 없음: \(appleIDToken.debugDescription)&quot;)
                return
            }
            // 사용자의 전체 이름을 포함한 Firebase 자격 증명을 초기화합니다.
            let credential = OAuthProvider.appleCredential(withIDToken: idTokenString, rawNonce: nonce, fullName: appleIDcredential.fullName)

            // Firebase에 로그인하기.
            Auth.auth().signIn(with: credential) { authResult, error in
                if let error = error {
                    print(&quot;Apple Signin Error: \(error.localizedDescription)&quot;)
                    return
                }
                //로그인에 성공했을 시 실행할 메서드
            }
        }
    }
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: any Error) {
        // 에러 핸들링
        print(&quot;Sign in with Apple errored: \(error)&quot;)
    }
}
extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -&gt; ASPresentationAnchor {
        // Apple 로그인 인증 창 띄우기
        return self.view.window ?? UIWindow()
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Delegate Pattern (Swift)]]></title>
            <link>https://velog.io/@dx_xk7/Delegate-Pattern-Swift</link>
            <guid>https://velog.io/@dx_xk7/Delegate-Pattern-Swift</guid>
            <pubDate>Sat, 03 Aug 2024 11:51:25 GMT</pubDate>
            <description><![CDATA[<h1 id="delegate-pattern딜리게이트-패턴">Delegate Pattern(딜리게이트 패턴)</h1>
<p>Delegate Pattern이란 <code>class, struct</code> 가 위임해 기능을 수행하는 방식입니다.</p>
<blockquote>
<p>솔직히 전 이해가 잘 안됩니다.</p>
</blockquote>
<p>코드를 먼저 볼게요.</p>
<pre><code class="language-swift">//부품 정의
protocol Parts: AnyObject {
    func makePart() -&gt; String
}

//자동차 공장 정의
class Factory {
    weak var delegate: Parts?
    init () {}

    func partsOrder() {
        guard let part = delegate?.makePart() else { return }
        print(&quot;주문했던 \(part)이 도착했습니다.&quot;)
    }
}
//부품 생성 정의

class Engine: Parts {
    func makePart() -&gt; String {
        &quot;engine&quot;
    }
}

let factory = Factory()
let engine = Engine()

factory.delegate = engine

factory.partsOrder() 
</code></pre>
<p>여기서 중요한 부분은 <code>Factory</code> 클래스를 수정하지 않고도 <code>print</code> 를 수정할 수 있는 장점이 있습니다</p>
<p>저는 자동차 공장이 돌아가는 시나리오로 코드를 작성해봤습니다.</p>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/78ca706e-dd29-49c5-84aa-4b8523d7d398/image.png" alt=""></p>
<p>위 사진을 보면 이해가 조금 쉬울것 같습니다.</p>
<ol>
<li><p><strong>필요 부품 정의</strong></p>
<p> 먼저 주문을 하기전에 필요한 부품이 뭔지를 알아야 주문을 할 수 있기 때문에 정의를 해줍니다.</p>
<p> 코드에서는 <code>makePart()</code> 함수는 <code>String</code> 타입을 반환하게 설계되었습니다.</p>
</li>
<li><p><strong>부품 주문</strong></p>
<p> 부품 주문 부분에서는 필요한 부품이 뭔지 확인했고 <code>makePart</code> 반환되는 <code>String</code> 사용합니다.</p>
</li>
<li><p><strong>부품 제작</strong></p>
<p> 부품 제작에서는 정의한 프로토콜에 맞게 <code>makePart</code> 함수를 사용해 리턴할 값을 저장해줍니다.</p>
</li>
<li><p><strong>부품 전달</strong></p>
<p> 전달에서는 <code>delegate</code> 를 위임합니다</p>
</li>
</ol>
<p>Delegate 패턴을 사용하면 매인 클래스의 수정을 최소화 하여 코드를 추가하고 수정이 용이하다고 생각을합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Singleton pattern (Swift)]]></title>
            <link>https://velog.io/@dx_xk7/Singleton-pattern-Swift</link>
            <guid>https://velog.io/@dx_xk7/Singleton-pattern-Swift</guid>
            <pubDate>Wed, 17 Jul 2024 08:10:04 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅은 싱글톤 패턴에 대해 알아보도록 하겠습니다</p>
<blockquote>
<p>개인적으로 꼭 알고 있어야 될 패턴이라고 생각을 합니다.</p>
</blockquote>
<h2 id="singleton-pattern이란">Singleton Pattern이란?</h2>
<p>싱글톤 패턴은 <strong>특정한 용도</strong>에 맞게 <strong>객체를 하나</strong>만 생성하여 공용으로 사용하고 싶을 때 사용하는 패턴입이다.<img src="https://velog.velcdn.com/images/dx_xk7/post/89ac1496-a105-41aa-9c2f-c2b36c7ea38d/image.png" alt=""></p>
<h2 id="예제">예제</h2>
<h3 id="싱글톤의-나쁜-예">싱글톤의 나쁜 예</h3>
<pre><code class="language-swift">class Employess {
    number: Int
    name: String
    address: String
}</code></pre>
<p><strong>employees 클래스를 만들고 사원에 정보를 담아줍니다</strong></p>
<pre><code class="language-swift">//NumberViewController.swift
let emp = Employess()
emp.number = 123

//NameViewController.swift
let emp = Employess()
emp.name = &quot;DeukRyoeng&quot;

//AddressViewController.swift
let emp = Employess()
emp = address = &quot;DeukRyoeng&quot;</code></pre>
<p>이런식으로 만들면 각 뷰마다 객체를 생성하여 총 3개의 객체가 생성됩니다</p>
<p>이렇게 각 ViewController에 Instance를 생성하게 되면 데이터 일관성에 문제가 생깁니다.</p>
<p>하나의 인스턴스를 만들어 어떤 클래스에서든 접근이 가능하도록 만드는것이 싱글톤 패턴입니다</p>
<h2 id="싱글톤-패턴의-좋은예">싱글톤 패턴의 좋은예?</h2>
<pre><code class="language-swift">class Employess {
    static let shared = Employess()
    number: Int
    name: String
    address: String

    private init() { }    
}</code></pre>
<p><code>static</code> 으로 전역 변수로 지정 해줍니다.</p>
<p><code>init</code> 함수 접근제어자를 <code>private</code> 으로 지정</p>
<p>다른 인스턴스 생성을 예방 해줍니다.</p>
<p>끝</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UserDefaults 과 CoreData 차이(Swift)]]></title>
            <link>https://velog.io/@dx_xk7/UserDefaults-%EA%B3%BC-CoreData-%EC%B0%A8%EC%9D%B4Swift</link>
            <guid>https://velog.io/@dx_xk7/UserDefaults-%EA%B3%BC-CoreData-%EC%B0%A8%EC%9D%B4Swift</guid>
            <pubDate>Sun, 14 Jul 2024 08:52:07 GMT</pubDate>
            <description><![CDATA[<h2 id="저장하는-방식들">저장하는 방식들</h2>
<h3 id="userdefaults">UserDefaults</h3>
<ol>
<li><p><strong>간단한 사용법</strong></p>
<p> UserDefaults는 사용하기 매우 간편합니다. Key - Value 쌍을 저장하고 불러오는 방식으로, 단순한 데이터 저장에 적합합니다.</p>
<pre><code class="language-swift"> UserDefaults.standard.set(&quot;value&quot;, forKey: &quot;key&quot;)
 let value = UserDefaults.standard.string(forKey: &quot;key&quot;)</code></pre>
</li>
<li><p><strong>자동 동기화</strong></p>
<p> UserDefaults에 저장된 데이터는 앱이 백그라운드로 들어갈 때 자동으로 동기화됩니다.</p>
</li>
</ol>
<h3 id="coredata">CoreData</h3>
<ol>
<li><p>복잡한 데이터 구조</p>
<p>  CoreData는 객체 그래프와 영속성을 관리할 수 있는 강력한 프레임워크입니다. 관계형 데이터베이스 모델처럼 복잡한 데이터 구조를 관리할 수 있습니다.</p>
</li>
<li><p>성능 최적화</p>
<p> CoreData는 내부적으로 SQLite를 사용하여 데이터를 관리하므로 대용량 데이터 처리가 효율적입니다.</p>
</li>
<li><p>데이터 쿼리</p>
<p> NSPredicate를 사용하여 복잡한 쿼리를 실행할 수 있습니다.</p>
<pre><code class="language-swift"> let fetchRequest: NSFetchRequest&lt;Entity&gt; = Entity.fetchRequest()
 fetchRequest.predicate = NSPredicate(format: &quot;attribute == %@&quot;, &quot;value&quot;)
 let results = try context.fetch(fetchRequest)</code></pre>
</li>
</ol>
<h3 id="결론">결론</h3>
<ul>
<li><strong>UserDefaults</strong>: 간단한 설정 값이나 작은 데이터를 저장할 때 유용합니다.</li>
<li><strong>CoreData</strong>: 복잡한 데이터 구조를 관리하고, 대용량 데이터를 처리하며, 관계형 데이터를 다룰 때 적합합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[PokemonAPI 받아오기]]></title>
            <link>https://velog.io/@dx_xk7/PokemonAPI-%EB%B0%9B%EC%95%84%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dx_xk7/PokemonAPI-%EB%B0%9B%EC%95%84%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Sun, 14 Jul 2024 08:39:44 GMT</pubDate>
            <description><![CDATA[<p>오늘은 API를 받아서 랜덤 이미지,이름을 만들겠습니다.</p>
<h2 id="친구목록-만들기">친구목록 만들기</h2>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/5e8f38e1-15d6-41cc-9558-c2342bf90067/image.png" alt=""></p>
<pre><code class="language-swift">//ContactCellView.swift
import UIKit
import SnapKit

class ContactCellView:UITableViewCell {

    static let identifier = &quot;ContactCell&quot;
    //전역변수로 선언

    let image:UIImageView = {
        let imageView = UIImageView()
        imageView.image = UIImage(systemName: &quot;person&quot;)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return  imageView

    }()

    let nameLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;파이리&quot;
        label.textColor = UIColor.black
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    let numberLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;010-0000-0000&quot;
        label.textColor = UIColor.black
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        addContentView()
        autoLayout()

    }

    required init?(coder: NSCoder) {
         fatalError(&quot;init(coder:) has not been implemented&quot;)
     }

    private func addContentView() {
        contentView.addSubview(image)
        contentView.addSubview(nameLabel)
        contentView.addSubview(numberLabel)
    }

    private func autoLayout() {
        image.snp.makeConstraints {
            $0.size.width.height.equalTo(80)
        }
        nameLabel.snp.makeConstraints {
            $0.leading.equalTo(image.snp.trailing).offset(30)
            $0.top.equalTo(30)


        }
        numberLabel.snp.makeConstraints {
            $0.leading.equalTo(nameLabel.snp.trailing).offset(50)
            $0.top.equalTo(30)

        }
    }

}</code></pre>
<h2 id="tableview">TableView</h2>
<pre><code class="language-swift">extension ViewController:UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {
        return 10
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: ContactCellView.identifier, for: indexPath) as! ContactCellView
        cell.image.image = UIImage(systemName: &quot;person.circle&quot;)
        cell.nameLabel.text = &quot;Test Title&quot;
        return cell
    }
}
extension ViewController:UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            print(&quot;select \(indexPath.row)&quot;)
        }
}
</code></pre>
<p>이제 Navigation Title을 만들어주겠습니다.</p>
<h2 id="viewcontroller">ViewController</h2>
<pre><code class="language-swift">title = &quot;친구 목록&quot;
        navigationController?.navigationBar.titleTextAttributes = [
            NSAttributedString.Key.font : UIFont.systemFont(ofSize: 20, weight: .bold),
            NSAttributedString.Key.foregroundColor : UIColor.black
        ]</code></pre>
<p>위 코드가 <code>ViewDidload</code> 에서 동작 되어야 합니다. 또한 추가적으로 <code>SceneDelegate</code>도 코드를 추가 해줘야 합니다.</p>
<h2 id="scenedelegate">SceneDelegate</h2>
<pre><code class="language-swift">let navigationContoller = UINavigationController(rootViewController: ViewController())</code></pre>
<p><code>RootView</code> 를 <code>navigationContoller</code>로 바꿔주시면 됩니다</p>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/2ac71aad-b999-499c-a150-948bfb2cb9d5/image.png" alt=""></p>
<p>다음으로는 <strong>NavigationBaritem</strong> 추가해주겠습니다.</p>
<pre><code class="language-swift">let addButton = UIBarButtonItem(title: &quot;추가&quot;, style: .plain, target: self, action: nil)
        addButton.tintColor = UIColor.gray
        navigationItem.rightBarButtonItem = addButton</code></pre>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/c39957a3-34d0-493d-a868-ecaa3f7a93f5/image.png" alt=""></p>
<p>이제 추가 버튼을 눌렀을때 연락처를 추가할 수 있는 <strong>View</strong>를 만들어 줘야합니다.</p>
<pre><code class="language-swift">import UIKit
import SnapKit

class PhoneBookViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        addSubView()
        setupNavigation()
        view.backgroundColor = .white
    }

    let image:UIImageView = {
        let imageView = UIImageView()
        imageView.image = UIImage(named: &quot;imageSP&quot;)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.layer.borderWidth = 2
        imageView.layer.cornerRadius = 90
        imageView.layer.masksToBounds = true
        imageView.layer.borderColor = UIColor.gray.cgColor
        return  imageView

    }()

    let randomButton: UIButton = {
        let button = UIButton()
        button.setTitle(&quot;랜덤이미지 변경&quot;, for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 20)
        button.setTitleColor(.gray,for: .normal)
        return button
    }()

    @objc func randomBtnTapped(_ sender: UIButton) {
        print(&quot;버튼 탭&quot;)
    }
}

extension PhoneBookViewController {
    private func addSubView() {
        [randomButton, image].forEach({view.addSubview($0)})
        self.randomButton.addTarget(self, action: #selector(randomBtnTapped(_: )), for: .touchUpInside)
        image.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.size.width.height.equalTo(180)
            $0.top.equalTo(130)
        }
        randomButton.snp.makeConstraints {
            $0.top.equalTo(image.snp.bottom).offset(40)
            $0.centerX.equalToSuperview()
        }
    }
    private func setupNavigation() {
        title = &quot;연락처 추가&quot;
        navigationController?.navigationBar.titleTextAttributes = [
            NSAttributedString.Key.font : UIFont.systemFont(ofSize: 20, weight: .bold),
            NSAttributedString.Key.foregroundColor : UIColor.black
        ]    }

}</code></pre>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/3ccf2d56-77d0-4df4-b17c-5c68dbba9493/image.png" alt=""></p>
<p>이름과, 전화번호를 입력할 수 있는 <strong>TextField</strong>를 추가해주겠습니다.</p>
<pre><code class="language-swift">let nameField: UITextField = {
        let field = UITextField()
        field.placeholder = &quot;이름을 입력해주세요.&quot;
        field.borderStyle = .roundedRect
        field.translatesAutoresizingMaskIntoConstraints = false
        return field
    }()

    let numberField: UITextField = {
        let field = UITextField()
        field.placeholder = &quot;이름을 입력해주세요.&quot;
        field.borderStyle = .roundedRect
        field.translatesAutoresizingMaskIntoConstraints = false
        return field
    }()
    nameField.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.trailing.equalTo(-20)
            $0.leading.equalTo(20)
            $0.height.equalTo(40)
            $0.top.equalTo(randomButton.snp.bottom).offset(30)
        }
        numberField.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.trailing.equalTo(-20)
            $0.leading.equalTo(20)
            $0.height.equalTo(40)
            $0.top.equalTo(nameField.snp.bottom).offset(10)
        }
</code></pre>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/0b608dfc-f6fb-43f4-b090-0e0c8d06e2d5/image.png" alt=""></p>
<p>이제는 번호가 입력되면 010-0000-0000 “ - “ 를 넣어주겠습니다.</p>
<p>포맷팅을 쉽게 하기 위하여 anyFormatKit을 사용하도록 하겠습니다.</p>
<pre><code class="language-swift">//viewDidload
numberfield.delegate = self

extension PhoneBookViewController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -&gt; Bool {

        guard let text = numberField.text else {
            return false
        }
        let characterSet = CharacterSet(charactersIn: string)
        if CharacterSet.decimalDigits.isSuperset(of: characterSet) == false {
            return false
        }

        let formatter = DefaultTextInputFormatter(textPattern: &quot;###-####-####&quot;)
        let result = formatter.formatInput(currentText: text, range: range, replacementString: string)
        textField.text = result.formattedText
        let position = textField.position(from: textField.beginningOfDocument, offset: result.caretBeginOffset)!
        textField.selectedTextRange = textField.textRange(from: position, to: position)
        return false
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/acb6f1ba-f2ce-4ba0-8ffa-4376c3fb263c/image.png" alt=""></p>
<blockquote>
<p>화면에 보이는 점들은 iOS 18에 있는 차량 모션큐 때문입니다. 차타고 여행가는도중에도 코딩을 합니다 !!!!!</p>
</blockquote>
<p>이제 <code>https://pokeapi.co/api/v2/pokemon/</code> 이 도메인 에서 랜덤버튼을 눌렀을때 이미지를 가져올려고 합니다. 먼저 어떤 데이터를 받아와야 되는지 Json 형태로 입니다 QuickType 통해서 Struct로 변환 하겠습니다.</p>
<h3 id="json-data">Json Data</h3>
<pre><code class="language-json">{
  &quot;id&quot;: 25,
  &quot;name&quot;: &quot;pikachu&quot;,
  &quot;height&quot;: 4,
  &quot;weight&quot;: 60,
  &quot;sprites&quot;: {
    &quot;front_default&quot;: &quot;https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png&quot;
  }
}</code></pre>
<blockquote>
<p><a href="https://app.quicktype.io/">https://app.quicktype.io/</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/2495534d-6dc1-46f5-83f1-075db6f15dda/image.png" alt=""></p>
<p>이제는 Get 메서드를 사용해서 이미지를 받아오겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/7147ef65-1a0c-4e8b-8559-395b5acd0dc6/image.png" alt=""></p>
<p>URL이 잘 나옵니다.</p>
<pre><code class="language-swift">import Foundation
import Combine

class ViewModel: NSObject {
    @Published var pokemon: PokeAPI?
    @Published var errorMessage: String?

    func fetchPokemonData(for id: Int, completion: @escaping (PokeAPI?) -&gt; Void) {
           let urlString = &quot;https://pokeapi.co/api/v2/pokemon/\(id)&quot;
           guard let url = URL(string: urlString) else {
               errorMessage = &quot;Invalid URL&quot;
               completion(nil)
               return
           }

           URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
               if let error = error {
                   DispatchQueue.main.async {
                       self?.errorMessage = &quot;Error: \(error.localizedDescription)&quot;
                       completion(nil)
                   }
                   return
               }

               guard let data = data else {
                   DispatchQueue.main.async {
                       self?.errorMessage = &quot;No data returned&quot;
                       completion(nil)
                   }
                   return
               }

               do {
                   let pokemon = try JSONDecoder().decode(PokeAPI.self, from: data)
                   DispatchQueue.main.async {
                       self?.pokemon = pokemon
                       completion(pokemon)
                   }
               } catch {
                   DispatchQueue.main.async {
                       self?.errorMessage = &quot;Decoding error: \(error.localizedDescription)&quot;
                       completion(nil)
                   }
               }
           }.resume()
       }
}</code></pre>
<p>위에 코드는 다음 글에서 자세히 설명하도록 하겠습니다.</p>
<h3 id="phonebookviewcontrollerswift">PhoneBookViewController.swift</h3>
<pre><code class="language-swift">//
//  PhoneBookViewController.swift
//  Pokemon_Contact
//
//  Created by DEUKRYEONG LEE on 7/12/24.
//

import UIKit
import SnapKit
import AnyFormatKit

class PhoneBookViewController: UIViewController {

    let getdata = GetData()

    let vm = ViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        addSubView()
        setupNavigation()
        view.backgroundColor = .white
        numberField.delegate = self


    }

    let image:UIImageView = {
        let imageView = UIImageView()
        imageView.image = UIImage(named: &quot;imageSP&quot;)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.layer.borderWidth = 3
        imageView.layer.cornerRadius = 90
        imageView.layer.masksToBounds = true
        imageView.layer.borderColor = UIColor.gray.cgColor
        return  imageView

    }()

    let randomButton: UIButton = {
        let button = UIButton()
        button.setTitle(&quot;랜덤이미지 변경&quot;, for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 15)
        button.setTitleColor(.gray,for: .normal)
        return button
    }()

    let nameField: UITextField = {
        let field = UITextField()
        field.placeholder = &quot;이름을 입력해주세요.&quot;
        field.borderStyle = .roundedRect
        field.translatesAutoresizingMaskIntoConstraints = false
        return field
    }()

    let numberField: UITextField = {
        let field = UITextField()
        field.placeholder = &quot;전화번호를 입력해주세요.&quot;
        field.borderStyle = .roundedRect
        field.keyboardType = .numberPad

        field.translatesAutoresizingMaskIntoConstraints = false
        return field
    }()

    @objc func randomBtnTapped(_ sender: UIButton) {
        fetchData()

    }
}

extension PhoneBookViewController {
    private func addSubView() {
        [randomButton, image, nameField, numberField].forEach({view.addSubview($0)})
        self.randomButton.addTarget(self, action: #selector(randomBtnTapped(_: )), for: .touchUpInside)
        image.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.size.width.height.equalTo(180)
            $0.top.equalTo(130)
        }
        randomButton.snp.makeConstraints {
            $0.top.equalTo(image.snp.bottom).offset(10)
            $0.centerX.equalToSuperview()
        }
        nameField.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.trailing.equalTo(-20)
            $0.leading.equalTo(20)
            $0.height.equalTo(40)
            $0.top.equalTo(randomButton.snp.bottom).offset(30)
        }
        numberField.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.trailing.equalTo(-20)
            $0.leading.equalTo(20)
            $0.height.equalTo(40)
            $0.top.equalTo(nameField.snp.bottom).offset(10)
        }
    }
    private func setupNavigation() {
        title = &quot;연락처 추가&quot;
        navigationController?.navigationBar.titleTextAttributes = [
            NSAttributedString.Key.font : UIFont.systemFont(ofSize: 20, weight: .bold),
            NSAttributedString.Key.foregroundColor : UIColor.black
        ]    }

}

extension PhoneBookViewController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -&gt; Bool {

        guard let text = numberField.text else {
            return false
        }
        let characterSet = CharacterSet(charactersIn: string)
        if CharacterSet.decimalDigits.isSuperset(of: characterSet) == false {
            return false
        }

        let formatter = DefaultTextInputFormatter(textPattern: &quot;###-####-####&quot;)
        let result = formatter.formatInput(currentText: text, range: range, replacementString: string)
        textField.text = result.formattedText
        let position = textField.position(from: textField.beginningOfDocument, offset: result.caretBeginOffset)!
        textField.selectedTextRange = textField.textRange(from: position, to: position)
        return false
    }
}

extension PhoneBookViewController {
    private func fetchData() {
        let randomID = Int.random(in: 1...1000)
        vm.fetchPokemonData(for: randomID) { [weak self] pokemon in
            guard let self = self, let pokemon = pokemon else {
                return
            }
            self.updateUI(with: pokemon)
        }
    }
    private func updateUI(with pokemon: PokeAPI) {
        nameField.text = &quot;\(pokemon.name)&quot;
//        heightLabel.text = &quot;Height: \(pokemon.height)&quot;
//        weightLabel.text = &quot;Weight: \(pokemon.weight)&quot;
        if let url = URL(string: pokemon.sprites.frontDefault) {
            loadImage(from: url)
        }
    }
    private func loadImage(from url: URL) {
           URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
               if let error = error {
                   DispatchQueue.main.async {
                       self?.showError(&quot;Image loading error: \(error.localizedDescription)&quot;)
                   }
                   return
               }

               guard let data = data, let image = UIImage(data: data) else {
                   DispatchQueue.main.async {
                       self?.showError(&quot;Failed to load image&quot;)
                   }
                   return
               }

               DispatchQueue.main.async {
                   self?.image.image = image
               }
           }.resume()
       }
    private func showError(_ message: String) {
            let alert = UIAlertController(title: &quot;Error&quot;, message: message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: &quot;OK&quot;, style: .default))
            present(alert, animated: true)
        }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[#1 iOS 주문앱 만들기(Team Project)]]></title>
            <link>https://velog.io/@dx_xk7/1-iOS-%EC%A3%BC%EB%AC%B8%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0Team-Project</link>
            <guid>https://velog.io/@dx_xk7/1-iOS-%EC%A3%BC%EB%AC%B8%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0Team-Project</guid>
            <pubDate>Mon, 08 Jul 2024 09:21:22 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. 이번에 진행했던 협업 프로젝트의 대해 회고 할려고 합니다 !</p>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/7af51af9-5f8a-4a85-984d-fc049babfb80/image.png" alt=""></p>
<h1 id="프로젝트-개요">프로젝트 개요</h1>
<p>프로젝트: <strong>MacM Coffee</strong> 주문앱
팀원소개:윤대성(팀장), 이득령, 김광현, 김솔비, 안지현</p>
<hr>
<h2 id="프로젝트-내용">프로젝트 내용</h2>
<p>이번에 진행한 프로젝트는 <strong>Mega Coffee</strong> 를 밴치마킹하여 제작된 앱입니다.</p>
<h2 id="사용기술">사용기술</h2>
<ul>
<li><h3 id="ui-configuration">UI configuration</h3>
UIView: 모든 UI 요소의 기본 클래스.
UIButton: 터치 이벤트를 처리하는 버튼.
UILabel: 텍스트를 표시하는 레이블.
UIImageView: 이미지를 표시하는 뷰.</li>
<li><h3 id="layout">Layout</h3>
UIStackView: 수직, 수평으로 뷰 정렬 컨테이너.
UIScrollView: 컨텐츠에 스크롤 기능을 추가해줌.
UICollectionView: 커스텀 레이아웃을 사용하여 데이터 항목을 표시하는 뷰.
UISegmentedControl: 여러 세그먼트 중 하나를 선택할 수 있는 컨트롤.</li>
<li><h3 id="layout-tools">Layout Tools</h3>
SnapKit: Codebase 코딩을 보다 쉽게 해줌.
AutoLayout: xcode에서 제공하는 서드파티 라이브러리.</li>
<li><h3 id="programming-tool">Programming, Tool</h3>
Swift: iOS 앱 개발에 사용되는 프로그래밍 언어.
Github: 코드 버전 관리를 위한 플랫폼.</li>
</ul>
<h3 id="wire-frame">Wire Frame</h3>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/eb5b943b-8ba7-4096-b356-abf0112212f8/image.png" alt="">
이번프로젝트에서 구현할 <strong>HomeView</strong>와 <strong>ModalView</strong>입니다 <img src="https://velog.velcdn.com/images/dx_xk7/post/4ae363e7-6e96-4ac1-b9f1-0fc8181f1931/image.png" alt="">
wireframe안에 <strong>RGB</strong> 박스들이 있는데요,
1 .파란색 박스는 App 상단로고 및 카테고리뷰를 담당합니다.
2 .빨간색 박스는 App 안에서 메뉴를 보여주고 선택이 될 수 있게 해주는 역할을 담당합니다
3. 마지막으로 초록색 박스는 위에 메뉴를 주문이 된 것들을 모아서 보여줍니다.</p>
<h3 id="팀원-역할">팀원 역할</h3>
<ul>
<li>김광현님 : 파란색 박스에 있는 <strong>카테고리뷰</strong>와 이미지를 담당했습니다</li>
<li>윤대성님, 박솔비님: 빨간색 박스안에 있는 <strong>콜렉션뷰</strong>를 만드는 역할을 하였습니다</li>
<li>이득령, 안지현님: 초록색 박스에 있는 <strong>ModalView, TabelView</strong> 담당 했습니다.</li>
</ul>
<h2 id="ground-rule">Ground Rule</h2>
<h3 id="github">gitHub</h3>
<ul>
<li><strong>Push</strong>후 팀원중 둘 이상이 <strong>Approve</strong> 해야지만 PR이 되게 설정</li>
<li><strong>Push</strong> 하기전 본인 브랜치에 Psuh 후 Main 브랜치가 아닌 Dev 브랜치에 <strong>Psuh</strong></li>
<li><strong>PR</strong> 리뷰 하기<h3 id="팀원">팀원</h3>
</li>
<li><strong>Error, Bug</strong>는 즉시 공유, 밑 공동으로 해결</li>
<li>나의 브랜치가 아닌 다른팀원 브랜치에 Push 금지</li>
</ul>
<p>이렇게 프로젝트 진행전에 간단한 Wireframe 밑 Ground Rule을 설정했습니다 .</p>
<blockquote>
<p><strong>끝</strong> <em>((참고로 프로젝트 끝나고 회고 하는거에용~!))</em></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[고차함수(swift)]]></title>
            <link>https://velog.io/@dx_xk7/%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98swift-rlri41zn</link>
            <guid>https://velog.io/@dx_xk7/%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98swift-rlri41zn</guid>
            <pubDate>Tue, 18 Jun 2024 08:25:36 GMT</pubDate>
            <description><![CDATA[<p>고차함수 <code>map</code>  <code>filter</code>  <code>reduce</code> 에 대해 알아 보겠습니다</p>
<blockquote>
<p>본론에 앞서 <code>$0</code> 은 고차함수에서 사용되는 클로저가 처리 하고 있는 현재의 요소를 나태냈습니다</p>
</blockquote>
<h3 id="map">map</h3>
<p><code>map</code>  함수는 기존 컬렉션 안에 있는 데이터를 가공해서 새로운 컬렉션을 만들어줍니다</p>
<p>예시코드</p>
<pre><code class="language-swift">var arr = [1, 2, 3, 4, 5]

var str = [String]()

str = arr.map {
    if let intToStr = String?(&quot;\($0)&quot;) {
        return intToStr
    }
    return &quot;&quot;
}
print(&quot;\(str)&quot;)</code></pre>
<ol>
<li><code>arr</code> 에는 정수 5개가 들어있습니다</li>
<li><code>str</code> 배열을 타입은 문자열입니다</li>
<li>배열타입안에 정수 타입 데이터를 문자열로 변경할려고 합니다.</li>
</ol>
<h3 id="filter">filter</h3>
<p><code>filter</code> 는 배열에 조건을 만족하는것 들만 모아서 새로운 배열을 만듭니다</p>
<p>예시코드</p>
<pre><code class="language-swift">var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let result = arr.filter{ $0 % 2 == 0 }</code></pre>
<ol>
<li><code>arr</code> 배열에는 1 부터 10 까지 정수가 들어있습니다 </li>
<li><code>result</code> 상수는 <code>arr</code> 배열 요소중 2로 나누었을때 나머지가 0인 요소만 저장합니다.</li>
</ol>
<h3 id="reduce">reduce</h3>
<p><code>reduce</code> 는 컨테이너 안에 모든 요소를 결합하면 배출하는 고차함수 입니다</p>
<p>예시코드</p>
<pre><code class="language-swift">var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let sum = arr.reduce(0) { $0 + $1 }</code></pre>
<ol>
<li><code>arr</code> 배열안에는 1 부터 10까지 정수가 들어 있습니다</li>
<li><code>sum</code> 은 <code>arr</code> 배열안에 있는 모든 수를 합친 값을 저장합니다.</li>
</ol>
<ul>
<li><p>설명</p>
<p>  <code>reduce(0){ $0 + $1 }</code> </p>
<p>  처음에는 $0 = 입니다 왜냐하면 <code>reduce(0)</code> 값의 0이라는 값이 있기 때문입니다.</p>
<p>  <code>$1</code> 은 에는 1이 들어갑니다 왜냐면 <code>arr</code> 의 첫번째 값은 1이기 때문입니다 </p>
<p>  <code>$1</code> 에 있던 값은 다시 <code>$0</code> 으로 되돌아갑니다 <code>$1</code> 의 데이터는 2가 됩니다 </p>
<p>  위에 동작들은 10이 배열에 모든 값들이 더해지면 끝납니다</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[숫자 야구게임(Swift)]]></title>
            <link>https://velog.io/@dx_xk7/%EC%88%AB%EC%9E%90-%EC%95%BC%EA%B5%AC%EA%B2%8C%EC%9E%84Swift</link>
            <guid>https://velog.io/@dx_xk7/%EC%88%AB%EC%9E%90-%EC%95%BC%EA%B5%AC%EA%B2%8C%EC%9E%84Swift</guid>
            <pubDate>Mon, 17 Jun 2024 07:49:29 GMT</pubDate>
            <description><![CDATA[<p>오늘은 야구게임을 만들어 보겠습니다.</p>
<p>야구게임이 뭔지는 잘 찾아보세요.</p>
<p>먼저 클래스 선언부터 해주겠습니다.</p>
<pre><code class="language-swift">class BaseballGame {

}</code></pre>
<h3 id="랜덤숫자-생성">랜덤숫자 생성</h3>
<p>다음으로는 3자리 숫자를 중복없이 1 부터 9까지 숫자를 뽑아주는 함수를 만들어 줄게요</p>
<p><strong>함수선언</strong></p>
<pre><code class="language-swift">func makeAnswer() -&gt; Int { }</code></pre>
<p>이 함수는 반환타입이 <code>Int</code> 입니다.</p>
<p><strong>변수선언 밑 초기화</strong></p>
<pre><code class="language-swift">var number = [Int]()</code></pre>
<p>비여있는 정수배열을 만들어 주고 여기에 랜덤숫자 3개를 저장할 것입니다.</p>
<p><strong>3개의 숫자 생성</strong></p>
<pre><code class="language-swift">while number.count &lt; 3 { }</code></pre>
<ul>
<li><p><strong>랜덤숫자 생성</strong></p>
<pre><code class="language-swift">  let num = Int.random(in: 1...9)</code></pre>
<p>  <code>num</code> 이라는 상수를 만들어주고 ‘1’ 부터 ‘9’ 까지의 수를 <code>num</code> 에 저장합니다.</p>
<p>  <strong>랜덤숫자 중복 검사</strong></p>
<pre><code class="language-swift">  if !number.contains(num) {
      number.append(num)
  }</code></pre>
<blockquote>
<p><strong>contains은 컬렉션타입에서 특정 요소가 있는지 Bool 타입으로 값을 내는 메서드</strong></p>
</blockquote>
<p>  <code>number</code> 배열에 <code>num</code> 이 포함 되어있는지 확인 하고 값을 포함 합니다.</p>
</li>
</ul>
<p><strong>배열을 정수로 변환 밑 반환</strong></p>
<pre><code class="language-swift">return number.reduce(0) { $0 * 10 + $1 }</code></pre>
<blockquote>
<p><strong>reduce</strong>는 배열의 모든 요소를 <strong>결합하여 하나의 값</strong>으로 만드는 함수 입니다.</p>
</blockquote>
<p><code>return</code> 과 동시에 배열을 숫자로 변환 해줍니다.  (임의 숫자 [4,3,2])</p>
<p><code>number = [4, 3, 2]</code> 를 reduce를 통해 3자리 숫자로 만들어줍니다</p>
<ul>
<li><p><strong>계산 방식:</strong></p>
<p>  초기값을 <code>0</code> 으로 잡습니다.</p>
<p>  계산방식은 $0 * 10 + $1입니다.</p>
<p>  <strong>$0</strong> 초기값인 0이 들어가고 <strong>$1</strong>에는 number의 처음 값인 4가 들어갑니다</p>
<p>  0 + 10 * 4 = <strong>4</strong></p>
<p>  이렇게 계산이 됩니다.</p>
</li>
</ul>
<h3 id="유저입력-처리">유저입력 처리</h3>
<p><strong>입력 요청</strong></p>
<pre><code class="language-swift">print(&quot;3자리 숫자를 입력하세요 (예: 123): &quot;, terminator: &quot;&quot;)</code></pre>
<p>User에게 3자리 숫자를 입력 하도록 요청합니다</p>
<p><code>terminator: &quot;&quot;</code> 은 출력후 줄 바꿈으 ㄹ하지 않도록 설정합니다.</p>
<p><strong>유효성 감사</strong></p>
<pre><code class="language-swift">guard let input = readLine(), input.count == 3, let number = Int(input) else {
    print(&quot;유효하지 않은 입력입니다. 3자리 숫자를 입력해주세요.&quot;)
    return nil
}</code></pre>
<ul>
<li><code>readLine()</code> 을 사용하여 User 입력을 읽습니다</li>
<li><code>input.count == 3</code> 는 입력된 문자열의 길이가 3인지 확인합니다.</li>
<li><code>let number = Int(input)</code> 는 입력된 문자열이 정수로 변환 가능한지 확인합니다</li>
</ul>
<p><strong>문자열을 배열로 배열로 변환</strong></p>
<pre><code class="language-swift">let digits = Array(String(number)).compactMap { $0.wholeNumberValue }</code></pre>
<ul>
<li><code>String(number)</code> 는 숫자를 문자열로 변환합니다.</li>
<li><code>Array(String(number))</code> 는 문자 배열로 변환합니다.</li>
<li><code>compactMap { $0.wholeNumberValue }</code> 는 각 문자를 정수로 변환하여 새로운 배열을 만듭니다</li>
</ul>
<p><strong>확인 및 반환</strong></p>
<pre><code class="language-swift">return digits.count == 3 ? digits : nil</code></pre>
<ul>
<li>변환한 배열 <code>digits</code> 의 길이가 3인지 확인합니다.</li>
<li>길이가 3이면 배열을 반환하고, 그렇지 않으면 <code>nil</code> 을 반환합니다.</li>
</ul>
<h3 id="스트라이크-볼-추론">스트라이크, 볼 추론</h3>
<p><strong>함수 만들기</strong> </p>
<pre><code class="language-swift">func calculateScore(secret:[Int], guess:[Int]) -&gt;(strikes:Int, balls: Int)</code></pre>
<ul>
<li>이 함수는 두개의 정수 배열인 <code>secret, guess</code> 를 인자로 받습니다.</li>
<li>반환타입은 <code>(strikes: Int, balls: Int)</code> 로 스트라이크와 볼의 수를 반환합니다.</li>
</ul>
<p><strong>초기화</strong></p>
<pre><code class="language-swift">var strikes = 0
var balls = 0</code></pre>
<ul>
<li><code>strikes, balls</code> 변수를 각각 0으로 초기화합니다.</li>
<li><code>strikes</code> 는 정확한 위치와 값을 맞춘 횟수를 <code>balls</code> 는 값은 맞췄지만 위치가 틀린 횟수를 의미합니다.</li>
</ul>
<p><strong>반복문, 인덱스 열거</strong></p>
<pre><code class="language-swift">for (i, num) in guess.enumerated() { }</code></pre>
<blockquote>
<p><strong>enumerated</strong> 메서드는 시퀀스(배열, 문자열 등) 을 각 요소와 해당 요소의 인덱스를 함께 튜플로 반환함</p>
</blockquote>
<blockquote>
<p>열거는 여러 개의 항목이나 요소를 하나씩 차례로 나열, 카운트 하는것</p>
</blockquote>
<ul>
<li><code>guess</code> 배열을 열거하여 각 요소의 인덱스와 값을 가져옵니다.</li>
</ul>
<p><strong>시크릿 숫자 배열에 포함 여부 확인</strong></p>
<pre><code class="language-swift">if secret.contains(num) { }</code></pre>
<ul>
<li><code>num</code> 이 <code>secret</code> 배열에 포함되어 있는지 확인합니다.</li>
<li>포함되어 있으면 다음 조건을 확인합니다.</li>
</ul>
<p><strong>스트라이크와 볼 확인</strong></p>
<pre><code class="language-swift">if secret[i] == num {
    strikes += 1
} else { 
    balls += 1 
}</code></pre>
<ul>
<li><code>secret[i] == num</code> 조건은 <code>guess</code> 배열의 현제 인덱스 <code>i</code> 에서의 값 <code>num</code> 이 배열의 같은 인텍스에 있는 값과 일치 하는지 확인합니다.</li>
<li>일치하면 <code>strikes</code> 를 1증가 시킵니다.</li>
<li>일치하지 않으면 <code>ball</code> 를 1증가 시킵니다.</li>
</ul>
<h3 id="전체코드">전체코드</h3>
<pre><code class="language-swift">//MARK: - BaseballGame
class BaseballGame {
    //MARK: - 전역변수 선언
    var functionCallCount = 0
    var gameCount = [Int]()
    var currentNumber = 1
    var tryCount = [Int]()
    //MARK: - 랜덤숫자 만들기
    func makeAnswer() -&gt; Int {
        var number = [Int]()
        while number.count &lt; 3 {
            var num: Int = 0
            if (number.isEmpty) {
                num = Int.random(in: 1...9)
            } else {
                num = Int.random(in: 0...9)
            }
            if !number.contains(num) {
                number.append(num)
            }
        }
        return number.reduce(0) { $0 * 10 + $1 }
    }
    //MARK: - 유저입력 처리
    func getUserInput() -&gt; [Int]? {
        print(&quot;3자리 숫자를 입력하세요 (예: 123): &quot;, terminator: &quot;&quot;)
        guard let input = readLine(), input.count == 3, let number = Int(input) else {
            print(&quot;유효하지 않은 입력입니다. 3자리 숫자를 입력해주세요.&quot;)
            return nil
        }

        let digits = Array(String(number)).compactMap { $0.wholeNumberValue }
        return digits.count == 3 ? digits : nil
    }

    //MARK: - 스트라이크, 볼 추론
    func calculateScore(secret:[Int], guess:[Int]) -&gt;(strikes:Int, balls: Int) {
        var strikes = 0
        var balls = 0

        for (i, num) in guess.enumerated() {
            if secret.contains(num) {
                if secret[i] == num {
                    strikes += 1
                } else { balls += 1 }
            }

        }
        return (strikes, balls)
    }
    //MARK: - 게임 구동
    func Start() {
        functionCallCount += 1
        let secretNumber = Array(String(makeAnswer())).compactMap { $0.wholeNumberValue }
        var attempts = 0
        var isRunning = true
        print(secretNumber)

        while isRunning {
            attempts += 1

            guard let userInput = getUserInput() else { continue }

            let score = calculateScore(secret: secretNumber, guess: userInput)
            print(&quot;스트라이크: \(score.strikes), 볼: \(score.balls)&quot;)
            if score.strikes == 3 {
                print(&quot;축하합니다! \(attempts)번 만에 맞추셨습니다.&quot;)
                print(&quot;&lt; 숫자 게임이 끝났습니다. &gt;&quot;)
                gameCount.append(currentNumber)
                currentNumber += 1
                tryCount.append(attempts)
                isRunning = false
                MainMenu()
            }

        }
    }
    //MARK: - 게임 시작 메뉴
    func MainMenu() {
        var isRunning = true
        while isRunning {
            if functionCallCount == 0 {
                print(&quot;환영합니다! 원하시는 번호를 입력해주세요&quot;)
                print(&quot;1. 게임 시작하기  2. 게임 기록 보기  3. 종료하기&quot;)
                print(&quot;선택: &quot;, terminator: &quot;&quot;)
            } else {
                print(&quot;1. 게임 시작하기  2. 게임 기록 보기  3. 종료하기&quot;)
                print(&quot;선택: &quot;, terminator: &quot;&quot;)
            }
            guard let choice = readLine(), let option = Int(choice) else {
                print(&quot;유효하지 않은 입력입니다. 다시 시도해주세요.&quot;)
                continue
            }
            switch option {
            case 1:
                print(&quot;게임을 시작합니다!&quot;)
                Start()

            case 2:
                print(&quot;게임 기록을 보여줍니다.&quot;)
                showGameLog()
            case 3:
                print(&quot;&lt; 숫자 게임을 종료합니다. &gt;&quot;)
                isRunning = false
            default:
                print(&quot;유효하지 않은 선택입니다. 다시 시도해주세요.&quot;)
            }
        }
    }
    //MARK: - 게임기록확인 함수
    func showGameLog() {
        for(num1, num2) in  zip(gameCount, tryCount) {
            print(&quot;\(num1)번째 게임 : 시도 횟수 - \(num2)&quot;)
        }
    }

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[No Storyboard Settings(with canvas)]]></title>
            <link>https://velog.io/@dx_xk7/No-Storyboard-Settingswith-canvas</link>
            <guid>https://velog.io/@dx_xk7/No-Storyboard-Settingswith-canvas</guid>
            <pubDate>Thu, 06 Jun 2024 12:04:31 GMT</pubDate>
            <description><![CDATA[<p>오늘은 스토리보드 없는 UiKit을 세팅해보겠습니다. 스토리보드가 없으면 뭐가 좋은지도 알아 보겠습니다.</p>
<h2 id="no-storyboard-장단점">No Storyboard 장단점</h2>
<p><strong>장점:</strong></p>
<ul>
<li><p>버전 관리 및 병합 충돌 방지</p>
<p>  코드 기반 UI는 텍스트 파일 혙애로 관리되기 때문에 Git 등의 버전 관리 시스템 에서 변경 사항 추적하고 병합하는 것이 용이합니다 반면 스토리보드는 XML 파일로 병합 충돌이 발생하면 해결하기 어렵습니다.</p>
</li>
<li><p>재사용성과 모듈화</p>
<p>  코드로 작성된 UI 컴퍼넌트는 쉽게 재사용이 가능, 별도 파일이나 클래스로 분리하여 모듈화가 가능합니다.</p>
</li>
</ul>
<p><strong>단점:</strong></p>
<ul>
<li><p>디자이너와의 협업 문제</p>
<p>  디자이너와의 협업 시 스토리보드르 ㄹ사용하면 디자이너가 직접 UI를 수정하거나 검토할 수 있지만 코드 기반 접근 방식에서는 개발자가 직접 코드를 수정해야 하므로 협업이 어려울 수 있음</p>
</li>
<li><p>코드 증가</p>
<p>  코드 양 증가 모든 UI를 코드로 작성하면 프로젝트의 코드 양이 급격히 증가하여 코드베이스가 커지고 복잡해질 수 있습니다. 이는 유지보수와 관리에 부답이 될 수 있습니다.</p>
</li>
</ul>
<h2 id="no-storyboard-setting">No Storyboard Setting</h2>
<ol>
<li>Interface를 stotyboard로 설정해주고 파일을 만들어주세요</li>
</ol>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/5d2d89dd-6c4b-43b1-bbb8-4c25a9271fff/image.png" alt=""></p>
<ol>
<li><p><strong>Info → Application Scene Manifest → Scene Configuration → Window Application Session Role → Item 0 → Storyboard Name</strong> 경로로 오셔서 <strong>Storyboard</strong> <strong>Name</strong>을 삭제해주세요 
<img src="https://velog.velcdn.com/images/dx_xk7/post/6c9beb09-b1dc-4716-9d31-b46de27b68f5/image.png" alt=""></p>
</li>
<li><p><strong>Main.storyboard</strong>를 삭제해주세요.</p>
</li>
<li><p>supports multiple windows로 클릭해주세요
<img src="https://velog.velcdn.com/images/dx_xk7/post/1c52999c-4033-4eb6-923d-361260bd0fd9/image.png" alt=""></p>
</li>
</ol>
<ol start="4">
<li><p><strong>Main storyboard file base name</strong> 의 <strong>Value</strong>를 삭제해주세요</p>
<p> <img src="https://velog.velcdn.com/images/dx_xk7/post/9822c4aa-bf75-4271-a193-ad0dfc6648c2/image.png" alt=""></p>
</li>
</ol>
<ol start="5">
<li><p><strong>SceneDelegate</strong>로 이동해주세요 <strong>SceneDelegate</strong> 안에 있는 </p>
<pre><code class="language-swift"> func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)</code></pre>
<p> 이 함수 안에 있는 </p>
<pre><code class="language-swift"> guard let _ = (scene as? UIWindowScene) else { return }</code></pre>
<p> 삭제 혹은 주석처리해주세요 그리고 </p>
<pre><code class="language-swift">  guard let windowScene = (scene as? UIWindowScene) else { return }

         window = UIWindow(frame: UIScreen.main.bounds)

         let viewContoller = ViewController() // RootView 입니다 

         window?.rootViewController = viewContoller

         window?.makeKeyAndVisible()
         window?.windowScene = windowScene</code></pre>
<p> 이코드를 넣어주세요.</p>
<p> <img src="https://velog.velcdn.com/images/dx_xk7/post/37dd55c9-045f-4e64-b21b-2e2ea3fa24e5/image.png" alt=""></p>
</li>
</ol>
<pre><code>이렇게 해주시면 끝 입니다 !!</code></pre><h2 id="canvas-추가하는-법"><strong>Canvas 추가하는 법</strong></h2>
<p>ViewController 아래 이코드를 추가해주세요.</p>
<pre><code class="language-swift">struct ViewControllerRepresentable: UIViewControllerRepresentable {
    typealias UIViewControllerType = ViewController

    func makeUIViewController(context: Context) -&gt; ViewController {
        return ViewController()
    }

    func updateUIViewController(_ uiViewController: ViewController, context: Context) {
    }
}

@available(iOS 13.0.0, *)
struct ViewPreview: PreviewProvider {
    static var previews: some View {
        ViewControllerRepresentable()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/63280eaa-c9b1-4d56-b62b-b27e63805500/image.png" alt=""></p>
<p>끝</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[계산기 만들기(추상화클래스)]]></title>
            <link>https://velog.io/@dx_xk7/%EA%B3%84%EC%82%B0%EA%B8%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0%EC%B6%94%EC%83%81%ED%99%94%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@dx_xk7/%EA%B3%84%EC%82%B0%EA%B8%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0%EC%B6%94%EC%83%81%ED%99%94%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Wed, 05 Jun 2024 11:55:54 GMT</pubDate>
            <description><![CDATA[<p>오늘은 추상화클래스를 활용해서 사측연산이 가능한 프로그램을 구현 해봤습니다.</p>
<h2 id="추상화-클래스">추상화 클래스</h2>
<p>추상화 클래스는 코드 재사용성이 좋습니다 또한 코드의 일관성유지가 용이합니다.</p>
<h3 id="추상화-프로토콜-생성">추상화 프로토콜 생성</h3>
<pre><code class="language-swift">protocol Operation {
    func performOperation(a: Double, b: Double) -&gt; Double
}
</code></pre>
<p><code>Operation</code> 라는 프로토콜을 생성해줍니다. 이것은 나중에 사측연산에 필요한 기본값을 정의 해주는 작업입니다.</p>
<p>코드를 보면 파라미터 a, b가 있습니다 a, b의 타입은 Double이고 반환 타입 또한 Double입니다.</p>
<p>이를 통해 추후에 <code>Operation</code> 프로토콜을 상송받은 클래스가 <code>performOperation</code> 함수를 준수할 수 있게 해서 코드를 재사용성을 높입니다.</p>
<h3 id="구체적인-연산-클래스add-sub-mult-div">구체적인 연산 클래스(Add, Sub, Mult, Div)</h3>
<p>추상화 프로토콜을 활용해 추상적이지 않고 구체적으로 무엇을 하는 코드인지 정해줄 것입니다.</p>
<pre><code class="language-swift">class Add: Operation {
    func performOperation(a: Double, b: Double) -&gt; Double {
        return a + b
    }
}</code></pre>
<p><code>Add</code> 클래스는 <code>Operation</code> 을 상속받습니다 파미리터 a, b의 값을 받아와서 <code>return</code> 시 연산을 진행 시켜줍니다.</p>
<h3 id="계산기-클래스cal">계산기 클래스(Cal)</h3>
<p>위에서 만든 구체적인 연산 클래스를 활용하여 계산기 클래스를 만들어 보겠습니다.</p>
<pre><code class="language-swift">class Cal {
    var operation: Operation
    init(operation: Operation) {
        self.operation = operation
    }
    func calculate(a: Double, b: Double) -&gt; Double {
        return operation.performOperation(a: a, b: b)
    }
}</code></pre>
<blockquote>
<p><strong>인니셜라이즈</strong>의 역할은 그저 인스턴스의 첫 사용을 위해 초기화 하는것 뿐입니다.</p>
</blockquote>
<pre><code class="language-swift">init(operation: Operation)</code></pre>
<p>이 부분은 추후에 사용할 객체를 실행하기 위해 초기설정을 해주는 겁니다.</p>
<h3 id="정리">정리</h3>
<ul>
<li><strong>Operation Protocol</strong>: 사측연산을 정의하는 규칙</li>
<li><strong>Add, Sub, Mult, Div Class:</strong> Operation 프로토콜을 따는 구체적인 연산 클래스입니다.</li>
<li><strong>Cal Class</strong>:Operation 프로토콜을 따르는 객체를 활용하여 연산을 수행하는 클래스입니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift(다중 패러다임 지향언어)]]></title>
            <link>https://velog.io/@dx_xk7/Swift%EB%8B%A4%EC%A4%91-%ED%8C%A8%EB%9F%AC%EB%8B%A4%EC%9E%84-%EC%A7%80%ED%96%A5%EC%96%B8%EC%96%B4</link>
            <guid>https://velog.io/@dx_xk7/Swift%EB%8B%A4%EC%A4%91-%ED%8C%A8%EB%9F%AC%EB%8B%A4%EC%9E%84-%EC%A7%80%ED%96%A5%EC%96%B8%EC%96%B4</guid>
            <pubDate>Mon, 03 Jun 2024 06:18:37 GMT</pubDate>
            <description><![CDATA[<p>오늘 적어볼 내용을 Swift 언어에 대한 특징들을 끄적여보겠습니다.</p>
<ol>
<li><p><strong>안정성(Type Safe한 언어)</strong></p>
<p> 문자열 배열에 다른 타입 숫자, 불리언 타입의 값을 저장할 수 없습니다.</p>
<p> 안정성을 위해 체크를 해야합니다.</p>
<ul>
<li><p>스위프트에서는 빈(nil) 값에 대해 엄격합니다.</p>
<p>  만약 nil을 사용할 경우 옵셔널를 이용해야 합니다</p>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>표현성(Expressive)</strong></p>
<p> 스위프트는 표현성을 고려한 언어입니다.</p>
<p> 다중 패러다임 지향 언어</p>
<ul>
<li><p><strong>객체 지향 프로그래밍(OOP: Object-Oriented Programming)</strong></p>
<ul>
<li><p>객체 지향 프로그래밍이란 <strong><code>클래스(class)</code></strong>와 <strong><code>구조체(struct)</code></strong>를 사용하여 데이터를 객체라는 단위로 묶어 객체들 간의 상호작용으로 프로그래밍 하는 방식</p>
<pre><code class="language-swift">  class Animal {
      var name: String
      init(name: String) {
          self.name = name
      }
      func speak() {
          print(&quot;\(name) makes a noise.&quot;)
      }
  }

  class Dog: Animal {
      override func speak() {
          print(&quot;\(name) barks.&quot;)
      }
  }

  let dog = Dog(name: &quot;Fido&quot;)
  dog.speak()  // &quot;Fido barks.&quot;</code></pre>
<p>  위 코드는 <strong>OOP</strong>의 개념을 활용하여 클래스와 상속을 표현 했습니다</p>
<pre><code class="language-swift">  class Animal {
      var name: String

      init(name: String) {
          self.name = name
      }

      func speak() {
          print(&quot;\(name) makes a noise.&quot;)
      }
  }</code></pre>
<p>  순차:</p>
<ol>
<li><p><strong>클래스 정의</strong></p>
<p> <code>“Animal”</code> 이라고 클래스를 정의했습니다.</p>
</li>
<li><p><strong>속성 정의</strong></p>
<p> <code>&quot;name&quot;</code> 이라고 문자열 타입의 속성을 정의했습니다, 문자열을 저장할 수 있습니다.</p>
</li>
<li><p><strong>초기화 메서드(Initializer)</strong></p>
<p> <code>&quot;init(name: String)&quot;</code> 메서드는 클래스이 초기화 메서드로, 객체가 생성될 때 호출됩니다, <code>&quot;name&quot;</code> 메서드를 속성을 초기화합니다</p>
</li>
<li><p><strong>메서드 정의</strong></p>
<p> <code>&quot;speak()&quot;</code> 메서드는 <code>“\(name) makes a noise.”</code> 라고 출력합니다.</p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<pre><code>        ```swift
        class Dog: Animal {
            override func speak() {
                print(&quot;\(name) barks.&quot;)
            }
        }
        ```

        1. **클래스 상속**

            `Dog` 클래스는 `Animal` 클래스의 모든 속성과 메서드를 물려 받는다는 의미 입니다.

        2. **메서드 오버라이딩**

            `Dog` 클래스는 `speak()` 메서드를 오버라이드하여 재정의합니다.

            이 메서드는 `“\(name) barks.&quot;` 라고 출력합니다 


        &gt; **Override란** 서브클래스가 슈퍼클래스로 부터 상속 받은 메서드를 재정의 하는 것
        &gt; 
- **함수형 프로그래밍(Functional Programming)**
    - 함수형 프로그래밍은 **함수를 일급객체로 취급** 함수의 조합과 응용으로 프로그래밍 하는 것

        ```swift
        let numbers = [1, 2, 3, 4, 5]
        let doubledNumbers = numbers.map { $0 * 2 }
        print(doubledNumbers)  // [2, 4, 6, 8, 10]
        ```

        1. `numbers` 는 **[1, 2, 3, 4, 5]** 라는 정수 배열입니다.
        2. `let` 키워드는 이 배열이 상수(immutable)임을 의미합니다. 즉, `numbers` 배열 자체는 변경할 수 없습니다.

        ```swift
        let doubledNumbers = numbers.map { $0 * 2 }
        ```

        `map` 함수는 배열의 각 요소에 동일한 변환을 적용하여 새로운 배열을 변환합니다.

        `{ $0 * 0 }` 는 클로저입니다. `map` 함수는 이 클로저를 배열의 각 요소에 적용합니다.

        `numbers.map { $0 * 0 }` 의 결과는 **[2, 4, 6, 8, 10]** 입니다. 각 요소가 2배로 변환된 새로운 배열입니다.

        &gt; **$0**은 배열의 각 요소를 나타내느 것입니다. 1이 될 수도 있고 2가 될 수도 있습니다.
        &gt; 
- **프로토콜 지향 프로그래밍(Protocol-Oriented Programming)**

    &gt; 프로토콜이란 작업, 클래스를 수행하기 위해 클래스, 구조체 또는 열거형에 구현하는 요구사항의 정의
    &gt; 

    - 프로토콜 지향 프로그래밍은 공통된 인터페이스를 정의하고, 이를 통해 코드의 재사용성과 확장성을 높이는 방식입니다.

        ```swift
        protocol Describable {
            var description: String { get }
        }

        struct Car: Describable {
            var model: String
            var description: String {
                return &quot;Car model: \(model)&quot;
            }
        }

        let car = Car(model: &quot;Tesla&quot;)
        print(car.description)  // &quot;Car model: Tesla&quot;
        ```

        1. **프로토콜 정의**

        ```swift
        protocol Describable {
            var description: String { get }
        }
        ```

        `Describable` 프로토콜은 하나의 속성`description` 을 정의합니다.

        이속성은 읽기 전용(computed property)이며, 문자열을 반환합니다.

        1. **구조체 정의**

        ```swift
        struct Car: Describable {
            var model: String

            var description: String {
                return &quot;Car model: \(model)&quot;
            }
        ```

        `Car` 구조체는 `Describable` 프로토콜을 채택합니다.

        `model` 속성은 자동차 모델을 나타내는 문자열을 저장합니다.

        `description` 속성은 프로토콜에서 요구한 대로 구현되어 있습니다. 이는 자동차 모델을 나타내는 문자열을 반환합니다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 내일배움캠프 1일차]]></title>
            <link>https://velog.io/@dx_xk7/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-1%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@dx_xk7/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-1%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 27 May 2024 12:01:57 GMT</pubDate>
            <description><![CDATA[<h1 id="내일-배움캠프-1일차-끝">내일 배움캠프 1일차 끝</h1>
<p>오늘 프로젝트 선정, github에 대해 공부를 했다.
Github를 다루면서 많은 시행착오가 있었지만 내일은 GitHub에 대해 자세히 포스팅 할 계획이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter기본기(Dart)]]></title>
            <link>https://velog.io/@dx_xk7/Flutter%EA%B8%B0%EB%B3%B8%EA%B8%B0Dart</link>
            <guid>https://velog.io/@dx_xk7/Flutter%EA%B8%B0%EB%B3%B8%EA%B8%B0Dart</guid>
            <pubDate>Mon, 15 Apr 2024 19:35:55 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 <strong>DartPad</strong>를 활용했습니다!</p>
<h2 id="변수선언">변수선언</h2>
<hr>
<p>Dart 언어에서 변수 선언 방법은 다음과 같습니다.</p>
<pre><code class="language-dart">var name = &#39;DeukRyeong&#39;;</code></pre>
<p><strong>Variable</strong>의 앞 3글자를 따와서 var라고 합니다. 여기서 중요한점은 코드가 끝날때 <code>;</code> 을 붙여줘야합니다.</p>
<p>변수 데이터값 변경하는 방법은 다음과 같습니다.</p>
<pre><code class="language-dart">var name = &#39;DeukRyeong&#39;;

name = &#39;득령&#39;;</code></pre>
<h2 id="변수타입data-type">변수타입(Data Type)</h2>
<hr>
<ul>
<li><p><strong>정수(integer)</strong></p>
<p>  정수타입 선언은 다음과 같습니다.</p>
<pre><code class="language-dart">  int number1 = 10;</code></pre>
<p>  integer의 앞 3글자를 따서 <code>int</code> 라고 선언해줍니다.</p>
</li>
</ul>
<pre><code>사측연산 하는 방법은 다음과 같습니다.

```dart
  int num1 = 10;
  int num2 = 5;
  print(num1 + num2);
  print(num1 - num2);
  print(num1 * num2);
  print(num1 / num2);  
```</code></pre><ul>
<li><p><strong>실수(double)</strong></p>
<p>  실수를 선언하는 방법은 다음과 같습니다.</p>
<pre><code class="language-dart">  double num1 = 2.5;</code></pre>
<p>  실수 타입도 변수 사측연산이 가능합니다.</p>
</li>
</ul>
<ul>
<li><p><strong>Boolean(불리언)</strong></p>
<p>  Boolean를 선언하는 방법은 다음과 같습니다.</p>
<pre><code class="language-dart">  bool isTrue = true;</code></pre>
</li>
</ul>
<ul>
<li><strong>데이터 타입이 궁금할때</strong></li>
</ul>
<ul>
<li><p><strong>print문 안에 변수를 넣는방법</strong></p>
<pre><code class="language-dart">  var str = &quot;글자&quot;;
    print(&#39;이것은${str}입니다.&#39;);
  }</code></pre>
</li>
</ul>
<ul>
<li><p><strong>dynamic</strong></p>
<p>  다이나믹타입을 사용하는 이유</p>
<p>  데이터를 변경할때 <code>int</code> 타입에서<code>String</code> 타입으로는 변경이 안되지만</p>
<p>  다이나믹 타입을 사용하면 가능합니다.</p>
<pre><code class="language-dart">  dynamic name = &#39;DuekRyeong&#39;;
   print(name);
    name = 10;
    print(name);
  }</code></pre>
</li>
</ul>
<h2 id="nullable-non-nullable">Nullable, non-Nullable</h2>
<hr>
<p><strong>nullable - null이 될 수 없다.</strong></p>
<p><strong>non-nullable - null이 될 수 있다.</strong></p>
<ul>
<li><p><strong>null 값을 출력하는 방법</strong></p>
<pre><code class="language-dart">   String? name = &#39;DeukRyeong&#39;;
    print(name);

    name = null;
    print(name);</code></pre>
<p>  <code>?</code> 를 활용하여 있을 수 도 있고 없을 수 도 있는 타입으로 바꿀 수 있습니다.</p>
<p>  반대로 <code>!</code> 를 활용하면 변수안에 데이터가 있다 라고 알립니다.</p>
</li>
</ul>
<h2 id="final-const">Final, Const</h2>
<hr>
<ul>
<li><p><strong>Final로 선언 하는 방법은 다음과 같습니다.</strong></p>
<pre><code class="language-dart">  final String name = &#39;DeukRyeong&#39;;</code></pre>
<p>  <strong>Final이란</strong> 한번 선언된 데이터를 수정 할 수 없게 만드는 타입 입니다.</p>
</li>
<li><p><strong>Const로 선언 하는 방법은 다음과 같습니다.</strong></p>
<pre><code class="language-dart">  const Stirng name = &#39;DeukRyeong&#39;;</code></pre>
<p>  <strong>Const</strong>또한 데이터 변경을 할 수 없게 만드는 방법중 하나 입니다.</p>
</li>
<li><p><strong>그러면 두 타입의 다른 점이 무엇인지 알아 봅시다.</strong></p>
<p>  먼저 <code>DateTime</code> 이란 메서드가 있습니다. 호출 시 코드가 실행 된 시간을 표기 해줍니다.</p>
<pre><code class="language-dart">  DateTime now = DateTime.now();</code></pre>
</li>
</ul>
<pre><code>**Final** 같은 경우에는 빌드타임의 값을 알지 못해도 상관이 없는 반면 **Const**같은 경우에는 빌드타임이 있어야되는 타입입니다. 이러한 이유로 DateTime은 코드가 실행 될 때의 시간을 가져오기 때문에 **const**는 선언할 수 없습니다.</code></pre><h2 id="list">List</h2>
<hr>
<ul>
<li><p><strong>list를 선언하는 방법은 다음과 같습니다.</strong></p>
<pre><code class="language-dart">  List&lt;String&gt; lis = [&#39;one&#39;, &#39;two&#39;];</code></pre>
<p>  List에서는 0부터 숫자를 셉니다. 즉 1번은 0번입니다.</p>
<p>  0번의 <strong>Index</strong>를 호출 하고 싶으시면 다음과 같이 하시면 됩니다.</p>
<pre><code class="language-dart">  print(lis[0]);</code></pre>
<p>  만약 <strong>배열의 길이</strong>를 알고 싶다면 다음과 같이 하시면 됩니다.</p>
<pre><code class="language-dart">  print(lis.length);</code></pre>
<p>  만약 배열의 <strong>데이터를 추가</strong>하고 싶다면 다음과 같이 하시면 됩니다.</p>
<pre><code class="language-dart">  lis.add(&quot;three&quot;); //print(lis); == [&#39;one&#39;, &#39;two&#39;, &#39;three&#39;]</code></pre>
<p>  만약 배열의 <strong>데이터를 제거</strong>하고 싶다면 다음과 같이 하시면 됩니다.</p>
<pre><code class="language-dart">  lis.remove(&quot;three&quot;);</code></pre>
<p>  만약 배열 안에 있는 <strong>데이터가 몇번째인지 알고</strong> 싶다면 다음과 같이 하시면 됩니다.</p>
<pre><code class="language-dart">  print(lis.indexOf(&#39;one&#39;)); // 0</code></pre>
</li>
</ul>
<h2 id="map">MAP</h2>
<hr>
<ul>
<li><p>Map의 데이터구조는 Key 와 Value 로 이루어져 있습니다.</p>
<p>  데이터를 선언하는 방법은 다음과 같습니다.</p>
<pre><code class="language-dart">      Map&lt;String, String&gt; dictionary = {
      &#39;swift&#39;: &#39;스위프트&#39;,
      &#39;flutter&#39;: &#39;플루터&#39;,
      &#39;java&#39;: &#39;자바&#39;
    };</code></pre>
<p>  Map 타입은 Key값으로 Value를 찾는 방식입니다.</p>
<pre><code class="language-dart">  print(dictionary[&#39;swift&#39;]); //스위프트</code></pre>
</li>
</ul>
<h2 id="set">Set</h2>
<hr>
<p>Set 타입은 데이터안에 중복된 값을 스스로 제거하는 배열의 종류입니다, 선언하는 방법은 다음과 같습니다.</p>
<pre><code class="language-dart">final Set&lt;String&gt; names = { 
    &#39;swift&#39;, &#39;flutter&#39;, &#39;java&#39;
};</code></pre>
<h2 id="if-문">if 문</h2>
<hr>
<p>if 문의 기본 구조는 다음과 값습니다.</p>
<pre><code class="language-dart">if() { };</code></pre>
<p>다음은 응용하는 방법입니다.</p>
<pre><code class="language-dart">var num = 2;
  if (num % 2 == 0) {
    print(&quot;짝수 입니다.&quot;);
  } else {
    print(&quot;홀수 입니다.&quot;);
  }</code></pre>
<p><strong>포스팅 Rmx</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JD분석,  자기객관화]]></title>
            <link>https://velog.io/@dx_xk7/JD%EB%B6%84%EC%84%9D-%EC%9E%90%EA%B8%B0%EA%B0%9D%EA%B4%80%ED%99%94</link>
            <guid>https://velog.io/@dx_xk7/JD%EB%B6%84%EC%84%9D-%EC%9E%90%EA%B8%B0%EA%B0%9D%EA%B4%80%ED%99%94</guid>
            <pubDate>Thu, 11 Apr 2024 05:38:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번 포스팅에서는 저의 부족한점을 찾고 제가 진정 원하는 회사는 무엇인지에 대해 글을 써보겠습니다.</p>
</blockquote>
<h2 id="1-내가-원하는-기업">1. 내가 원하는 기업</h2>
<blockquote>
<p><strong>먼저 저는 돈보다는 영향력이 있는 서비스를 만들고 싶습니다.</strong></p>
</blockquote>
<hr>
<p>대학생일때 수업중 경영학의 이해라는 수업에서 과제를 받았습니다.</p>
<p>과제에 내용은 한 기업의 대표를 분석해라 다만 <strong>사회에 영향력있는 기업의 대표.</strong></p>
<p>저는 이 과제의 주인공을 비바리퍼블리카의 대표인 이승건 대표님으로 선정하였습니다.</p>
<p>왜냐하면 토스가 등장하기 전에는 인터넷 뱅킹이란것은 참 어려운 일이 였습니다</p>
<p>송금을 하기 위에서는 공인인증서, 본인인증 등의 많은 과정을 거친 후에서야 송금을 할 수 있는 시스템 이였고 이를 바꾸고자 토스라는 서비스가 탄생하게 되었다.</p>
<p>또한 평생 수수료 무료 정책을 펼치며 사회에 큰 영향을 주었기 때문에 이승건 대표님을 선정하게 되었습니다.</p>
<p>제가 원하는 기업는 이와같이 사회에 영향력있는 기업에 들어가고 싶고 같이 성장하고 싶은 개발자입니다.</p>
<h2 id="기업-listup">기업 ListUp</h2>
<hr>
<ol>
<li><strong>토스</strong><ul>
<li><strong>함께할 업무</strong><ul>
<li>송금, 자산 조회, 신용 관리, 대출, 보험, 인증, 결제 등 수백가지 서비스를 제공하는 앱 토스의 iOS 개발</li>
</ul>
</li>
<li><strong>자격요건</strong><ul>
<li>iOS 개발 경력 3년 이상에 준하는 실력</li>
<li>탄탄한 기본기, 논리적 사고력과 추론 능력 그리고 실제 코드 구현 능력</li>
<li>서로 다른 직군과 협업</li>
<li>Protocol Oriented Programming에 대한 이해</li>
</ul>
</li>
</ul>
</li>
<li><strong>배달의 민족</strong><ul>
<li><strong>함께할 업무</strong><ul>
<li>배달의민족 앱의 B마트, 배민스토어 등 커머스 서비스 운영/개발</li>
<li>배민상회 앱 운영/개발</li>
</ul>
</li>
<li><strong>자격요건</strong><ul>
<li>iOS 개발 경력 5년 이상</li>
<li>Swift 개발언어에 능숙</li>
<li>RxSwift에 대한 이해도가 높은 분</li>
<li>적극적인 의사소통을 통해 주도적으로 문제를 해결하고, 필요한 부분</li>
</ul>
</li>
</ul>
</li>
<li><strong>테스트밸리</strong><ul>
<li><strong>함께할 업무</strong><ul>
<li>Flutter를 사용한 모바일 앱 개발</li>
<li>Android / iOS 스토어 모바일 앱 배포</li>
</ul>
</li>
<li><strong>자격요건</strong><ul>
<li>App 개발 신입 또는 경력자</li>
<li>Flutter 앱 개발 또는 Native 앱 개발(Android, iOS) 경험</li>
<li>UI/UX의 디테일에 대한 집착과 욕심이 있으신 분(애니메이션 등)</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="정리">정리</h2>
<hr>
<p>위에 내용의 공통점은 협업능력을 우선시 하는거 같습니다.</p>
<p>또한 <strong>Native</strong> 개발뿐만 아니라 <strong>Hybrid App</strong> 개발공부를 하면서 능력을 올려야겠다는 생각이 듭니다</p>
<h3 id="역량-키우기">역량 키우기</h3>
<ol>
<li>POP(Protocol Oriented Programming) 공부</li>
<li>협업 프로젝트(디자이너,백엔드개발자)등등 </li>
<li>HybridApp 공부</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[x만큼 간격이 있는 n개의 숫자 ]]></title>
            <link>https://velog.io/@dx_xk7/x%EB%A7%8C%ED%81%BC-%EA%B0%84%EA%B2%A9%EC%9D%B4-%EC%9E%88%EB%8A%94-n%EA%B0%9C%EC%9D%98-%EC%88%AB%EC%9E%90</link>
            <guid>https://velog.io/@dx_xk7/x%EB%A7%8C%ED%81%BC-%EA%B0%84%EA%B2%A9%EC%9D%B4-%EC%9E%88%EB%8A%94-n%EA%B0%9C%EC%9D%98-%EC%88%AB%EC%9E%90</guid>
            <pubDate>Tue, 09 Apr 2024 06:47:45 GMT</pubDate>
            <description><![CDATA[<h2 id="스팩">스팩</h2>
<hr>
<p>함수 solution은 정수 x와 자연수 n을 입력 받아, x부터 시작해 x씩 증가하는 숫자를 n개 지니는 리스트를 리턴해야 합니다. 다음 제한 조건을 보고, 조건을 만족하는 함수, solution을 완성해주세요.</p>
<h2 id="내-풀이">내 풀이</h2>
<hr>
<pre><code class="language-swift">func solution(_ x:Int, _ n:Int) -&gt; [Int64] {

    var ans = [Int64]()

    for i in 1...n {
        ans.append(Int64(i*x))
    }
    return ans
}</code></pre>
<p>함수의 반환 타입은 <code>[Int64]</code> 입니다 </p>
<p><code>ans</code> 라는 변수의 타입을 <code>[Int64]</code> 로 초기화를 해주고</p>
<p>반복문을 활용해 배수를 집어넣어줍니다</p>
<p>1부터 n번까지 반복을 하고 <code>append</code> 를 활용해 배열 넣어주고 반복이 끝나면 반환해주면 됩니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[나머지가 1이 되는 숫자 찾기]]></title>
            <link>https://velog.io/@dx_xk7/%EB%82%98%EB%A8%B8%EC%A7%80%EA%B0%80-1%EC%9D%B4-%EB%90%98%EB%8A%94-%EC%88%AB%EC%9E%90-%EC%B0%BE%EA%B8%B0</link>
            <guid>https://velog.io/@dx_xk7/%EB%82%98%EB%A8%B8%EC%A7%80%EA%B0%80-1%EC%9D%B4-%EB%90%98%EB%8A%94-%EC%88%AB%EC%9E%90-%EC%B0%BE%EA%B8%B0</guid>
            <pubDate>Tue, 09 Apr 2024 06:05:52 GMT</pubDate>
            <description><![CDATA[<h2 id="스팩">스팩</h2>
<hr>
<p>자연수 <code>n</code>이 매개변수로 주어집니다. <code>n</code>을 <code>x</code>로 나눈 나머지가 1이 되도록 하는 가장 작은 자연수 <code>x</code>를 return 하도록 solution 함수를 완성해주세요. 답이 항상 존재함은 증명될 수 있습니다.</p>
<h2 id="내풀이">내풀이</h2>
<hr>
<pre><code class="language-swift">import Foundation

func solution(_ n:Int) -&gt; Int {    
    var i = 2
    while n % i != 1{
        i += 1
    }
    return i
}
</code></pre>
<p><code>while</code> 문에서 <code>n</code> 을 <code>i</code> 로 나누었을때 나머지가 1이 아닌 경우까지 무한 반복합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[배열의 평균값 구하기]]></title>
            <link>https://velog.io/@dx_xk7/%EB%B0%B0%EC%97%B4%EC%9D%98-%ED%8F%89%EA%B7%A0%EA%B0%92-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dx_xk7/%EB%B0%B0%EC%97%B4%EC%9D%98-%ED%8F%89%EA%B7%A0%EA%B0%92-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 03 Apr 2024 05:17:29 GMT</pubDate>
            <description><![CDATA[<h2 id="스팩">스팩</h2>
<hr>
<p>정수 배열 <code>numbers</code>가 매개변수로 주어집니다. <code>numbers</code>의 원소의 평균값을 return하도록 solution 함수를 완성해주세요.</p>
<p><strong>평균 값을 구하는 방법</strong></p>
<p>배열에 있는 <code>numbers</code> 를 전부 더 한 값을 <code>numbers</code> 로 나누면 됩니다.</p>
<p>예를 들어 <code>let arr = [21, 35, 50, 11]</code></p>
<p>$$
21 + 35 + 50 +11 = 117 / 5
$$</p>
<p>이런 식으로 계산 하시면 됩니다.</p>
<blockquote>
<p><strong>코드를 짤때 결과값은 소수점이 포함 될 수 있으니 Double 반환해야 됩니다</strong></p>
</blockquote>
<pre><code class="language-swift">import Foundation

func solution(_ numbers:[Int]) -&gt; Double {

    var sum = 0


    for i in numbers {
        sum += i
    }

    let count = Double(numbers.count)
    let avs = Double(sum) / count

    return avs
}</code></pre>
<ul>
<li><p>배열안에 있는 모든 수를 더해 줍니다.</p>
</li>
<li><p>배열안에 데이터가 몇개가 있는지 구해줍니다</p>
<p>  <code>let count = Double(numbers.count)</code>
  <strong>Double</strong> 데이터 타입으로 변경해줍니다.</p>
</li>
<li><p>이제 계산을 해줍니다. 합 나누기 데이터 수</p>
<p>  <code>let avs = Double(sum) / count</code></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[짝수의 합 구하기]]></title>
            <link>https://velog.io/@dx_xk7/%EC%A7%9D%EC%88%98%EC%9D%98-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dx_xk7/%EC%A7%9D%EC%88%98%EC%9D%98-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 02 Apr 2024 05:41:25 GMT</pubDate>
            <description><![CDATA[<h2 id="스팩">스팩</h2>
<hr>
<p>정수 <code>n</code>이 주어질 때, <code>n</code>이하의 짝수를 모두 더한 값을 return 하도록 solution 함수를 작성해주세요.</p>
<p>만약 <code>n</code> 이 10이면 2 + 4 + 8 + 10 = 30 이런식의 문제입니다.</p>
<h2 id="나의-풀이">나의 풀이</h2>
<hr>
<ul>
<li><p><strong>방법 1.</strong></p>
<p>  수를 나누었을때 0로 떨어지는 코드를 구현 해야 됩니다.</p>
<pre><code class="language-swift">  func solution(_ n:Int) -&gt; Int {

      var i = 1
      var result = 0
      for i in 1...n {
          if i % 2 == 0 {
              result += i
          }
      }
      return result
  } </code></pre>
</li>
<li><p><strong>코드해석:</strong></p>
<p>  <code>i</code> 를 1로 초기화 합니다.</p>
<p>  <code>result</code> 또한 0으로 초기화 합니다</p>
<p>  <code>for i in 1...n</code> 은 1부터 n번 반복합니다</p>
<p>  <code>if i % 2 == 0</code> 만약 <code>i</code> 를 2로 나누었을때 나머지가 0이면 <code>result</code> 에 i를 추가 해라</p>
<p>  <strong>끝</strong></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[MVVM 정리 ]]></title>
            <link>https://velog.io/@dx_xk7/MVVM-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@dx_xk7/MVVM-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 31 Mar 2024 04:59:58 GMT</pubDate>
            <description><![CDATA[<h2 id="소프트웨어-아키텍처-패턴">소프트웨어 아키텍처 패턴</h2>
<hr>
<p>Software architectural pattern은 특정한 규칙과 템플릿의 집합입니다.</p>
<p>앱의 <strong>일관성</strong>을 높이고 코드를 <strong>개선</strong>하거나 <strong>유지 보수</strong>를 원활하게 하는 목적입니다</p>
<p><strong>(Software design pattern)</strong>이라고도 합니다</p>
<p>소프트웨어 아키텍처 패턴을 사용하게 된 이유!</p>
<p>초기의 UI 개발방식은 <strong>개발자들이</strong> View만들고 그에 관련된 로직을 작성했습니다</p>
<p>이러한 방식은 Class의 덩치를 키우고 UI를 정의하는코드, 데이터코드, 비즈니스코드에 </p>
<p>강력한 의존성이 생깁니다, 이러한 경우 보통 두명이상의 개발자들이 같은 View를 작업하는게 불가능합니다</p>
<p>이러한 이유로 인해 탄생하게 대였습니다.</p>
<h2 id="mvvm">MVVM</h2>
<hr>
<p>MVVM에는 3가지의 핵심 파트가 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/dx_xk7/post/067f4b89-bdc4-4d6f-839a-bee762d87eb2/image.png" alt=""></p>
<ul>
<li><p><strong>Model(모델)</strong></p>
<ul>
<li><p>앱의 기본적인 데이터와 비즈니스 로직을 구조화합니다.</p>
<p>  여기서 중요한 것은 <strong>View</strong>와 독집적이여야 합니다.</p>
</li>
</ul>
</li>
<li><p><strong>View(뷰)</strong></p>
<ul>
<li>플랫폼의 제공하는 시각적요소들을 사용하는 UI정의합니다</li>
</ul>
</li>
<li><p><strong>ViewModel(뷰모델)</strong></p>
<ul>
<li>뷰와 모델 사이의 중개자, 뷰에 모델의 데이터를 제공하며 모델에는 업데이트를 요청합니다.</li>
</ul>
</li>
</ul>
<h2 id="실습">실습</h2>
<hr>
<p>우선 Model Code를 작성해줍시다</p>
<ul>
<li><p><strong>Model</strong></p>
<pre><code class="language-swift">  struct My_Information_Model {
  var name: String
  var birthday: Date
  }</code></pre>
<p>  Name, birthday라는 변수와 데이터 타입만 선언해줍니다.</p>
</li>
<li><p><strong>View</strong></p>
<pre><code class="language-swift">  struct ContentView: View {

      let deukRyeong = My_Information_Model(name: &quot;득령&quot;, birthday: Date())    
      var body: some View {

          VStack {
              Text(deukRyeong.name)
                  .padding()
              Text(String(deukRyeong.age))
                  .padding()

          }
      }
  }</code></pre>
<p>  ContentView에서 불러왔습니다, 이렇게 하면 <strong>MVVM패턴</strong>이라고 볼 수 없습니다</p>
<p>  왜냐면 Model과 View는 접촉지점이 없어야 됩니다.</p>
<p>  이제 이름을 변환하는 함수를 만들어야 합니다 VeiwModel를 만들어봅시다.</p>
</li>
<li><p><strong>ViewModel</strong></p>
<pre><code class="language-swift">
  class ViewModel: ObservableObject {
      var deukRyeong = My_Information_Model(name: &quot;득령&quot;, birthday: Date())
      var name: String {
          deukRyeong.name
      }
      var age: String {

          return &quot;27&quot;
      }
      func chName(name: String) {
          deukRyeong.name = name

      }
  }</code></pre>
<p>  <strong>func</strong> <strong>chName</strong>에서 name 파라미터를 선언하고 ContentView에서 받아오는 코드입니다</p>
</li>
<li><p><strong>ContentView</strong></p>
<pre><code class="language-swift">  struct ContentView: View {

      let deukRyeong = My_Information_Model(name: &quot;득령&quot;, birthday: Date())
      @StateObject var viewModel = ViewModel()
      @State var text: String = &quot;&quot;
      var body: some View {

          VStack {
              HStack {
                  TextField(&quot;이름을 입력해주세요.&quot;, text: $text)
                      .textFieldStyle(RoundedBorderTextFieldStyle())
                      .padding()
                  Button {
                      viewModel.chName(name: text)
                  } label: {
                      Text(&quot;이름변경&quot;)
                  }
                  .padding()
              }
              .padding()
              Text(viewModel.name)
                  .padding()
              Text(viewModel.age)
                  .padding()
          }
      }
  }</code></pre>
<p>  이 상태로 실행을 해도 이름이 변경이 안됩니다 ViewModel에 중요한 부분이 빠져있기 때문이죠!</p>
<h2 id="published"><strong>@Published</strong></h2>
<hr>
<p>  먼저 해결방법 부터 알아볼게요.</p>
<pre><code class="language-swift">  class ViewModel: ObservableObject {
      @Published var deukRyeong = My_Information_Model(name: &quot;득령&quot;, birthday: Date())</code></pre>
<p>  deukRyeong이란 변수에 <strong>@Published</strong>를 선언 해주어야 됩니다.</p>
<p>  <strong>@Published</strong>는 objectWillChangePudlisher가 send 메서드를 호출하는 코드를 좀더 간소화시킨 것 입니다.</p>
<p>  <strong>간단하게 데이터를 전달해주는 메소드입니다.</strong></p>
</li>
</ul>
<blockquote>
<pre><code>💡 SwiftUI 에는 데이터를 다루는 도구들이 많습니다 그중 하나가 @Published 인거죠, 다음 포스팅에서는 데이터를 다루는 도구들을알아보겠습니다</code></pre></blockquote>
<pre><code>## 전체코드

---

```swift
//Model.swift
struct My_Information_Model {
var name: String
var birthday: Date
}

//ViewModel.swift
class ViewModel: ObservableObject {
    @Published var deukRyeong = My_Information_Model(name: &quot;득령&quot;, birthday: Date())
    var name: String {
        deukRyeong.name
    }
    var age: String {

        return &quot;27&quot;
    }
    func chName(name: String) {
        deukRyeong.name = name

    }
}

//ContentView.swift
struct ContentView: View {

    let duekRyeong = My_Information_Model(name: &quot;득령&quot;, birthday: Date())
    @StateObject var viewModel = ViewModel()
    @State var text: String = &quot;&quot;
    var body: some View {

        VStack {
            HStack {
                TextField(&quot;이름을 입력해주세요.&quot;, text: $text)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                Button {
                    viewModel.chName(name: text)
                } label: {
                    Text(&quot;이름변경&quot;)
                }
                .padding()
            }
            .padding()
            Text(viewModel.name)
                .padding()
            Text(viewModel.age)
                .padding()
        }
    }
}

```

**Today I Learned Rmx!**</code></pre>]]></description>
        </item>
    </channel>
</rss>