<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>eung7_</title>
        <link>https://velog.io/</link>
        <description>안녕하세요. SW Engineer eung7입니다.</description>
        <lastBuildDate>Fri, 27 May 2022 10:13:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. eung7_. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/wannabe_eung" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[iOS] UIButton Configuration 사용해보기 in iOS 15]]></title>
            <link>https://velog.io/@wannabe_eung/iOS-UIButton-Configuration-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0-in-iOS-15</link>
            <guid>https://velog.io/@wannabe_eung/iOS-UIButton-Configuration-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0-in-iOS-15</guid>
            <pubDate>Fri, 27 May 2022 10:13:00 GMT</pubDate>
            <description><![CDATA[<p>iOS 15로 업데이트되면서 기존의 UIButton을 정의하는 방법이 늘어났다.
<a href="https://developer.apple.com/documentation/uikit/uibutton/configuration">https://developer.apple.com/documentation/uikit/uibutton/configuration</a></p>
<pre><code class="language-swift">// 기존 버튼 객체 생성
let button = UIButton()
button.backgroundColor = .darkGray

// 업데이트 후, UIButton.Configuration in iOS 15
var config = UIButton.Configuration.filled()
config.baseBackgroundColor = .darkGray
let button = UIButton(configuration: config)</code></pre>
<p>변수로 <code>config</code>을 정의해주고 정의할 속성을 담아 UIButton을 생성</p>
<h1 id="configuration-생성하기">Configuration 생성하기</h1>
<pre><code class="language-swift">public static func plain() -&gt; UIButton.Configuration

        public static func tinted() -&gt; UIButton.Configuration

        public static func gray() -&gt; UIButton.Configuration

        public static func filled() -&gt; UIButton.Configuration

        public static func borderless() -&gt; UIButton.Configuration

        public static func bordered() -&gt; UIButton.Configuration

        public static func borderedTinted() -&gt; UIButton.Configuration

        public static func borderedProminent() -&gt; UIButton.Configuration</code></pre>
<p>UIButton.Configuration을 정의할 때는 크게 4가지가 있다.</p>
<ol>
<li><strong>Tinted</strong></li>
<li><strong>Filled</strong></li>
<li><strong>Gray</strong></li>
<li><strong>Plain</strong></li>
</ol>
<pre><code class="language-swift">var config = UIButton.Configuration.filled()
var config = UIButton.Configuration.tinted()
var config = UIButton.Configuration.gray()
var config = UIButton.Configuration.plain()</code></pre>
<p><img src="https://velog.velcdn.com/images/wannabe_eung/post/960a2dfc-d09b-4152-ae92-4a9b2283fc92/image.png" alt=""></p>
<ul>
<li>위에서 부터 Filled, Tinted, Gray, Plain 순이다.</li>
</ul>
<hr>
<h1 id="버튼-속성-변경하기">버튼 속성 변경하기</h1>
<h2 id="색상-변경하기">색상 변경하기</h2>
<ul>
<li><code>baseBackgroundColor</code>를 이용해 백그라운드의 색상 변경 가능</li>
<li><code>baseForegroundColor</code>를 이용해 포어그라운드의 색상 변경 가능</li>
</ul>
<pre><code class="language-swift">config.baseBackgroundColor = .systemBlue
config.baseForegroundColor = .systemBackground
</code></pre>
<h2 id="텍스트-속성">텍스트 속성</h2>
<ul>
<li>기본적으로 title과 subtitle의 속성을 변경할 수 있다.</li>
<li>텍스트 정렬: <code>titleAlignment</code>로 가능</li>
<li>title과 subtitle 사이의 거리: <code>titlePadding</code>으로 조절가능<pre><code class="language-swift">var titleAttr = AttributedString.init(&quot;Button&quot;)
  titleAttr.font = .systemFont(ofSize: 26.0, weight: .heavy)
  config.attributedTitle = titleAttr
</code></pre>
</li>
</ul>
<p>var subtitleAttr = AttributedString.init(&quot;Subtitle&quot;)
subtitleAttr.font = .systemFont(ofSize: 20.0, weight: .light)
config.attributedSubtitle = subtitleAttr</p>
<pre><code>![](https://velog.velcdn.com/images/wannabe_eung/post/758f4651-ce26-4f1f-89fe-fd8501149649/image.png)

## 이미지 속성
+ 버튼안에 쉽게 Image삽입가능
+ ```imagePadding```, ```imagePlacement``` 간단하게 설정 가능
```swift
config.image = UIImage(systemName: &quot;play.fill&quot;)
config.imagePadding = 10
config.imagePlacement = .bottom</code></pre><p><img src="https://velog.velcdn.com/images/wannabe_eung/post/7436f513-b45a-436c-86c6-cd99fa7e6f40/image.png" alt=""></p>
<h2 id="버튼-레이아웃-속성">버튼 레이아웃 속성</h2>
<ol>
<li><code>buttonSize</code>을 통해 버튼 사이즈를 쉽게 조절가능 (버튼의 요소 크기에 맞게 자동으로 조절됨)</li>
<li><code>contentInsets</code>으로 버튼의 마진 조절 가능</li>
</ol>
<pre><code class="language-swift">config.buttonSize = .small
config.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16)
</code></pre>
<p><img src="https://velog.velcdn.com/images/wannabe_eung/post/bf805f7a-9ab9-41af-bbe2-59494c268c84/image.png" alt=""></p>
<h2 id="버튼-이벤트-받기">버튼 이벤트 받기</h2>
<ul>
<li>버튼 이벤트를 받을 수 있는 <code>ConfigurationUpdateHandler</code>라는 클로저를 제공한다.</li>
</ul>
<pre><code class="language-swift">button.configurationUpdateHandler = { btn in
   switch btn.state {
   case .selected:
       var config = btn.configuration
       config?.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { attr in
          var new = attr
          new.backgroundColor = .darkGray
          return new
      }
   case .highlighted:
   default:
      break
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] MVVM, Clean Architecture에 대한 고찰]]></title>
            <link>https://velog.io/@wannabe_eung/iOS-MVVM-Clean-Architecture%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</link>
            <guid>https://velog.io/@wannabe_eung/iOS-MVVM-Clean-Architecture%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</guid>
            <pubDate>Sat, 21 May 2022 15:47:58 GMT</pubDate>
            <description><![CDATA[<h1 id="clean-architecture-필요성을-깨닫다">Clean Architecture, 필요성을 깨닫다.</h1>
<p>나는 주로 코드로 UI를 구현한다. 그래서 많은 UI객체들을 갖고 있는 VC에는 코드가 굉장히 길어지는 경험을 자주 했다.
앱의 버그가 발생하면 이 VC의 방대한 코드를 어디서부터 손을 봐야할지 막막했다.
데이터의 이동이 많아지고 앱이 커짐에 따라서 이 문제는 계속해서 문제가 되었다.</p>
<p>결국 Clean Architecture에 대한 필요성을 절실하게 깨달았고,
앱의 좋은 가독성이 유지 보수와도 연관된다는 것을 알았다.</p>
<p>이번에 상대적으로 방대한 앱을 만들어보았는데,
Clean Architecture라는 것은 앱 개발자로서 굉장히 중요하다는 것을 직접적으로 깨달은 계기가 되었다.
물론 이 Clean Architecture라는 것은 개개인마다 생각의 차이가 있다.
오늘은 MVVM에 대한 보편적인 이야기를 해보려고 한다.</p>
<hr>
<h1 id="mvvm이란">MVVM이란?</h1>
<ul>
<li><p>MVVM이란 Model - View - ViewModel의 약자로써 불려진다.</p>
</li>
<li><p>일반적으로 View는 iOS에서 UIViewController를 의미한다.</p>
</li>
<li><p>View와 Model은 서로 소통하지 않는다. 오직 징검다리 역할을 하는 ViewModel을 통해 소통한다.</p>
</li>
<li><p>ViewModel이란 단순하게 말하자면 오직 View를 위한 Model이다.</p>
</li>
<li><p>여기서 Model은 단순히 하나를 뜻하지 않는다. 데이터를 가공한 정도에 따라서 <strong>Entitiy, Model, ViewModel</strong>로 분류할 수 있다.</p>
</li>
</ul>
<p>여기까지가 간략한 MVVM에 대한 설명이다.
하지만 더 깊이 들어가보면 아직 설명해야 할 것들이 많다.</p>
<h2 id="model은-그-자체로-하나가-아니다">Model은 그 자체로 하나가 아니다.</h2>
<p><img src="https://velog.velcdn.com/images/wannabe_eung/post/4c1fbe28-97a4-4873-bce5-e48f9b3975db/image.png" alt=""></p>
<p>위 사진은 데이터가 MVVM 구조 내에서 어떻게 변형되어 View까지 전달되는 지에 대한 모식도이다.
밑의 정사각형의 Entity, Model, ViewModel은 모두 Model로 분류된다.</p>
<ul>
<li><p>Entity는 서버에서 받아온 원본 데이터이다. 즉 가공되지 않는 데이터이다.</p>
</li>
<li><p>Model은 Entity를 통해 앱에서 실제로 사용될 데이터로 가공하여 만들어진 데이터이다.</p>
</li>
<li><p>ViewModel은 Service를 통해 Model로 부터 만들어진다.</p>
</li>
<li><p>Model은 Repository를 통해 Entity로 부터 만들어진다.</p>
</li>
</ul>
<p>나는 그래서 Repository와 Service를 <code>@escaping Closure</code>를 통해서 변형을 했다. 
사실 아직 래퍼런스를 많이 찾아보진 않아서 이 방법이 좋다 라고 할 수는 없지만 개인적으로 괜찮은 방법같다.
예시 코드를 한번 보도록 하자.</p>
<pre><code class="language-swift">import Alamofire

/// 서버에서 원본데이터를 가져온다.
class Repository {
    static func fetchMovies(from term: String, completion: @escaping ([Movie]) -&gt; Void) {
        var components = URLComponents(string: &quot;https://itunes.apple.com/search&quot;)!
        let search = URLQueryItem(name: &quot;term&quot;, value: term)
        let media = URLQueryItem(name: &quot;media&quot;, value: &quot;movie&quot;)
        let entity = URLQueryItem(name: &quot;entity&quot;, value: &quot;movie&quot;)
        let limit = URLQueryItem(name: &quot;limit&quot;, value: &quot;20&quot;)
        components.queryItems =  [ search, media, entity, limit ]
        let url = components.url!

        AF
            .request(url)
            .validate()
            .responseDecodable(of: Result.self) { response in
                switch response.result {
                case .success(let result):
                    DispatchQueue.main.async {
                        completion(result.results)
                    }
                case .failure(let error):
                    print(&quot;Error! : \(error.localizedDescription)&quot;)
                }
            }
            .resume()
    }
}
</code></pre>
<ul>
<li><p>completion이라는 탈출 클로저를 이용하여 원본 데이터의 배열을 전달하고 있다. </p>
</li>
<li><p>여기서 원본 데이터를 Movie라는 객체의 타입이다.</p>
</li>
<li><p>이 데이터를 나중에 Service가 받아서 적절히 변형해줄 것이다.</p>
</li>
</ul>
<pre><code class="language-swift">/// Repository를 이용해 Entity -&gt; Model로 변형
class Service {
    static func fetchStarMovies(_ from: String, completion: @escaping ([StarMovie]) -&gt; Void) {
        Repository.fetchMovies(from: from) { movies in
            let starMovies = movies.map { return StarMovie(poster: $0.poster, movieName: $0.movieName, trailer: $0.trailer, isStar: false)}
            DispatchQueue.main.async {
                completion(starMovies)
            }
        }
    }
}
</code></pre>
<ul>
<li><p>여기서 정의된 StarMovie는 Model에 해당한다.</p>
</li>
<li><p>Repository에서 콜백함수로 받은 데이터를 실제 앱에서 사용할 데이터로 변형하는 것을 코드에서 볼 수 있다.</p>
</li>
</ul>
<p>여기서 둘다 전역 함수로 설정한 이유는 데이터를 받아오는 행위는 여러 화면에서 쓰일 수 있는 가능성을 열어둔 것이다.
바로 다음은 이 점에 대해서 알아보도록 하자.</p>
<hr>
<h1 id="mvvm-유의점">MVVM 유의점</h1>
<p>앞서 MVVM에 구조를 살펴봤지만 몇 가지 유의할 점이 있는 것 같아 적어본다.
처음 MVVM을 접했을 때 정해진 틀에 내 코드를 끼워맞추는 것이 너무 힘이 들었는데,
사실 이렇게 하지 않아도 무방하다는 것을 알았다. 정해진 게 없는 것이 아키텍쳐라고 생각한다.
내가 실제로 MVVM을 구현하고 몇 가지 깨달은 점을 적어볼까 한다.</p>
<ul>
<li><p>Entity와 Model 사이에 차이가 없다면 Entity를 Model로 취급해도 상관없다.</p>
</li>
<li><p>앱의 여러 곳에서 중복되는 메서드들이 있다면 그것을 싱글톤 객체로 따로 만들어 관리하는 것도 하나의 방법이다.</p>
</li>
</ul>
<pre><code class="language-swift">class StarMovieManager {
    static let shared = StarMovieManager()
}

extension StarMovieManager {
    func verifyInStarMovies(_ selectedMovie: StarMovie) -&gt; StarMovie {
        if let starMovie = StarMovie.movies.first(where: { $0.trailer == selectedMovie.trailer }) {
            return starMovie
        } else {
            return selectedMovie
        }
    }

    func saveStarMovies() {
        let userdefaults = UserDefaults.standard
        let data = try? JSONEncoder().encode(StarMovie.movies)
        userdefaults.set(data, forKey: &quot;StarMovies&quot;)
    }

    func loadStarMovies() {
        let userdefaults = UserDefaults.standard
        guard let starMovies = try? JSONDecoder().decode([StarMovie].self, from: userdefaults.data(forKey: &quot;StarMovies&quot;) ?? Data()) else { return }
        StarMovie.movies = starMovies
    }
}
</code></pre>
<p>위의 <code>StarMovieManager</code>객체는 중복 메서드를 따로 모아놓은 것이다.
이것을 싱글톤 객체로 만들어서 접근하게 만들었다.
이것은 물론 메서드뿐만 아니라 프로퍼티도 위의 처럼 만들 수 있다.
여러 화면에서 쓰일 수 있는 공통 데이터가 필요할 수 있으니 말이다 !</p>
<ul>
<li><p>간단한 앱은 오히려 MVC가 더 편리하다.</p>
</li>
<li><p>데이터 바인딩 같은 경우 구현하기 쉽지 않다는 점.</p>
</li>
</ul>
<p>데이터 바인딩과 연관해서 MVVM이 함수형 프로그래밍인 RxSwift, Combine과 같은 것들과 융합해서 많이 쓰이는 것은 널리 알려진 사실이다.
이런 라이브러리들 없이 데이터 바인딩을 구현할 수 있지만, 쉽지 않다는 점?</p>
<hr>
<h1 id="마치며">마치며</h1>
<p>이번 처음으로 제대로 MVVM을 리팩토링 해보고 느낀점을 적어봤다.
아직도 MVVM에 대해 배울 점이 많다고 생각한다.
앞으로 RxSwift를 배워서 MVVM과 접목시켜 리팩토링을 할 예정이다.</p>
<p>개인적으로 가장 중요하게 생각했던 것은
아키텍쳐 구현에 있어서 <strong>생각의 유연성</strong> 이라고 생각한다.
구현을 할 때 정답을 찾으려고 하면 오히려 더 막히는 느낌이 들었다.
그래서 많은 래퍼런스를 참고하고 자신에게 맞는 방식으로 Clean Architecture를 구현하면 되지 않을까 싶은 것이 내 생각이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] @escaping Closure(탈출 클로저)에 대한 고찰 with Network]]></title>
            <link>https://velog.io/@wannabe_eung/iOS-escaping-Closure%ED%83%88%EC%B6%9C-%ED%81%B4%EB%A1%9C%EC%A0%80%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0-with-Network</link>
            <guid>https://velog.io/@wannabe_eung/iOS-escaping-Closure%ED%83%88%EC%B6%9C-%ED%81%B4%EB%A1%9C%EC%A0%80%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0-with-Network</guid>
            <pubDate>Fri, 13 May 2022 09:17:10 GMT</pubDate>
            <description><![CDATA[<h1 id="escaping-closure탈출-클로저">@escaping Closure(탈출 클로저)!</h1>
<p>@escaping이란 키워드가 파라미터 타입 앞에 붙으면 함수가 끝난 이후에도 클로저를 실행할 수 있다. 
그런 이 클로저는 요긴하게 쓰이는 부분은 Network 작업을 할 때 종종 사용한다.
왜냐하면 콜백 함수로 비동기 작업을 수행할 때에는 함수의 동작이 이미 끝난 상태이므로..</p>
<p>클로저는 동적 메모리 할당으로 Heap영역에 메모리가 할당된다. 그래서 함수가 끝나도 클로저는 계속 참조할 수 있는 것이다.
물론 이 부분에서 <a href="https://velog.io/@wannabe_eung/strongreferencecycleinClosure">순환 참조</a>에 대해 신경써줘야할  부분이 있지만, 이것은 지금 논외로 하겠다.
순환 참조에 대해 자세하게 알아보고 싶으면 위의 링크를 클릭하면 된다.</p>
<hr>
<h1 id="network에서-escaping-closure">network에서 escaping closure</h1>
<p>탈출 클로저가 주로 network작업에서 쓰이는 이유는 대표적으로 다음과 같다.</p>
<ol>
<li>request를 보내고 response를 받을 때에는 클로저로 콜백 함수로써 데이터를 받기 때문</li>
<li>비동기 작업에서 작업의 순서를 정해주기 위해</li>
</ol>
<pre><code class="language-swift">func fetchMovies(from term: String, completion: @escaping () -&gt; Void) {
    var components = URLComponents(string: &quot;https://itunes.apple.com/search&quot;)!

    let movieName = URLQueryItem(name: &quot;term&quot;, value: term)
    let media = URLQueryItem(name: &quot;media&quot;, value: &quot;movie&quot;)
    let entity = URLQueryItem(name: &quot;entity&quot;, value: &quot;movie&quot;)
    let limit = URLQueryItem(name: &quot;limit&quot;, value: &quot;20&quot;)

    components.queryItems = [ movieName, media, entity, limit ]

    let url = components.url!

    var request = URLRequest(url: url)
    request.httpMethod = &quot;GET&quot;

    URLSession.shared.dataTask(with: request) {[weak self] data, response, error in
        guard let self = self else { return }
        if let error = error {
            print(error.localizedDescription)
            return
        }

        DispatchQueue.main.async {
            do {
                let object = try JSONDecoder().decode(Result.self, from: data!)
                self.items = object.results

                completion()
            } catch let error {
                print(error.localizedDescription)
            }
        }
    }
    .resume()
}</code></pre>
<p>위에 <code>fetchMovies</code> 메서드는 itunes open API를 통해 json데이터를 받아오는 과정이다.
여기서 중요한 부분은 <code>URLSession</code>을 통해 <code>dataTask</code>를 불러올 때이다.
<code>trailing closure</code>로 콜백 함수로 받아들여온 데이터를 비동기로 작업하는 것이 보일 것이다.</p>
<p>사실 이 <code>dataTask</code>라는 메서드도 당연하게? 하나의 탈출 클로저로 인자로 받고 있다.</p>
<pre><code class="language-swift">    open func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -&gt; Void) -&gt; URLSessionDataTask
</code></pre>
<h2 id="fetchmovies-로직-살펴보기"><code>fetchMovies</code> 로직 살펴보기</h2>
<p>이 메서드의 로직을 순서대로 살펴보자면
URL을 구성하는 Components들을 만들고 <code>URLSession</code>의 싱글톤 객체에 접근하여 <code>dataTask</code>메서드를 불러온다음
그것을 <code>resume</code> 하고 함수는 종료된다. </p>
<p>즉, <code>fetchMovies</code>는 데이터를 받아들여올 때까지 기다려주지 않고 종료된다.</p>
<p>이제 함수의 인자인 <code>completion</code>이라는 클로저를 콜백 함수 안에서 실행시킨다.
만약 여기서 <code>@escaping</code>이란 키워드를 붙여주지 않으면 오류가 뜬다.
당연하게도 <code>dataTask</code>의 콜백함수로 받는 <code>completionHandler</code>는 메서드가 종료되고 비동기 데이터를 가져오기 때문이다.</p>
<h2 id="왜-하필-escaping-일까">왜 하필 @escaping 일까?</h2>
<p>처음보면 어색한 개념일 것 같은 생소한 개념이지만, 사실은 프로그래머가 원하는데로 데이터를 사용하게 하기 위함이다.
대표적으로 비동기로 받는 데이터의 순서를 정해줄 수 있다.</p>
<pre><code class="language-swift">func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    if let text = searchBar.text {
        viewModel.fetchMovies(from: text) {[weak self] in
            self?.collectionView.reloadData()
        }
    }
}</code></pre>
<p>다음 코드는 UISearchBarDelegate를 채택하여 구현한 메서드이다.
방금 정의한 <code>fetchMovies</code>라는 메서드를 불러옴과 동시에 클로저를 정의해주고 있다.
클로저의 로직은 collectionView를 다시 그리는 <code>reloadData</code>메서드를 불러오고 있다.</p>
<p>여기서 방금 말한 <code>2. 비동기 작업에서 작업의 순서를 정해주기 위해</code> 라는 것이 나온다.</p>
<p>만약 탈출 클로저를 쓰지 않는다면, 이렇게 코드를 쓸 수도 있다.</p>
<pre><code class="language-swift">func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    if let text = searchBar.text {
        viewModel.fetchMovies(from: text)
        collectionView.reloadData()
        }
    }
}</code></pre>
<p>이렇게 되면 문제점이 뭐냐면 데이터를 받아오기전에 <code>reloadData</code>가 호출될 수 있다는 것이다.
결국 network를 사용한 로직은 UI 업데이트와 데이터간의 순서가 중요한데
이것을 해결할 수 있는 것이 탈출 클로저인 것이다.</p>
<hr>
<p>클로저에 대한 공식 스위프트 도큐먼트는 아래의 링크에서 참조할 수 있다.
<a href="https://docs.swift.org/swift-book/LanguageGuide/Closures.html">https://docs.swift.org/swift-book/LanguageGuide/Closures.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] CollectionLayout Dimension 알아보기]]></title>
            <link>https://velog.io/@wannabe_eung/iOS-NSCollectionLayoutDimension-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@wannabe_eung/iOS-NSCollectionLayoutDimension-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 09 May 2022 13:27:14 GMT</pubDate>
            <description><![CDATA[<p>UICollectionViewCompositionalLayout을 을 만들기 위해서는 NSCollectionLayoutDimension에 대해서 숙지하고 있어야 한다.
이름에서 볼 수 있듯이 각 item, group , section의 사이즈를 정해주는 객체이다.
사실 어려울건 없는 개념이라서 바로 코드로 넘어가면 될 듯하다.</p>
<p>혹시 여기서 Item, group, section이라는 개념이 생소하다면 
<a href="https://velog.io/@wannabe_eung/iOS-UICollectionViewCompositionalLayout-%EB%A7%8C%EB%93%A4%EA%B8%B0">UICollectionViewCompositionalLayout</a> 여기서 숙지하고 오길 바란다.</p>
<p><img src="https://velog.velcdn.com/images/wannabe_eung/post/83cce26d-0749-4036-b4b7-c99d13070b80/image.png" alt=""></p>
<h1 id="nscollectionlayoutdimesion">NSCollectionLayoutDimesion</h1>
<p>위에서 말했다 싶이 UICollectionViewCompositionalLayout의 사이즈를 정해주는 객체이다.</p>
<pre><code class="language-swift">let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(120.0), heightDimension: .absolute(160.0))</code></pre>
<p>이런식으로 작성해주면 된다. 그럼 item 사이즈는 결정됨</p>
<p>NSCollectionLayoutSize의 인자는 widthDimension, heightDimension 두개가 있는데
각각 자세히 살펴보자면 다음과 같다.</p>
<pre><code class="language-swift">@available(iOS 13.0, *)
open class NSCollectionLayoutDimension : NSObject, NSCopying {

    // dimension is computed as a fraction of the width of the containing group
    open class func fractionalWidth(_ fractionalWidth: CGFloat) -&gt; Self


    // dimension is computed as a fraction of the height of the containing group
    open class func fractionalHeight(_ fractionalHeight: CGFloat) -&gt; Self


    // dimension with an absolute point value
    open class func absolute(_ absoluteDimension: CGFloat) -&gt; Self


    // dimension is estimated with a point value. Actual size will be determined when the content is rendered.
    open class func estimated(_ estimatedDimension: CGFloat) -&gt; Self
</code></pre>
<h2 id="absolute">absolute</h2>
<p>예상 되는 것처럼 고정된 크기 값이다. </p>
<pre><code class="language-swift">let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(120.0), heightDimension: .absolute(160.0))</code></pre>
<p>방금과 같이 작성한 itemSize의 크기는 120 x 160이 된다.</p>
<h2 id="estimated">estimated</h2>
<p>런타임과 시스템의 글꼴 크기가 변경되는 경우 itemSize가 변경될 것을 고려하여 반환한다.
초기 값으로 정해주면 시스템이 실제 값을 반환하여 반환한다.
아직 실제로 사용해본적이 없어서 내부적으로 어떻게 돌아가는지는 모르겠다.</p>
<h2 id="fractional-widthheight">fractional Width/Height</h2>
<p>개인적으로 이 부분이 가장 중요하다고 생각
위에는 다른 객체에서도 자주 사용하는 Size 타입인데 fractional 같은 경우는 조금 생소할 수 있다.
사실 이것도 어려울 건 없는데 UIScreen.main.bounds.width/height의 값을 가지고 <strong>0~1 사이</strong>의 값을 대입하면
그 상대적인 크기를 반환해준다.</p>
<p>예를들어 </p>
<pre><code class="language-swift">let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8), heightDimension: .absolute(160.0))</code></pre>
<p>이라고 정의하면 itemSize는 메인 스크린 전체의 width 값의 0.8 값만큼을 반환받는다.</p>
<hr>
<p>전체 코드 </p>
<pre><code class="language-swift">func createBasicLayout() -&gt; NSCollectionLayoutSection {
    /// 각 item의 사이즈 설정 ( width: 120, height: 160 )
    let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(120.0), heightDimension: .absolute(160.0))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    /// 아이템의 마진 값 설정
    item.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)

    /// 아이템들이 들어갈 Group 설정
    /// groupSize 설정
    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .absolute(160.0))
    /// subitem에 item을 넣어주고 각 그룹 당 아이템이 보여질 갯수는 3개
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3)

    /// 최종적으로 section 설정
    let section = NSCollectionLayoutSection(group: group)

    /// 헤더 생성
    let titleHeader = createTitleHeaderLayout()
    /// 이 Layout은 헤더를 보여지도록 적용
    section.boundarySupplementaryItems = [ titleHeader ]
    /// 어떤 형식의 스크롤을 쓸지 결정
    section.orthogonalScrollingBehavior = .continuous

    return section
}</code></pre>
<p>AppleDocument :
<a href="https://developer.apple.com/documentation/uikit/nscollectionlayoutdimension">https://developer.apple.com/documentation/uikit/nscollectionlayoutdimension</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] UICollectionViewCompositionalLayout 만들기]]></title>
            <link>https://velog.io/@wannabe_eung/iOS-UICollectionViewCompositionalLayout-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@wannabe_eung/iOS-UICollectionViewCompositionalLayout-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Mon, 09 May 2022 10:09:34 GMT</pubDate>
            <description><![CDATA[<h1 id="uicollectionviewcompositionallayout">UICollectionViewCompositionalLayout</h1>
<ul>
<li>기존의 UICollectionViewLayout보다 더 확장된 레이아웃 제공</li>
<li>그림처럼 여러 섹션으로 나누어서 각 섹션끼리 관리 가능</li>
<li>item, group, section 순으로 적용해나가면 된다.</li>
</ul>
<p>기존 UICollectionViewLayout으로는 구현하기 힘든 여러 가지 UI들을 적용가능
각 섹션마다 서로 다른 레이아웃을 가지는 데도 쉽게 관리할 수 있음
기존 Layout은 하나의 콜렉션 뷰로 다양한 레이아웃을 가진 ScrollView를 갖기 쉽지 않음</p>
<p><img src="https://velog.velcdn.com/images/wannabe_eung/post/2bd7ad47-4b95-4c53-9dd7-41e6971b4b75/image.png" alt=""></p>
<p>예를 들어 요즘 앱들에서 많이 보이는 이런 디자인들..</p>
<hr>
<h1 id="compositionallayout-만들기">CompositionalLayout 만들기</h1>
<h2 id="compositionalsection-정의">CompositionalSection 정의</h2>
<pre><code class="language-swift">func createBasicLayout() -&gt; NSCollectionLayoutSection {
    /// 각 item의 사이즈 설정 ( width: 120, height: 160 )
    let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(120.0), heightDimension: .absolute(160.0))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    /// 아이템의 마진 값 설정
    item.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)

    /// 아이템들이 들어갈 Group 설정
    /// groupSize 설정
    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .absolute(160.0))
    /// subitem에 item을 넣어주고 각 그룹 당 아이템이 보여질 갯수는 3개
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3)

    /// 최종적으로 section 설정
    let section = NSCollectionLayoutSection(group: group)

    /// 헤더 생성
    let titleHeader = createTitleHeaderLayout()
    /// 이 Layout은 헤더를 보여지도록 적용
    section.boundarySupplementaryItems = [ titleHeader ]
    /// 어떤 형식의 스크롤을 쓸지 결정
    section.orthogonalScrollingBehavior = .continuous

    return section
}</code></pre>
<p>먼저 NSCollectionLayoutSection을 만들어주는 메서드 구현
위에서 말했 듯이 Item, Group, Section 순으로 만들어주고 차례차례 인자로 넣어주는 것이 보임</p>
<h2 id="uicollectionviewcompositionallayout-반환">UICollectionViewCompositionalLayout 반환</h2>
<pre><code class="language-swift">func createLayout() -&gt; UICollectionViewCompositionalLayout {
    /// return 값인 UICollectionViewCompositionalLayout은 콜백 함수로 두 개의 인자가 나온다.
    ///
    /// 콜백 함수 :
    /// sectionNumber [int] : sectionNumber마다 다르게 줄 수 있도록하는 인자
    /// env [NSCollectionLayoutEnvironment] : 사이즈, 인셋 등을 줄 수 있는 인자
    /// return [NSCollectionLayoutSection] : 만들어두었던 NSCollectionLayoutSection 객체를 넣어주면 최종적으로 Layout으로 반환됨
    return UICollectionViewCompositionalLayout {[weak self] sectionNumber, env -&gt; NSCollectionLayoutSection? in
        /// section 번호마다 다른 Layout을 설정하기 위해 switch 문
        switch sectionNumber {
        case 0:
            return self?.createMainLayout()
        default:
            return self?.createBasicLayout()
        }
    }
}</code></pre>
<p>최종 적으로 UICollectionView에 넣을 수 있는 Layout객체로 반환해주는 메서드 구현
굳이 Section과 Layout을 반환하는 메서드를 나눈 이유는
반환 값인 UICollectionViewCompositionalLayout의 콜백함수의 리턴 타입을 보면 알 수 있다.
코드를 보면 sectionNumber로 switch구문을 작성한 것이 눈에 띰</p>
<p>반환값인 UICollectionViewCompositionalLayout은 콜백함수로 섹션 번호랑, 레이아웃의 환경을 설정할 수 있는 인자를 반환받음 
여기서 모든 레이아웃을 한꺼번에 만들 수 있지만, 그러면 한 메서드 안에 코드가 너무 복잡해져 필자는 따로 나누는 것을 추천</p>
<p>왜 UICollectionViewCompositionalLayout이라는 값은 콜백함수로 이런 값을 가지는가? 
생각해보면 섹션 마다 각 커스텀한 레이아웃을 갖고 있을 것이기 때문에 이렇게 되어있는 것이 어찌보면 당연하다.</p>
<h2 id="collectionview의-layout으로-추가">CollectionView의 Layout으로 추가</h2>
<pre><code class="language-swift">lazy var collectionView: UICollectionView = {
    let layout = createLayout()
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
    collectionView.register(MovieCollectionViewCell.self, forCellWithReuseIdentifier: MovieCollectionViewCell.identifier)
    collectionView.register(MovieMainCollectionViewCell.self, forCellWithReuseIdentifier: MovieMainCollectionViewCell.identifier)
    collectionView.register(MovieCollectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: MovieCollectionHeaderView.identifier)
    collectionView.dataSource = self

    return collectionView
}()</code></pre>
<p>최종적으로 collectionView를 초기화 할 떄
createLayout() 메서드를 반환 받아서 섹션 마다 다른 레이아웃을 갖고 있는 레이아웃 객체를 생성
이제 섹션 마다 서로 다른 레이아웃을 갖고 있는 collectionView를 가질 수 있게 된다.</p>
<p><img src="https://velog.velcdn.com/images/wannabe_eung/post/217e2616-7394-4135-b1ac-29f7c670866d/image.gif" alt=""></p>
<hr>
<h2 id="고찰">고찰</h2>
<p>일반적으로 각 섹션마다 동일한 레이아웃을 사용할 거면 그냥 사용했던 UICollectionViewLayout을 사용하면 편할 것이라고 생각한다.
그런데 요즘 앱 트렌드 상 재밌는 UI들이 많이 출시하기 때문에 알아둬야 한다고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] AVFoundation을 이용하여 영상 진행바(UISlider) 구현하기]]></title>
            <link>https://velog.io/@wannabe_eung/iOS-AVFoundation%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EC%98%81%EC%83%81-%EC%A7%84%ED%96%89%EB%B0%94UISlider-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@wannabe_eung/iOS-AVFoundation%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EC%98%81%EC%83%81-%EC%A7%84%ED%96%89%EB%B0%94UISlider-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 08 May 2022 14:22:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/wannabe_eung/post/58b6c174-f629-4533-b0a4-61a53ce23e84/image.gif" alt=""></p>
<h1 id="avfoundation">AVFoundation</h1>
<p>AVFoundation은 동영상, 음악과 관련된 앱을 제작한다면 필수적으로 알아야할 프레임워크다.</p>
<p>이번에는 AVFoundation을 적절히 이용하여
영상의 특정 위치로 바로 이동할 수 있는 UISlider를 구현해보도록 하자.</p>
<p>참고로 AVFoundation의 OverView는 아래 링크에서 살펴볼 수 있다.
<a href="https://developer.apple.com/av-foundation/">https://developer.apple.com/av-foundation/</a></p>
<hr>
<h1 id="player에-observer-추가하기">Player에 Observer 추가하기</h1>
<pre><code class="language-swift">func prepareVideo(url: String) {
    guard let url = URL(string: url) else { return }

    player = AVPlayer(url: url)

    let playerLayer = AVPlayerLayer(player: player)
    playerLayer.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.height, height: UIScreen.main.bounds.width)

    videoPlayerView.layer.addSublayer(playerLayer)

    let interval = CMTime(seconds: 0.001, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
    player?.addPeriodicTimeObserver(forInterval:interval, queue: DispatchQueue.main, using: { [weak self] currentTime in
        self?.updateSlider(currentTime)
        self?.updateRemainingText(currentTime)
    })
}</code></pre>
<p>위의 메서드는 간략하게 말하자면 특정 URL을 받아서 동영상을 재생 시키는 메서드이다.</p>
<pre><code class="language-swift">    let interval = CMTime(seconds: 0.001, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
    player?.addPeriodicTimeObserver(forInterval:interval, queue: DispatchQueue.main, using: { [weak self] currentTime in
        self?.updateSlider(currentTime)
    })
}</code></pre>
<p>여기서 주목해야 할 것은 위의 코드이다.
Interval 이라는 상수는 CMTime이라는 객체를 받고 있는데,
0.001초마다 Player가 반응하게 만들기 위해서 0.001을 넣어주었다.</p>
<h2 id="cmtime">CMTime</h2>
<p>CMTime이란 시간 값을 분자로, 스케일을 분모로 하여 값을 만들어낸  후 시간을 표현하는 구조체이다.
AVFoundation에서 제공하는 이 구조체는 주로 미디어의 특정 위치를 시간으로 표현할 수 있는 도구라고 생각하면 된다.</p>
<p>쉽게 Seconds가 분자, Timescale이 분모로 들어가서 CMTime 객체가 만들어진다고 생각하면 된다.
만약 CMTime(seconds: 2, preferredTimescale: 2)이라면 CMTime은 1초가 된다.</p>
<h2 id="addperiodictimeobserver">addPeriodicTimeObserver</h2>
<p>그리고 현재 재생되는 AVPlayer 객체인 player에 <strong>addPeriodicTimeObserver</strong> 메서드를 불러주고 있다.
이 메서드는 세 개의 인자를 받고 있는데,</p>
<ol>
<li>forInterval
 이 인자 값으로는 CMTime 타입만 올 수 있으며
영상이 인자에 들어온 CMTime 값만큼의 간격으로 Observer를 두어
콜백함수에 작성한 클로저를 실행시킨다.</li>
</ol>
<ol start="2">
<li><p>queue
특정 간격마다 실행되는 클로저가 어떤 Queue에서 작동할지 정해준다.
나는 UI의 작업을 수행할 예정이라 메인 스레드로 이동 시켜 주었다.</p>
</li>
<li><p>using
영상이 정해준 간격만큼 시간이 지날때마다 실제로 실행되는 콜백함수이다.
이 콜백 함수의 인자로써 현재 시간을 나타내는 CMTime 객체가 들어온다.
이 인자를 사용하여 Slider를 실시간으로 업데이트 시켜주면 된다.
실제로 <strong>currentTime</strong>이란 객체를 받아서 <strong>updateSlider</strong>라는 메서드를 실행시켜주는 것을 볼 수 있다.</p>
</li>
</ol>
<p>이 메서드에 using 클로저 안에는 updateSlider라는 메서드를 불러주고 있다.
그 메서드의 자세한 사항은 아래에서 볼 수 있다.</p>
<hr>
<h1 id="콜백함수-로직-구현하기">콜백함수 로직 구현하기</h1>
<p>이제 본격적으로 Slider의 실제 값을 넣어줄 차례이다.</p>
<pre><code class="language-swift">    func updateSlider(_ currentTime: CMTime) {
        if let currentItem = player?.currentItem {
            let duration = currentItem.duration
            if CMTIME_IS_INVALID(duration) {
                return
            }
            progressBar.value = Float(CMTimeGetSeconds(currentTime) / CMTimeGetSeconds(duration))
        }
    }</code></pre>
<p>이 메서드는 인자로 currentTime 즉 0.001초 간격으로 생성되는 CMTime을 계속해서 받고 있는 셈이다 !
먼저 AVPlayer 객체로 부터 현재 재생되는 영상의 총 재생 시간을 받아오고 있다.
물론 duration이라는 것은 CMTime 타입이다. 그리고 이것을 <strong>CMTimeGetSeconds</strong>라는 메서드를 통해서
우리가 식별할 수 있는 초 단위의 Float형으로 반환할 수 있게 된다.</p>
<p>그리고 재생 바, 즉 progressBar에 value 값은 총 1이 되어야 한다. 
그래서 분모 값은 영상 전체의 길이인 duration을 넣어주었고
분자 값으로는 실시간으로 변경되는 currentTime을 초 단위의 시간으로 바꿔서 넣어주었다.
이제 영상을 재생시키면 ProgressBar는 영상 길이에 맞춰서 이동하게 된다 !</p>
<hr>
<h1 id="재생-바에-addtarget-추가하기">재생 바에 addTarget 추가하기</h1>
<p>이제 남은 것은 재생 바를 드래그를 하면 영상이 특정 위치로 이동하는 로직을 구현해야 한다.</p>
<pre><code class="language-swift">    lazy var progressBar: UISlider = {
        let slider = UISlider()
        slider.tintColor = .systemRed
        slider.addTarget(self, action: #selector(didChangedProgressBar(_:)), for: .valueChanged)

        return slider
    }()

    @objc func didChangedProgressBar(_ sender: UISlider) {
        guard let duration = player?.currentItem?.duration else { return }

        let value = Float64(sender.value) * CMTimeGetSeconds(duration)

        let seekTime = CMTime(value: CMTimeValue(value), timescale: 1)

        player?.seek(to: seekTime)
    }</code></pre>
<p>필자는 UI를 오직 코드로 짜기 때문에 progressBar에 addTarget을 추가했다.
여기서 주의할 점은 Slider의 드래그를 감지하기 위해서는 .valueChanged가 선택되어야 한다는 것이다.
이제 드래그가 될 때마다 <strong>didChangedProgressBar(_:)</strong>라는 메서드가 호출이 된다.</p>
<p>이 메서드에서도 필수적으로 영상의 총 길이가 필요하다.
sender로써 현재 사용하고 있는 UISlider라는 객체가 내려오고 이 sender의 value 값은 우리는 추출해낼 수 있다.
그리고 영상의 총 길이와 곱해주면 내가 드래그 한 위치의 값을 알게 된다.
그리고 CMTime객체로 다시 변형하여 AVPlayer 내부의 메서드로 구현된 seek(to:)에 찾아야 할 위치를 넣어주면 된다.</p>
<p>이제 player는 드래그 하면 특정 위치의 영상을 재생하게 된다 !</p>
<p><img src="https://velog.velcdn.com/images/wannabe_eung/post/44f1d449-f9ec-4a88-8b76-e2d0ed3c3e6d/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] 클로저(Closure) 순환 참조 문제]]></title>
            <link>https://velog.io/@wannabe_eung/strongreferencecycleinClosure</link>
            <guid>https://velog.io/@wannabe_eung/strongreferencecycleinClosure</guid>
            <pubDate>Thu, 05 May 2022 04:41:45 GMT</pubDate>
            <description><![CDATA[<p>Closure를 작성할 때, 우리는 지역변수를 참조할 때마다 순환참조 문제에 부딪히게 된다. </p>
<p>순환 참조의 이슈는 근본적으로 strong으로 서로 동시에 참조하고 있기 때문에 ARC가 0으로 안내려 가는 것이다. 원래 클로저가 종료되면 self로 선언해준 Reference Count도 0으로 내려가야 하지만, 다른 곳에서 클로저를 불러준 시점에서 Reference Count가 1이 증가하여 발생한 문제이다.</p>
<p>간혹 지역변수가 아니더라도 해당 Class에서 정의된 Property나 Method에 접근할 때도 마찬가지다. </p>
<p>그래서 그 해결방안으로</p>
<pre><code class="language-swift">{ [unowned self] _ in 
    /// Logic in Closure
}</code></pre>
<p>다음과 같은 코드로 순환참조를 해결할 수 있다는 것은 널리 알려진 사실이다.</p>
<p>하지만 근본적인 이유에 들어가서 왜 이것이 순환참조를 일으키고 도대체 [unowned self]라는 것은 어떤 것인가를 알아보도록 하자.</p>
<p>우선 이것을 알기 위해서는 ARC의 개념과 Strong, Weak 변수의 의미를 정확하게 알고 있어야 한다. 인스턴스의 참조 방식이 어떻게 이루어지는지 알기 위함이라고 할 수 있다.</p>
<h1 id="클로저의-reference-capture">클로저의 Reference Capture</h1>
<blockquote>
</blockquote>
<p>클로저는 기본적으로 Value Capture보다 Reference Capture를 기본적으로 수행한다. </p>
<p>이게 무슨 말이냐하면, 아래와 같은 NotifyPrice 함수가 있다고 해보자.</p>
<pre><code class="language-swift">func notifyPrice() {

    let percent = 10
    /// 클로저 값 캡쳐 시작
    var price = 200

    let notify: () -&gt; Void {
        print(&quot;Price is \(price).&quot;)
    }

    price = 300
    notify()
    /// 클로저 값 캡쳐 종료

    print(&quot;Function is Done&quot;)
}

결과 
// Price is 300.
// 300</code></pre>
<p>코드에서 notify라는 클로저가 정의되어 있다. </p>
<p>이 클로저는 함수에서 정의된 price를 참조하고 있는 것을 알 수 있다. 따라서 클로저의 값 캡쳐 범위는 price가 정의된 순간부터 클로저가 불릴때까지 라고 할 수 있다.</p>
<p>만약 클로저가 Value Capture라고 한다면 클로저를 정의 해주기 전에 초기화 된 200을 출력해야할 것이다. 하지만 클로저는 Reference Capture기 때문에 Price는 300을 출력하게 되는 것이다 ! </p>
<h1 id="클로저의-capture-list">클로저의 Capture List</h1>
<p>그렇다면 위의 Reference Capture를 Value Capture로 받을 수 있을까? </p>
<p>이때 사용하는 것이 클로저의 캡쳐 리스트(Capture List)이다. 바로 예시를 보자.</p>
<pre><code class="language-swift">func notifyPrice() {

    let percent = 10
    /// 클로저 값 캡쳐 시작
    var price = 200

    let notify: () -&gt; Void { [price] in
        print(&quot;Price is \(price).&quot;)
    }

    price = 300
    notify()
    /// 클로저 값 캡쳐 종료

    print(price)
}

결과 
// Price is 200.
// 300
</code></pre>
<p>잘 보면 클로저의 [price] 가 추가된 것을 확인할 수 있다. 결국 [ ] 안에 들어가는 멤버는 클로저의 캡쳐 리스트에 들어감으로써 Reference Capture를 Value Capture로 받을 수 있게 된다.</p>
<p>결과를 보면 이게 어떤 의미인지 정확하게 알 수 있다. price 변수를 Reference Capture할 경우에는 클로저 밖에서 price 값이 변경되어도 클로저의 실행결과는 변경된 값이 나왔다.</p>
<p>하지만 Value Capture인 경우에는 클로저가 정의된 시점 이전의 price를 하나의 값으로 Capture하면서 200으로 되고, 그 이후에 price 값이 변경되어도 클로저의 결과값은 변하지 않는다.</p>
<p>이처럼 클로저에서 Value Capture를 받고 싶다면 그 멤버들을 <strong>[ _ _ ]  in</strong> 이라는 틀 안에 넣으면 된다.</p>
<h2 id="예외--reference-type의-value-capture">예외 : Reference Type의 Value Capture</h2>
<p>하지만 유의할 것이 있다. 바로 대표적인 Reference Type인 class같은 경우에는 클로저의 캡쳐리스트의 멤버로 선언해도 Reference Capture를 한다. 이 점을 꼭 유의했으면 한다.</p>
<h1 id="클로저의-순환-참조-이슈">클로저의 순환 참조 이슈</h1>
<p>그렇다면 클로저는 어떤 요인으로 순환 참조 이슈가 발생하는 것일까? </p>
<pre><code class="language-swift">class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -&gt; String = {
        if let text = self.text {
            return &quot;&lt;\(self.name)&gt;\(text)&lt;/\(self.name)&gt;&quot;
        } else {
            return &quot;&lt;\(self.name) /&gt;&quot;
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print(&quot;\(name) is being deinitialized&quot;)
    }
}
</code></pre>
<p>여기 HTMLElement라는 class를 보여주는 코드가 있다. 여기서 눈에 띄는 것은 지연 저장 프로퍼티를 선언한 asHTML 클로저라고 할 수 있다. 이 클로저는 text가 nil이 아닐 경우와 nil일 경우를 나눠서 Property 값을 접근하고 있다.</p>
<p>문제는 아래의 코드에서 발생한다.</p>
<pre><code class="language-swift">var paragraph: HTMLElement? = HTMLElement(name: &quot;p&quot;, text: &quot;hello, world&quot;)
print(paragraph!.asHTML())
// Prints &quot;&lt;p&gt;hello, world&lt;/p&gt;&quot;</code></pre>
<p>지역 변수인 paragraph는 HTMLElement 인스턴스를 갖게 된다. 따라서 인스턴스의 RC값이 1이 증가하게 된다. 그리고 print로 인스턴스로 접근하여 asHTML이라는 클로저에 접근하고 있다.</p>
<p>따라서 아래의 그림과 같은 강한 순환 참조가 발생하고 마는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/wannabe_eung/post/7e49d7ff-ee30-422d-9484-85ee9287b656/image.png" alt=""></p>
<p>이것은 클로저를 어떤 특정 프로퍼티에 선언해줌과 동시에 클로저에서 self를 접근해서 나타나는 이슈이다.</p>
<p>따라서 방금같은 클로저의 Capture List에  참조하는 self를 weak나 unowned로 설정해주면 이 강한 사슬을 끊어줄 수 있게 된다. </p>
<pre><code class="language-swift">lazy var asHTML: () -&gt; String = { [weak self] in
    guard let self = self else { return } 
    if let text = self.text {
        return &quot;&lt;\(self.name)&gt;\(text)&lt;/\(self.name)&gt;&quot;
    } else {
        return &quot;&lt;\(self.name) /&gt;&quot;
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] CompeltionHandler를 예시를 통해 알아보자 !]]></title>
            <link>https://velog.io/@wannabe_eung/Swift-CompeltionHandler%EB%A5%BC-%EC%98%88%EC%8B%9C%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@wannabe_eung/Swift-CompeltionHandler%EB%A5%BC-%EC%98%88%EC%8B%9C%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Wed, 16 Mar 2022 03:50:05 GMT</pubDate>
            <description><![CDATA[<p>때는 Swift를 배운지 얼마되지 않았을 때... UIKit을 쓸 때 굉장히 자주 등장하는 CompletionHandler가 이해가 잘 되지 않았다. 물론 UIKit을 배우면서 이 메서드가 끝나갈 때 작동하는 구나~ 라고 어렴풋이 공부했던 기억이 있다. 하지만 이제는 알게 되어서 간단한 예시를 통해 나도 공부할겸 기록을 남겨보려고 한다.</p>
<h2 id="completionhandler">CompletionHandler</h2>
<p>CompletionHandler는 결국 클로저다. Swift 문법에 있어서 꽃이라고 불리우는 그 클로저! 만약 클로저에 대한 이해가 부족하다면 이해를 하고 오길 권장한다. </p>
<p>일단 제목에 쓰여진 것처럼 한 가지 예시로 CompletionHandler에 대해 알아보려고 한다. 일단 특정 URL에서 Data파일을 가져오는 메서드를 구현해보도록 하자. 그리고 그 Data 파일을 string 형식으로 변환시키는 작업을 수행하여 CompletionHandler로 string 값을 전달해주는 메서드를 구현해보자.</p>
<p>글만 봐서는 잘 모르니 예시 코드로 살펴보자.</p>
<pre><code class="language-swift">func downloadJson(url : String, completionHandler : (string?) -&gt; Void) {
    let url = URL(string : url)
    let data = try! Data(contentsOf : url)
    let json = String(data : data, encoding : .utf8)

    completionHandler(json)
}</code></pre>
<p>우선 downloadJson메서드에 url과 completionHandler라는 클로저를 파라미터로 넣어주었다. 그리고 인자로 부터 가져온 url이 json이라는 String 타입의 상수로 변형되어 completionHandler의 인자로 넣어주었다. </p>
<p>여기서 CompletionHandler는 어떻게 해석할 수 있을까?</p>
<blockquote>
<p><em>*<em>우리는 이 CompletionHandler의 로직을 나.중.에 구현해 줄 것이다.
*</em></em></p>
</blockquote>
<h2 id="completionhandler의-로직을-나중에-구현">CompletionHandler의 로직을 나중에 구현?</h2>
<p>이것도 글로만 보면 무슨 소리인가 싶다. 예제 코드를 보면서 살펴보자.</p>
<pre><code class="language-swift">downloadJson(myURL) { [weak self] text in
    guard let self = self else { return }
    self.editView.text = text           
}
</code></pre>
<p>이제 만들어두었던 downloadJson 메서드를 호출하고 미리 만들어준 myURL이라는 URL주소를 넣어주자. 그리고 주목해야 할 것은 바로 Trailing 클로저다! (Trailing Closure를 모르시다면 이해하기 힘드실 수도 있다...) </p>
<p>이 클로저에가 받고있는 text는 무엇일까? 바로 앞서 downloadJson에서 로직을 구현해줄 때, completionHandler의 인자로 json을 넣어준 것이다 ! 곧 trailing 클로저의 text와 downloadJson의 로직의 json이라는 상수는 똑같다는 것이다 ! </p>
<p>우리는 completionHandler의 구현을 나중에 해준다고 했다. 그것을 trailing 클로저를 통해 로직을 구현해나가면 되는 것이다. 나는 View에 있는 editView.text에 그 값을 넣어주었다. 그럼 이제 downloadJson이라는 로직이 실행되면서 completionHandler가 수행되기 시작하면, string으로 받은 text를 받아서 내가 원하는 로직대로 수행 된다 ! </p>
<hr>
<p>여담이지만 이렇게 네트워크를 이용하는 작업에서는 Concurrent Queue로 GCD를 통해 global Queue로 보내줘야 한다. 그러면 받은 데이터를 return 값으로 보내주질 못해서 completionHandler로 받아진 데이터를 사용하는게 전통적인 방법이다. 이것을 해결하기 위해 RxSwift를 쓰면 유용하다..!<img src="https://images.velog.io/images/wannabe_eung/post/b3230739-8b6b-4b3a-a88c-a3e2945c2e31/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] GCD, DispatchQueue의 종류를 알아보자 !]]></title>
            <link>https://velog.io/@wannabe_eung/iOS-GCD-Queue%EC%9D%98-%EC%A2%85%EB%A5%98%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@wannabe_eung/iOS-GCD-Queue%EC%9D%98-%EC%A2%85%EB%A5%98%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 15 Mar 2022 14:49:52 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/wannabe_eung/post/31456e78-978c-4133-9feb-1928f936daff/image.png" alt="">이전에 <a href="https://velog.io/@wannabe_eung/iOS-GCD-%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">GCD</a>와 <a href="https://velog.io/@wannabe_eung/iOS-%EB%8F%99%EA%B8%B0%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">동기와 비동기</a>가 무엇인지 간략하게 알아보았다. 이번에는 마지막으로 DispatchQueue의 종류를 알아보고, Serial Queue와 Concurrent Queue에 대해 알아보려고 한다. </p>
<p>우리가 DispatchQueue라는 코드를 통해 Queue라는 곳으로 작업들을 보낸다. 이 Queue에는 두 가지 종류가 있다. 바로 Serial Queue와 Concurrent Queue가 이에 해당한다.</p>
<h2 id="serial-queue직렬-큐">Serial Queue(직렬 큐)</h2>
<p>먼저 Serial Queue를 알아보자. 우리가 알고 있는 DispatchQueue.main 이 여기에 해당한다! mainQueue는 main스레드로만 작업을 보낸다는 특징이 있다.
<img src="https://images.velog.io/images/wannabe_eung/post/f727dbd7-5ea0-4c01-abc9-7fb21524253f/image.png" alt=""></p>
<p>위 사진은 GCD를 통해서 DispatchQueue로 보내진 작업 목록들이다. 작업들을 Queue로 보내지면 Serial Queue는 다음과 같이 처리된다.</p>
<p><img src="https://images.velog.io/images/wannabe_eung/post/748a70d3-824e-4312-a527-1891a940717a/image.png" alt=""></p>
<p>위의 사진과 같이 Queue에 있는 작업 목록들이 <strong>차.례.대.로</strong> 스레드2 라는 하나의 스레드로 작업이 수행되는 것을 볼 수 있다. 즉</p>
<blockquote>
<p>💡 Serial Queue는 Queue의 작업 목록들이 오직 하나의 스레드로만 전달되어 작업을 수행하는 Queue이다.</p>
</blockquote>
<h2 id="concurrent-queue동시-큐">Concurrent Queue(동시 큐)</h2>
<p>그렇다면 Concurrent Queue는 무엇일까? 우리가 알고 있는 DispatchQueue.global() 이 여기에 해당한다. Global Queue에는 QoS라는 우선순위를 가지는 6 종류가 있다.
<img src="https://images.velog.io/images/wannabe_eung/post/481fa38f-33cb-44f4-80ab-742db3509565/image.png" alt=""></p>
<p>우리가 작업들을 DispatchQueue를 이용하여 Concurrent Queue로 보내주면 당연하게 Queue에 작업목록들이 쌓이게 된다. 위의 사진과 같이 [task 1]이 스레드2에 가서 처리되는 것은 Serial Queue와 동일하지만!</p>
<p><img src="https://images.velog.io/images/wannabe_eung/post/9c857718-25c8-4f94-996d-f5abae4ef6fb/image.png" alt=""></p>
<p>Queue에 남아있는 작업들이 <strong>여.러.스.레.드</strong> 로 옮겨가서 작업이 동시에 진행되는 것을 알 수 있다 ! 물론 여기서 어떤 스레드로 분산할지는 GCD가 알아서 결정하기 때문에 우리가 상관하지 않아도 된다. 즉</p>
<blockquote>
<p>💡 Concurrent Queue는 Queue에 있는 작업들이 여러 스레드로 옮겨가서 동시에 작업이 수행된다.</p>
</blockquote>
<h3 id="그럼-concurrent-queue가-더-좋은거-아니야">그럼 Concurrent Queue가 더 좋은거 아니야?</h3>
<p>여기까지만 보면 비동기와 동기에서는 비동기가 많이 쓰이는 것과 같이 Serial과 Concurrent 중에는 Concurrent가 많이 쓰이고 Serial은 덜 쓰일 거라는 느낌적인 느낌이 든다. </p>
<p>Serial Queue는 작업들을 한 개의 스레드로 옮겨가서 작업을 수행하기 때문에 <strong>순서가 중요한 작업</strong>을 보통 Serial Queue로 처리한다.</p>
<p>반면에 Concurrent Queue는 여러 스레드로 동시에 작업이 수행되어서 보통 순서와 관련 없는 유사한 여러 작업들을 수행할 때 사용한다.</p>
<h3 id="비동기async와-concurrent-queue의-차이점">비동기(Async)와 Concurrent Queue의 차이점?</h3>
<p>여기까지 알았다면 이제 비동기와 동시(Concurrent Queue)의 혼동이 생길 수도 있다. (내가 그랬다...) 다시 한번 살펴보면 일단 작업이 시작되는 기준을 잘 알아야 한다 !</p>
<p>일단 비동기의 경우에는 작업이 메인스레드 -&gt; 다른 스레드로 옮겨가고 그 작업을 기다리지 않는 것을 말한다.</p>
<p>반면에 Concurrent Queue에서는 DispatchQueue를 통해 Queue에 들어있는 작업 목록들이 여러 개의 스레드로 분산되어 작업을 처리하는 것을 말한다.</p>
<hr>
<p>이 포스트는 앨런님의 <a href="https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation?inst=f3dcde30">강의</a>에서 발췌했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] 동기/비동기, 동시성 프로그래밍 알아보기 ]]></title>
            <link>https://velog.io/@wannabe_eung/iOS-%EB%8F%99%EA%B8%B0%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@wannabe_eung/iOS-%EB%8F%99%EA%B8%B0%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 15 Mar 2022 13:07:33 GMT</pubDate>
            <description><![CDATA[<p><a href="https://images.velog.io/images/wannabe_eung/post/501d3575-c8d3-48e5-b775-59d3d41d5ca1/image.png"></a>
저번 시간에는 <a href="https://velog.io/@wannabe_eung/iOS-GCD-%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">GCD</a>에 대해 간략하게 알아봤다. 이번 시간에는 GCD의 동작을 더 정확하게 이해하기 위해서 동기와 비동기의 개념을 알아보도록 하자. </p>
<h2 id="비동기asynchronous">비동기(Asynchronous)</h2>
<p><img src="https://images.velog.io/images/wannabe_eung/post/834beee4-5f19-4cb0-85fb-84ee6e29705b/image.png" alt=""></p>
<p>비동기는 메인스레드에 있는 작업하나가 다른 스레드에 옮겨 갈때, 그 작업의 결과(끝나는 시점)을 기다리지 않고 바로 다음 작업이 실행되는 것이다. 여기서는 [task1][이 다른 스레드로 옮겨갔고 메인스레드에서는 [task2]가 [task1]의 작업을 기다리지 않고 즉시 실행된다.</p>
<p><img src="https://images.velog.io/images/wannabe_eung/post/3ad518cf-b4f0-47ad-964c-734ae6b9edcc/image.png" alt=""></p>
<p>즉 메인스레드는 다음과 같이 된다. 결국 [task1]이 <strong>오래 걸리든 안 걸리든 상관하지 않겠다는 의미이다.</strong> 이 의미를 코드로 살펴보도록 하자.</p>
<pre><code class="language-swift">DispatchQueue.global().async {
    print(&quot;나는 현재&quot;)
    print(&quot;비동기를 배우는&quot;)
    print(&quot;중이다...&quot;)
}</code></pre>
<p>위 코드의 의미는 메인스레드에서는 global Queue로 보낸 작업(여기에서는 클로저 안에 있는 print 3개)을 기다리지 않고 바로 다음 작업을 수행하겠다. 라는 의미이다.</p>
<h2 id="동기synchronous">동기(Synchronous)</h2>
<p><img src="https://images.velog.io/images/wannabe_eung/post/0ec605e0-9884-4f7c-b5d7-f25014fda5ec/image.png" alt=""></p>
<p>이번에는 동기를 알아보자. 위의 비동기의 사진과 같이 [task1]을 스레드2로 보냈지만, 메인스레드에는 여전히 block이란 것이 막고있어서 [task2]가 실행되지 않고 있다. <strong>이것은 다른 스레드로 간 [task1]의 결과를 기다린다는 의미로도 해석</strong>될 수 있다.</p>
<pre><code class="language-swift">DispatchQueue.global().sync {
    print(&quot;나는 현재&quot;)
    print(&quot;동기를 배우는&quot;)
    print(&quot;중이다...&quot;)
}</code></pre>
<p>이 코드를 해석하면 global Queue로 보낸 작업을 메인스레드에서는 그 작업이 끝날 때까지 기다린다는 의미가 된다 ! </p>
<p><strong>_그런데 한 가지 의문이 드는 점이 있다. 저렇게 다른 스레드로 보낸 작업을 동기화로 기다릴거라면, 굳이 다른 스레드로 보내서 작업을 처리할 필요가 있는가? 라는 의문이다..!
_</strong></p>
<p>그래서 실제로 많은 코드들을 보면 DispatchQueue를 사용할 때에는 주로 비동기인 async를 많이 사용하는 편이다. 결국 우리가 사용하고 싶은건 <strong>비동기 프로그래밍</strong>인데 이렇게 작업을 기다리면 무슨 소용이 있나 싶다.</p>
<h3 id="소스코드로-이해하기">소스코드로 이해하기</h3>
<p>그럼 여태까지 내용을 소스코드로 이해해보자.</p>
<pre><code class="language-swift">func task1() {
    print(&quot;Task 1 시작&quot;)
    sleep(1)
    print(&quot;Task 1 완료&quot;)
}

func task2() {
    print(&quot;Task 2 시작&quot;)
    print(&quot;Task 2 완료&quot;)
}

func task3() {
    print(&quot;Task 3 시작&quot;)
    sleep(4)
    print(&quot;Task 3 완료&quot;)
}

// 비동기 작업
DispatchQueue.global().async {
    task1()
}

DispatchQueue.global().async {
    task2()
}

DispatchQueue.global().async {
    task3()
}

// Task 1 시작
// Task 2 시작
// Task 3 시작
// Task 2 완료
// Task 1 완료
// Task 3 완료

// 동기 작업

DispatchQueue.global().sync {
    task1()
}

DispatchQueue.global().sync {
    task2()
}

DispatchQueue.global().sync {
    task3()
}

// Task 1 시작
// Task 1 완료
// Task 2 시작
// Task 2 완료
// Task 3 시작
// Task 3 완료
</code></pre>
<hr>
<p>이 포스트는 앨런님의 <a href="https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation?inst=f3dcde30">강의</a> 내용에서 발췌했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] 동시성 프로그래밍을 구현할 수 있는 GCD 알아보기]]></title>
            <link>https://velog.io/@wannabe_eung/iOS-GCD-%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@wannabe_eung/iOS-GCD-%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 15 Mar 2022 10:59:32 GMT</pubDate>
            <description><![CDATA[<p>iOS 개발을 하다보면 필수적으로 알아야할 개념인 <strong>GCD(Grand Central Dispatch)</strong> 를 알아야 한다. 오늘은 동시성 프로그래밍을 위한 GCD를 알아보도록 하자. 먼저 동시성 프로그래밍이란 무엇이고 GCD란 무엇일까? 하나하나씩 알아보자.</p>
<h2 id="동시성-프로그래밍">동시성 프로그래밍</h2>
<p><img src="https://images.velog.io/images/wannabe_eung/post/af2fd751-3922-43bd-896e-b23da2cf04dc/image.png" alt=""></p>
<p>여기 좋은 그림을 하나 가져왔다. 사진을 보면 하얀 실이 바늘 구멍을 들어가면 여러가지 색의 줄로 나뉘고 있다. 이 줄들이 바로 스레드(Thread)이다. 여기서 잠깐 상상력(?)을 발휘해보자. 사진에는 보이지 않지만 실 위에 많은 작업들이 쌓여있다고 상상해보자. 그럼 당연히 하나의 실(스레드)보다 여러 가지 색의 실로 나뉘면 작업을 수행하기 더욱 수월할 것이라는 것을 직감적으로 알 수 있다 이것이 바로 동시성 프로그래밍이다 !</p>
<p>그렇다면 어떻게 코드로 동시성 프로그래밍을 구현할 수 있을까?</p>
<p>iOS 개발자들은 이런 작업들을 Queue로만 보내주면 된다 !</p>
<blockquote>
<p>바로 여기서 등장하는 것이 <strong>GCD(Grand Central Dispatch)</strong>란 개념이다.</p>
</blockquote>
<h2 id="gcdgrand-central-dispatch">GCD(Grand Central Dispatch)</h2>
<p>작업을 대기행렬(Queue)을 보내는 것은 크게 GCD와 Operation이 있는데, 이번에는 GCD에 대해 알아볼까 한다. </p>
<p><em>Operation은 GCD를 기반으로 만들어졌으며, 주로 복잡한 작업을 수행할 때 사용하고 데이터와 기능을 캡슐화한 객체이다. 반대로 GCD는 간단하고 메서드 위주의 작업을 수행할 때 사용한다.</em></p>
<blockquote>
<p>💡 GCD란 직접적으로 스레드를 관리하지 않고 작업을 Queue로 보내 분산처리 하는 방법이다.</p>
</blockquote>
<p>여기까지 들어도 이해가 가지 않을 수 있는데, 작업을 Queue로 보내면 메인스레드에서 처리 되었던 작업들이 차례차례 다른 스레드2번, 스레드3번, 스레드 4번... 으로 <strong>작업이 자동적으로 분산처리</strong> 된다는 뜻이다 ! </p>
<p><img src="https://images.velog.io/images/wannabe_eung/post/88f46427-44cd-4326-b05c-c59c30239ea6/image.png" alt=""></p>
<p>결국 궁극적으로 여러 작업들이 <strong>비동기적으로 실행</strong>된다는 것이다. 물론 여기서 말하는 작업들은 대부분 네트워크와 관련된 작업을 말한다. 왜냐하면 iOS에서는 UI와 관련된 작업들은 모두 메인스레드라는 하나의 스레드에서 처리하도록 되어있기 때문이다.</p>
<pre><code class="language-swift">DispatchQueue.global().async {
    print(&quot;나는&quot;)
    print(&quot;GCD를 배우고 있어&quot;)
}</code></pre>
<p>위 코드에서는 DispatchQueue(큐에 보낼거야), global(global 큐로), async(비동기적으로) 라는 의미로 클로저 안에는 하나의 <strong>작업</strong>을 의미한다. </p>
<p>여기서는     </p>
<pre><code class="language-swift">print(&quot;나는&quot;)
print(&quot;GCD를 배우고 있어&quot;)
</code></pre>
<p>이 코드가 하나의 작업(task)가 되는 것이다 ! (통으로 묶는다는 뜻..)</p>
<hr>
<p>지금까지 간단하게 GCD와 동시성 프로그래밍을 알아보았다. 다음에는 동기(sync)와 비동기(async)의 차이점과 Concurrent Queue와 Serial Queue의 차이점을 알아보도록 하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] WebView의 종류를 알아보자 !]]></title>
            <link>https://velog.io/@wannabe_eung/iOS-WebView%EC%9D%98-%EC%A2%85%EB%A5%98%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@wannabe_eung/iOS-WebView%EC%9D%98-%EC%A2%85%EB%A5%98%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sat, 12 Mar 2022 02:51:16 GMT</pubDate>
            <description><![CDATA[<p>주로 뉴스나 여러 커뮤니티 앱을 보면 내장되어 있는 것이 바로 <strong>WebView</strong>이다. WebView는 주로 특정 앱을 실행하고 하이퍼링크나 정보성 글을 클릭했을 때 보여진다. 이건 말 안해도 자주 사용해 보셨을 것이기 때문에 익숙할 것이라고 생각한다. 그럼 WebView의 종류와 그 쓰임새를 알아보자.</p>
<p><img src="https://images.velog.io/images/wannabe_eung/post/b7b6ba25-d6ea-4f78-8a55-c11e8a8d1327/image.png" alt=""></p>
<h2 id="webview의-종류">WebView의 종류?</h2>
<p>iOS에는 WebView를 구현할 수 있는 방법이 총 3가지가 있다.     </p>
<ol>
<li>UIWebView</li>
<li>SFSafariView</li>
<li>WKWebView</li>
</ol>
<p>결론부터 말하자면 특별한 경우가 아닌 이상, WKWebView를 사용하면 된다. 그럼 하나씩 어떤 차이점이 있는지 알아보자.</p>
<h2 id="uiwebview">UIWebView</h2>
<p>UIWebView는 일단 출시된지 너~무 오래됐다. iOS 2.0부터 쓰여왔기 때문에... 한국에 처음 출시된 아이폰 3gs에서 쓰였던 WebView라고 생각하면 될 것 같다. 일단 성능적인 부분에서 많이 부족하고, 복합적인 측면에서 WebView는 사용을 안 하는 것이 좋을 것 같다.
<img src="https://images.velog.io/images/wannabe_eung/post/25372c0f-4d03-4523-9729-a33680e762ac/image.png" alt="">
이 사실을 알고 있는 Apple의 공식문서에서는 WebView를 Deprecated해서 개발자들의 사용을 막아두고 있다.</p>
<h2 id="wkwebview">WKWebView</h2>
<p>가장 많이 사용되고 있는 것은 WKWebView이다. WKWebView는 iOS 8.0부터 꾸준하게 사용되어 오고 있는 WebView이다. 일반적으로 위에서 설명했던 UIWebView보다 성능이 좋다. WKWebView에는 또다른 장점이 있는데, 바로 </p>
<blockquote>
<p> 웹 페이지에서 할당하는 메모리는 앱과 별도의 스레드에서 관리</p>
</blockquote>
<p>즉 웹 페이지는 앱 메모리와 별도로 동작하기 때문에 웹 페이지의 메모리가 아무리 크더라도 앱이 죽지 않는다고 볼 수 있다.</p>
<h2 id="sfsafariview">SFSafariView</h2>
<p>마지막으로 SFSafariView이다. iOS 9.0부터 사용되기 시작했으며, 이름에서 볼 수 있는 것처럼 Safari를 이용하는 WebView이다. 사용자가 어떤 웹 페이지에 들어가면, Safari에서 웹 페이지를 구동하는 것과 같은 똑같은 화면이 구현된다. WKWebView에서는 단순 웹 페이지 하나만 보여주는 역할을 한다고 하면, SFSafariView는 사파리의 기능을 이용할 수가 있어서 더욱 다양한 동작들을 수행할 수 있게 된다. 여기서 또 주목할 점은</p>
<blockquote>
<p>기존 아이폰의 Safari 쿠키, 데이터 등을 공유할 수 있다.</p>
</blockquote>
<p>라는 점이다. 앱 내에서 SFSafariView를 열어도 기존에 내가 사용하던 Safari의 쿠키와 데이터를 공유할 수 있다는 장점?이 있다.</p>
<hr>
<p>지금 까지 WebView 종류에 대해서 살펴봤다. 주로 사용하는 것은 WKWebView와 SFSafariView인 것을 확인했다. 일반적으로 사용하는 것은 WKWebView이지만 &quot;나는 웹 페이지에서 좀 더 복잡한 기능들을 사용자가 했으면 좋겠다!&quot; 라고 한다면 SFSafariView도 좋은 선택지가 될 것 같다 ! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] 앨범의 이미지를 선택할 수 있는 PHPickerViewController를 알아보자!]]></title>
            <link>https://velog.io/@wannabe_eung/%EC%95%A8%EB%B2%94%EC%9D%98-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A5%BC-%EC%84%A0%ED%83%9D%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-PHPickerViewController%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@wannabe_eung/%EC%95%A8%EB%B2%94%EC%9D%98-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A5%BC-%EC%84%A0%ED%83%9D%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-PHPickerViewController%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Wed, 16 Feb 2022 02:56:34 GMT</pubDate>
            <description><![CDATA[<h1 id="phpickerviewcontroller가-뭐야">PHPickerViewController가 뭐야?</h1>
<p><img src="https://images.velog.io/images/wannabe_eung/post/b74499b1-881d-48a0-bc6a-f11256a4b32b/image.png" alt="">
거의 대부분의 앱에서 볼 수 있는 위와 같이 로컬 라이브러리와 연결하여 사진과 동영상을 선택할 수 있는 것이 바로 <strong>PHPickerViewController</strong>이다. 그런데 이것은 iOS14+부터 사용이 가능하다. 그 이전에는 UIImagePickerController를 사용했다. Apple은 PHPickerViewController를 새로 발표하여 이전보다 더 안정성을 개선하고 많은 이점을 제공한다고 밝혔다. 그렇다면 어떤 이점이 있길래 그러는 걸까?</p>
<h1 id="개요">개요</h1>
<ol>
<li>느린 이미지 로딩과 복구 UI 개선</li>
<li>Raw 와 파노라마 이미지의 안정적인 처리 개선</li>
<li>UIImagePickerControll에서 사용할 수 없는 이미지나 동영상 기능 추가</li>
<li>Live Photos만 표시하도록 할 수 있는 기능</li>
<li>라이브러리 사용 권한 요청 없이 PHLivePhoto 사용 가능</li>
<li>유효하지 않는 입력에는 엄격한 규제 추가</li>
</ol>
<pre><code class="language-swift">class PHPickerViewController : UIViewController</code></pre>
<p>Apple에서 정의하는 PHPickerViewController는 UIViewController를 상속받고 있다. 이제 본격적인 구현 방법을 알아보자.</p>
<hr>
<h2 id="picker-구성하기">Picker 구성하기</h2>
<p>우선 가장 기본적인 PHPickerViewController의 인스턴스를 만들어야한다! 일단 선행되어야 할 것은 PhotosUI를 따르고 있기 때문에, 이것을 import 해줘야 한다. 그리고 인스턴스를 만들기 위해 configuration을 정해줘야한다. PHPickerConfiguration 구조체의 자세한 값들을 보고 싶으면 <a href="https://developer.apple.com/documentation/photokit/phpickerconfiguration">여기</a>를 클릭해주면 된다!</p>
<pre><code class="language-swift">import photosUI

var configuration = PHPickerConfiguration() // 1.
configuration.selectionLimit = 1 // 2.
configuration.filter = .images // 3.
let picker = PHPickerViewController(configuration: configuration)</code></pre>
<p>PHPickerViewController의 인스턴스를 만들기 위해서는 초기 값으로 PHPickerConfiguration의 인스턴스가 필요하다.</p>
<ol>
<li>먼저 PHPickerConfiguration을 생성하여 configuration 변수에 넣어주도록 한다.</li>
<li>configuration.selectionLimit는 최대로 선택할 사진 및 동영상의 개수를 입력해주면 된다.</li>
<li>configuration.fliter는 라이브러리에 접근할 때 적용되는 필터 역할을 한다. 타입은 총 네 가지가 있다. images, livePhotos, videos와 이것들을 짬뽕시킬 수 있는 any가 있다.<pre><code class="language-swift">static let images: PHPickerFilter
static let livePhotos: PHPickerFilter
static let videos: PHPickerFilter
static func any(of: [PHPickerFilter]) -&gt; PHPickerFilter</code></pre>
</li>
</ol>
<p>마지막으로 구성된 configuration을 가지고 PHPickerViewController의 초기값으로 넣어주면 인스턴스 완성이다.</p>
<h1 id="phpicker-delegate">PHPicker Delegate</h1>
<p>이제 PHPicker를 만들었다면, 선택된 이미지를 가지고 놀기 위해 delegate를 채택해야한다. </p>
<pre><code class="language-swift">picker.delegate = self
</code></pre>
<p>그리고 PHPickerViewControllerDelegate를 채택하여 필수 메서드를 구현해주도록 하자.</p>
<pre><code class="language-swift">extension FeedViewController : PHPickerViewControllerDelegate { // 1.

    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { // 2.
        var selectedImage : UIImage?

        picker.dismiss(animated: true, completion: nil) // 3.

        let itemProvider = results.first?.itemProvider // 4.
        if let itemProvider = itemProvider,
           itemProvider.canLoadObject(ofClass: UIImage.self) { // 5.
            itemProvider.loadObject(ofClass: UIImage.self) { image, error in // 6.
                DispatchQueue.main.async { //
                    guard let selectedImage = image as? UIImage else { return }
                    let uploadViewController = UploadViewController(uploadImage: selectedImage)
                    let navigationController = UINavigationController(rootViewController: uploadViewController)
                    navigationController.modalPresentationStyle = .fullScreen

                    self.present(navigationController, animated: true, completion: nil)
                }
            }
        }
    }
}</code></pre>
<p>위의 소스코드를 차례대로 살펴보도록 하자.</p>
<ol>
<li>우선 PHPickerViewDelegate를 채택하였다.</li>
<li>필수 메서드를 구현해주었다. 이 메서드는 사진을 선택하고 난 후에 실행되는 메서드이다.<pre><code class="language-swift">func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult])</code></pre>
</li>
<li>현재 PHPicker가 사라지도록 구현해주었다.</li>
<li>results의 첫번째 배열의 값을 불러왔다. 여기서 itemProvider는 선택한 아이템을 representation해주는 역할을 한다.</li>
<li>itemProvider의 값이 있고, UIImage로 값을 불러올 수 있을 경우에만 실행하도록 구현</li>
<li>itemProvider에서 UIImage Object를 불러오는 메서드를 구현했다. </li>
</ol>
<hr>
<p><a href="https://developer.apple.com/documentation/photokit/phpickerviewcontroller">https://developer.apple.com/documentation/photokit/phpickerviewcontroller</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UiKit] UIControl과 특정 action을 연결하는 addTarget을 알아보자 !]]></title>
            <link>https://velog.io/@wannabe_eung/UiKit-UIControl%EA%B3%BC-%ED%8A%B9%EC%A0%95-Action%EC%9D%84-%EC%97%B0%EA%B2%B0%ED%95%98%EB%8A%94-addTarget%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@wannabe_eung/UiKit-UIControl%EA%B3%BC-%ED%8A%B9%EC%A0%95-Action%EC%9D%84-%EC%97%B0%EA%B2%B0%ED%95%98%EB%8A%94-addTarget%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 15 Feb 2022 00:42:14 GMT</pubDate>
            <description><![CDATA[<h1 id="addtarget-이-뭐야">addTarget 이 뭐야?</h1>
<p>addTarget은 UIControl 클래스 안에 있는 인스턴스 메서드이다. 주로 UIButton, UITextField와 같이 사용자가 직접 Control을 할 수 있는 객체에 접근하여 특정 이벤트가 발생할 때 마다 내가 작성한 메서드를 동작하도록 만들 수 있는 아주 유용한 메서드이다. 그럼 본격적으로 알아보자.</p>
<hr>
<h1 id="선언">선언</h1>
<pre><code class="language-swift">func addTarget(_ target: Any?, 
        action: Selector, 
           for controlEvents: UIControl.Event)</code></pre>
<p>addTarget의 파라미터는 세 가지가 있다. target, action, controlEvents이다. 이름에서 어느정도 유추할 수 있을 것 같다. 그럼 하나씩 살펴보자. </p>
<h2 id="target">target</h2>
<p>내가 설정한 action 메서드가 호출되는 객체를 설정하는 파라미터다. 즉, action파라미터에 설정한 메서드가 호출되는 개체라고 생각하면 된다. addTarget을 정의하면 UIControl를 갖고 있는 상위 View를 특정하기 때문에, 보통의 경우는 self로 둔다. </p>
<h2 id="action">action</h2>
<p>#selector()를 이용하여 ControlEvents의 설정한 이벤트가 발생할 때마다 동작하는 메서드를 선택해주어야 하는 파라미터이다. </p>
<h2 id="controlevents">controlEvents</h2>
<p>어떤 이벤트가 발생할 때 마다 선택한 메서드를 실행시킬 것인지 지정해주는 파라미터다. 이 파라미터는 UIControl.Event라는 상수 목록들을 지정하고 있다. 다양한 이벤트들은 <a href="https://developer.apple.com/documentation/uikit/uicontrol/event">여기서</a> 확인할 수 있다.</p>
<hr>
<p>마지막으로 예시를 든 소스코드를 참고 해주셨으면 좋겠다.</p>
<pre><code class="language-swift">let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(fetchData), for: .valueChanged)

    @objc private func fetchData() {

        let stationName = station.stationName
        let urlString = &quot;http://swopenapi.seoul.go.kr/api/subway/sample/json/realtimeStationArrival/0/5/\(stationName.replacingOccurrences(of: &quot;역&quot;, with: &quot;&quot;))&quot;
        AF
            .request(urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? &quot;&quot;)
            .responseDecodable(of: StationArrivalDataResponseModel.self) { [weak self] response in
                guard let self = self else { return }
                self.refreshControl.endRefreshing()
                guard case .success(let data) = response.result else { return }

                self.realTimeArrivalList = data.realTimeArrivalList
                self.collectionView.reloadData()
                print(data.realTimeArrivalList)
            }
            .resume()
    }
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UiKit] UICollectionViewCompositionalLayout 을 알아보자!]]></title>
            <link>https://velog.io/@wannabe_eung/UiKit-UICollectionViewCompositionalLayout-%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@wannabe_eung/UiKit-UICollectionViewCompositionalLayout-%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Fri, 11 Feb 2022 04:38:39 GMT</pubDate>
            <description><![CDATA[<p>이번에는 UICollectionViewCompositionalLayout에 대해 알아보려고 한다. &#39;아니 UICollectionViewFlowLayout이 있는데 CompositionalLayout이 또 뭐지?&#39; 라고 생각할 수 있다. 대체 이 객체는 무엇이고 FlowLayout과 차이점은 무엇일까? 기존 FlowLayout의 단순하고 일반적인 CollectionView에서 오늘날 많은 앱들이 구현하고 있는 더 복잡하고 디자인이 겸비된 Layout으로 탈피할 수 있는 객체라고 말할 수 있다.</p>
<h1 id="uicollectionviewcompositionallayout은-왜-쓰는거야">UICollectionViewCompositionalLayout은 왜 쓰는거야?</h1>
<pre><code class="language-swift">@MainActor class UICollectionViewCompositionalLayout : UICollectionViewLayout</code></pre>
<p>우선 Compositonal Layout은 CollectionViewLayout을 상속받고 있다. 레이아웃의 한 유형이라는 뜻이다. Apple은 이 객체를 사용하면 Flexible(유연하고), Fast(빠르고), Composable(구성가능한) 장점을 지니고 있다고 설명하고 있다. 
<img src="https://images.velog.io/images/wannabe_eung/post/f092bbbf-6eda-443c-bc6d-c1d392919cc7/image.png" alt="">
그러니까 쉽게 말하자면 각각의 구성요소들을 따로 만드는 것이다. 여기서 Section, Group, Item이 보일 것이다. 구성된 레이아웃은 거시적으로 섹션을 기준으로 나뉘게 된다. 그리고 각 섹션은 또 Item의 모임인 Group으로 나뉘게 되고, 가장 작은 단위는 역시 Item이라고 말할 수 있다. 이 그룹들은 사용자 지정으로 여러 가지 방향으로 배치할 수 있다.</p>
<h1 id="item---group---section">Item -&gt; Group -&gt; Section</h1>
<p>그러면 가장 작은 구성요소로 부터 순서가 Item -&gt; Group -&gt; Section이라는 메커니즘을 이해할 수 있을 것이다. 이런 순서로 결합해주는 것이 바로 UICollectionViewCompositionalLayout이라고 말할 수 있다. </p>
<pre><code class="language-swift">func createBasicListLayout() -&gt; UICollectionViewLayout { 
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),                                  
                                         heightDimension: .fractionalHeight(1.0))    
    let item = NSCollectionLayoutItem(layoutSize: itemSize)  

    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),                                          
                                          heightDimension: .absolute(44))    
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,                                                   
                                                     subitems: [item])  

    let section = NSCollectionLayoutSection(group: group)    

    let layout = UICollectionViewCompositionalLayout(section: section)    
    return layout
}</code></pre>
<hr>
<p>더 자세한 내용을 원한다면 공식문서를 참조하길 바랍니다!
<a href="https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout/">https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UIKit] UICollectionViewFlowLayout 을 알아보자!]]></title>
            <link>https://velog.io/@wannabe_eung/UIKit-UICollectionViewFlowLayout-%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@wannabe_eung/UIKit-UICollectionViewFlowLayout-%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Fri, 11 Feb 2022 04:17:59 GMT</pubDate>
            <description><![CDATA[<p>CollectionView는 여러가지로 TableView와 비슷한 점이 많다. 하지만 CollectionView의 장점은 역시 Cell의 위치와 배열을 개발자 마음대로 할 수 있다는 장점이 아닐까 싶다. 그래서 이 역할의 중점적인 역할을 하는 UICollectionViewFlowLayout을 이번에 명쾌하게 알아보고자 한다.</p>
<h1 id="uicollectionviewflowlayout이-뭐야">UICollectionViewFlowLayout이 뭐야?</h1>
<pre><code class="language-swift">// Declaration
@MainActor class UICollectionViewFlowLayout : UICollectionViewLayout</code></pre>
<p>위의 코드에서 보면 UICollectionViewFlowLayout은 <a href="https://developer.apple.com/documentation/uikit/uicollectionviewlayout">UICollectionViewLayout</a>을 상속받고 있다. UICollectionViewLayout은 CollectionView안에 있는 구성요소들의 시각적 배열과 조직을 구성하는 역할을 하는데, 그 중에서 UICollectionViewFlowLayout은 각 섹션의 item, Header, Footer와 같은 것들의 크기를 결정한다. 예를 들면 한 개의 행이 몇개의 item이 들어갈지, 너비와 높이는 어떻게 될지 하는 것들이다. 아직까지 감이 잡히질 않는다. 좀 더 자세하게 들여다보자.</p>
<h1 id="uicollectionviewdelegateflowlayout-을-채택하자">UICollectionViewDelegateFlowLayout 을 채택하자.</h1>
<p>UICollectionViewFlowLayout을 결정하기 위해서는 delegate 디자인 패턴인, delegate 개체를 원하는 View에 할당해줘야 한다. 그리고 <a href="https://developer.apple.com/documentation/uikit/uicollectionviewdelegateflowlayout">UICollectionViewDelegateFlowLayout</a> Protocol을 채택해야한다. 이제 이 delegate을 이용하여 Cell을 내가 원하는데로 조정할 수 있게 된다. 만약 delegate를 사용하지 않으면 속성의 기본값으로 나타내어진다. </p>
<pre><code class="language-swift">// Getting the Size of Items
func collectionView(UICollectionView, layout: UICollectionViewLayout, sizeForItemAt: IndexPath) -&gt; CGSize

// Getting the Section Spacing
func collectionView(UICollectionView, layout: UICollectionViewLayout, insetForSectionAt: Int) -&gt; UIEdgeInsets

func collectionView(UICollectionView, layout: UICollectionViewLayout, minimumLineSpacingForSectionAt: Int) -&gt; CGFloat

func collectionView(UICollectionView, layout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt: Int) -&gt; CGFloat

// Getting the Header and Footer Sizes
func collectionView(UICollectionView, layout: UICollectionViewLayout, referenceSizeForHeaderInSection: Int) -&gt; CGSize

func collectionView(UICollectionView, layout: UICollectionViewLayout, referenceSizeForFooterInSection: Int) -&gt; CGSize
</code></pre>
<p>위 코드는 UICollectionViewDelegateFlowLayout의 메서드 들이다. 이것들을 이용하여 원하는 디자인을 구축하면 될 것이다. 여기서 특히 필수적으로 구현 해야할 것은 당연하게도 가장 첫 번째에 있는 Size of Items 메서드가 아닐까 싶다.</p>
<h1 id="스크롤-방향">스크롤 방향</h1>
<p>Flow layouts은 한 방향으로만 스크롤이 가능하고, 나머지 한 방향은 고정되어 있다. 이건 어떻게보면 많은 앱들을 사용하면서 자연스럽게 터득한 배경지식이라고 할 수 있다. 예를 들어 수직 스크롤 방향의 CollectionView라면 View의 너비는 고정되어 있고, 세로 방향의 View의 길이는 유동적으로 조절되는 것을 알 수 있다. 
 Default 값은 세로 스크롤이지만, 개발자가 스크롤의 방향도 바꿀 수 있다. UICollectionViewFlowLayout에 있는 <a href="https://developer.apple.com/documentation/uikit/uicollectionviewflowlayout/1617720-scrolldirection">scrollDirection</a> Property를 이용하면 된다.</p>
<pre><code class="language-swift">var scrollDirection : UICollectionView.ScrollDirection { get set } </code></pre>
<h1 id="header-and-footer-구현">Header and Footer 구현</h1>
<p>UICollectionViewFlowLayout은 Header와 Footer의 구현도 가능하다고 위에서 말했다. 이것은 delegate를 선언하여 UICollectionViewDelegateFlowLayout에 있는 Header와 Footer의 사이즈가 0이 아니게 설정하면 된다. 물론 그 안에 어떤 값이 들어갈지는 UICollectionViewDataSource에서 구현해주면 된다!</p>
<pre><code class="language-swift">// Getting the Header and Footer Sizes
func collectionView(UICollectionView, layout: UICollectionViewLayout, referenceSizeForHeaderInSection: Int) -&gt; CGSize

func collectionView(UICollectionView, layout: UICollectionViewLayout, referenceSizeForFooterInSection: Int) -&gt; CGSize</code></pre>
<hr>
<p><a href="https://developer.apple.com/documentation/uikit/uicollectionviewflowlayout/%08">https://developer.apple.com/documentation/uikit/uicollectionviewflowlayout/</a> 여기서 더 자세한 정보를 보실 수 있습니다 ! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] 푸쉬알림을 받을 수 있는 UserNotifications 알아보자]]></title>
            <link>https://velog.io/@wannabe_eung/UserNotifications-Framework%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@wannabe_eung/UserNotifications-Framework%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 10 Feb 2022 11:20:10 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/wannabe_eung/post/56f75813-e6d6-431c-a2dc-b8e025ffc43c/image.png" alt=""></p>
<h1 id="개요">개요</h1>
<p>앱을 사용하다보면 푸쉬알림을 받을 것을 동의하는 것과 동시에 이렇게 푸쉬알림을 받아본 적이 있을 것이다. 오늘 소개할 것은 애플에서 제공하고 있는 프레임워크 중에 하나인 <strong>User Notifications</strong>를 소개하려고 한다.</p>
<p>푸쉬알림에는 거시적으로 두 가지 종류가 있다. 인터넷 필요 없이 앱 내에서 작동하는 <strong>Local Notifications</strong>와 누군가가 불특정한 시간, 장소에서 알림이 필요할 때 사용하는 <strong>Remote Notifications</strong>가 있다. 후자의 경우는 서버와 관련되어 있기 때문에 Back-End나 구글에서 제공하고 있는 Firebase를 이용해야 하며 Apple Push Notification service(APNs)의 이해가 필요하다.</p>
<p>이번 포스트에서는 Local-Notifications를 알아보고자 한다. UserNotifications Framework를 사용해야 할 때, 다음과 같은 프로세스가 있다.</p>
<ol>
<li>푸쉬알림의 유형을 정의한다.</li>
<li>푸쉬알림과 관련된 원하는 작업을 정의한다.</li>
<li>푸쉬알림을 예약한다.</li>
<li>전달된 알림을 처리한다.</li>
<li>사용자 지정 작업에 응답한다.</li>
</ol>
<p>사실여기까지만 들어도 잘 모르겠다. 왜냐하면 내가 그랬다...ㅎㅎ 이제 본격적으로 어떻게 구현하는지 알아보자.</p>
<h2 id="사용자에게-알림-동의-구하기">사용자에게 알림 동의 구하기</h2>
<p><img src="https://images.velog.io/images/wannabe_eung/post/66d9a732-c375-4ed8-a99b-1e9742c8fbd0/image.png" alt="">
새로 설치한 앱을 열 때 이런 문구를 접해본 적이 있을 것이다. UserNotifications를 사용하기 위해서는 위와 같은 작업을 <strong>필수적으로!</strong> 해줘야 한다. requestAuthorization은 UNUserNotificationCenter의 인스턴스를 생성하면 가져올 수 있다. 그리고 알림과 함께 사용할 옵션들의 사용 동의를 사용자에게 요청한다.</p>
<pre><code class="language-swift">let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in

    if let error = error {
        // Handle the error here.
    }

    // Enable or disable features based on the authorization.
}</code></pre>
<h2 id="notifications-content-만들기">Notification&#39;s Content 만들기</h2>
<p>사용자에게 동의를 구했다면, 본격적으로 구체적인 알림을 만들 시간이다. UNMutableNotificationContent라는 객체를 생성하여 푸쉬알림에서 보여줄 Property에 접근할 수 있다. 이외에도 Sound설정과 같은 다양한 Property가 있으니 사용하면 된다.</p>
<pre><code class="language-swift">let content = UNMutableNotificationContent()
content.title = &quot;Weekly Staff Meeting&quot;
content.body = &quot;Every Tuesday at 2pm&quot;</code></pre>
<h2 id="notification-trigger-만들기">Notification Trigger 만들기</h2>
<p>이제 푸쉬알림의 실체를 구현했다면, 알림이 사용자에게 보여질 조건을 설정해줘야 한다. 이 조건에는 3가지 객체가 있다. 구현하려는 목적에 따라서 이 3가지 중 하나를 사용하면 되겠다.</p>
<blockquote>
<p><strong>UNCalendarNotficationTrigger</strong> : 정해진 날짜나 시간에 알림 배송
<strong>UNTimeIntervalNotificationTrigger</strong> : 지정한 시간이 지난 후에 알림 배송
<strong>UNLocationNotificationTrigger</strong> : 지정한 위치에 있을 때 알림 배송</p>
</blockquote>
<pre><code class="language-swift">// Configure the recurring date.
var dateComponents = DateComponents()
dateComponents.calendar = Calendar.current

dateComponents.weekday = 3  // Tuesday
dateComponents.hour = 14    // 14:00 hours

// Create the trigger as a repeating event.    
let trigger = UNCalendarNotificationTrigger(
         dateMatching: dateComponents, repeats: true)</code></pre>
<p>위의 소스코드는 UNCalendarNotificationTrigger를 사용한 예시이다. repeats는 말그대로 이 조건을 반복할 것인가에 대한 물음이다.</p>
<h2 id="notification-request-생성하고-등록하기">Notification Request 생성하고 등록하기</h2>
<p>이제 대망의 알림을 요청할 시간입니다! </p>
<pre><code class="language-swift">// Create the request
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, 
            content: content, trigger: trigger)

// Schedule the request with the system.
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request) { (error) in
   if error != nil {
      // Handle any errors.
   }
}</code></pre>
<p> 만들었던 콘텐츠(Notification Content)와 조건(Notification Trigger)을 대상으로 <strong>UNNotificationRequest</strong> 객체를 생성해주어야 합니다. 그리고 add(_:withCompletionHandler:)를 호출하여 구현하고 싶은 메서드에 집어 넣어주기만 하면 됩니다. 또 한가지, 여기서 indentifier는 여러 가지 알림이 있을테니, 각 알림을 구별해줄 인자입니다.</p>
<hr>
<p>조금 이해가 가셨는지 모르겠네요 ㅎㅎ.. 저도 이번에 처음 배워보는 Framework라서 많이 헷갈렸습니다. 도움이 되셨으면 좋겠네요 ! 
<a href="https://developer.apple.com/documentation/usernotifications/">https://developer.apple.com/documentation/usernotifications/</a></p>
]]></description>
        </item>
    </channel>
</rss>