<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Hi Lily</title>
    <link>https://hilily.tistory.com/</link>
    <description>굉장할 나</description>
    <language>ko</language>
    <pubDate>Thu, 16 Jul 2026 02:43:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hilily</managingEditor>
    <image>
      <title>Hi Lily</title>
      <url>https://tistory1.daumcdn.net/tistory/5177550/attach/eb886c3fdbbb4947a7025bd587787e4c</url>
      <link>https://hilily.tistory.com</link>
    </image>
    <item>
      <title>[iOS/Swift] Hashable, HashValue, Equatable 이란</title>
      <link>https://hilily.tistory.com/101</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Hash란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 관리, 유지하는 자료구조로 데이터들을 해시 함수를 통해 key로 분류하고, 그 key에 따라 value를 저장하는 형태를 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HashTable이란 Key를 이용해 배열의 Index를 찾고, 그 인덱스를 통해 값을 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1670&quot; data-origin-height=&quot;812&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k2vKu/btsF79JI94O/EYZRYEJqUEcubaQ89KhRG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k2vKu/btsF79JI94O/EYZRYEJqUEcubaQ89KhRG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k2vKu/btsF79JI94O/EYZRYEJqUEcubaQ89KhRG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk2vKu%2FbtsF79JI94O%2FEYZRYEJqUEcubaQ89KhRG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;306&quot; data-origin-width=&quot;1670&quot; data-origin-height=&quot;812&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;제리&quot;의 영어 이름을 찾기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &quot;제리&quot;라는 key를 hash 함수에 전달한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. hash 함수 내에 hash함수를 통해 찾을 value의 인덱스를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 찾은 인덱스를 통해 value값 &quot;jarry&quot;를 얻는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Hashable이란?&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/hashable&quot;&gt;https://developer.apple.com/documentation/swift/hashable&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711887302323&quot; style=&quot;color: #333333; text-align: start;&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eEmT0/hyVGMLXkQm/XBKPhdnIE64pvUqfo0kvwk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ce7YcI/hyVGIis56k/PTngJlLSC7yhukjMf6mkr1/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/swift/hashable&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/swift/hashable&quot; data-og-host=&quot;developer.apple.com&quot; data-og-description=&quot;A type that can be hashed into a to produce an integer hash value.&quot; data-og-title=&quot;Hashable | Apple Developer Documentation&quot; data-og-type=&quot;website&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://developer.apple.com/documentation/swift/hashable&quot; data-source-url=&quot;https://developer.apple.com/documentation/swift/hashable&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eEmT0/hyVGMLXkQm/XBKPhdnIE64pvUqfo0kvwk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ce7YcI/hyVGIis56k/PTngJlLSC7yhukjMf6mkr1/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Hashable | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;A type that can be hashed into a to produce an integer hash value.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Hashable이란 해시값을 생성하게 하는 Hash가 가능한 타입이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로 swift의 기본 타입들은 모두 Hashable하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나 개발자가 직접 만든 타입은 Hashable 하지 않기 때문에 Hash를 가능하게 하기 위해서는 Hashable 프로토콜을 채택해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 예시를 통해 더 자세히 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711461400725&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Person {
    let name: String
    let gender: Gender
    let identityNumber: Int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Person 타입의 객체 2개가 있다고 해보쟈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711461587716&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let jarry = Person(name: &quot;제리&quot;, gender: .men, identityNumber: 1)
let lily = Person(name: &quot;릴리&quot;, gender: .women, identityNumber: 2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jarry 와 lily를 비교할 때 단순히 생각하면 내부 요소(name, gender, identityNumber)를 비교한다고 생각할 수 있지만&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;80&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nzJar/btsF70MOPn3/n9xq2c82XuqebqgnsO8KB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nzJar/btsF70MOPn3/n9xq2c82XuqebqgnsO8KB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nzJar/btsF70MOPn3/n9xq2c82XuqebqgnsO8KB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnzJar%2FbtsF70MOPn3%2Fn9xq2c82XuqebqgnsO8KB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;29&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;80&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 해보면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명했듯이 Person은 개발자가 직접 만든 타입이기 때문에 Person이 Hashable하지 않기 때문에 비교할 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하기 위해 Hashable 프로토콜을 채택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711462225409&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Person: Hashable {
    let name: String
    let gender: Gender
    let identityNumber: Int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Hashable을 채택하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 jarry와 lily를 비교하면 오류메시지가 나타나지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 새로운 사용자 정의 타입인 House를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711887928113&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct House {
    let type: HouseType
    let name: String
    let address: String
    let cost: Int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711887943644&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let jarryHouse = House(type: .apartment, name: &quot;아파트&quot;, address: &quot;서울&quot;, cost: 1000)
let lilyHouse = House(type: .building, name: &quot;빌딩&quot;, address: &quot;경기도&quot;, cost: 500)

print(jarry == lily)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 새로운 오류메시지가 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg4sa6/btsGfwp9mXQ/Va6AuC3VliPf0kVSRHYO5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg4sa6/btsGfwp9mXQ/Va6AuC3VliPf0kVSRHYO5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg4sa6/btsGfwp9mXQ/Va6AuC3VliPf0kVSRHYO5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg4sa6%2FbtsGfwp9mXQ%2FVa6AuC3VliPf0kVSRHYO5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;122&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Person이 다시 Hashable하지 않게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Fix를 눌러 == 함수를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1711888107348&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static func == (lhs: Person, rhs: Person) -&amp;gt; Bool {
    return lhs.name == rhs.name
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1650&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sqnyy/btsGd7d0kfi/UuhdXMxdcSNTUSdQ4Ogin1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sqnyy/btsGd7d0kfi/UuhdXMxdcSNTUSdQ4Ogin1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sqnyy/btsGd7d0kfi/UuhdXMxdcSNTUSdQ4Ogin1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSqnyy%2FbtsGd7d0kfi%2FUuhdXMxdcSNTUSdQ4Ogin1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;107&quot; data-origin-width=&quot;1650&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그럼에도 오류메시지는 사라지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711888307049&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct House: Hashable {
    let type: HouseType
    let name: String
    let address: String
    let cost: Int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 House가 Hashable하게 되어서야 오류 메시지는 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fix를 통해 == 함수를 추가하는것과 관계 없이 House를 Hashable로 해주어야 오류메시지가 사라졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 == 함수와 hash(into: )를 사용하지 않았는데 두 값을 비교 가능했다면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;== 함수와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;hash(into: )는 왜 필요한지 궁금해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Hashable의 정의부를 가면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1711545378913&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public protocol Hashable : Equatable {

    /// The hash value.
    ///
    /// Hash values are not guaranteed to be equal across different executions of
    /// your program. Do not save hash values to use during a future execution.
    ///
    /// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
    ///   conform to `Hashable`, implement the `hash(into:)` requirement instead.
    ///   The compiler provides an implementation for `hashValue` for you.
    var hashValue: Int { get }

    /// Hashes the essential components of this value by feeding them into the
    /// given hasher.
    ///
    /// Implement this method to conform to the `Hashable` protocol. The
    /// components used for hashing must be the same as the components compared
    /// in your type's `==` operator implementation. Call `hasher.combine(_:)`
    /// with each of these components.
    ///
    /// - Important: In your implementation of `hash(into:)`,
    ///   don't call `finalize()` on the `hasher` instance provided,
    ///   or replace it with a different instance.
    ///   Doing so may become a compile-time error in the future.
    ///
    /// - Parameter hasher: The hasher to use when combining the components
    ///   of this instance.
    func hash(into hasher: inout Hasher)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 살펴보겠당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;hashValue&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hashable 프로토콜을 채택하면 hashValue를 구할 수 있게 되고, 이 hashValue는 요소들의 위치를 찾는 수단이 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;프로그램의 여러 실행에서 해시 값이 동일하다고 보장되지는 않습니다. &lt;br /&gt;향후 실행 중에 사용할 해시 값을 저장하지 마십시오.&lt;br /&gt;&lt;br /&gt;- 중요: hashValue는 Hashable의 필수 요소로 부터 deprecated 되었습니다.&lt;br /&gt;대신 Hashable 요건을 구현하십시오. &lt;br /&gt;컴파일러는 HashValue에 대한 구현을 제공합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 주석 내용처럼 hashValue는 프로그램이 실행될 때 마다 값이 계속 변한다. 따라서 따로 이 값을 저장해서 식별하는 등의 작업은 옳지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Swift5 기준으로 hashValue를 직접 설정해주는 것은 deprecated되었기 때문에 hash(into:)를 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 hashValue는 get만 가능하기 때문에 따로 값을 지정할 수는 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ADixK/btsGe8wjOvm/4WcW8hd12qDsAHdrn52kvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ADixK/btsGe8wjOvm/4WcW8hd12qDsAHdrn52kvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ADixK/btsGe8wjOvm/4WcW8hd12qDsAHdrn52kvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FADixK%2FbtsGe8wjOvm%2F4WcW8hd12qDsAHdrn52kvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;696&quot; height=&quot;161&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hashValue는 원본 데이터를 특정 규칙에 따라 처리하여 간단한 숫자로 만든 것으로 실제로 값을 찍어보면 Int형의 값이 print되고, 이 값은 실행할 때 마다 변한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 hashValue가 Int형으로 유한한 범위(2^32)이며, 데이터가 무수히 많아질 경우 서로 다른 데이터가 동일한 해쉬값을 가질 수 있다. 따라서 해쉬 충돌이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;hash(into:)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/hashable/hash(into:)-v52&quot;&gt;https://developer.apple.com/documentation/swift/hashable/hash(into:)-v52&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711893615770&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;hash(into:) | Apple Developer Documentation&quot; data-og-description=&quot;Hashes the essential components of this value by feeding them into the given hasher.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/swift/hashable/hash(into:)-v52&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/swift/hashable/hash(into:)-v52&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gvaah/hyVGN5b6Kb/JWMeUbrPGTZrKabdr3xSBk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/i8q8K/hyVJV8mNm2/ZoD3zDOUtNoHfaJv6BujZk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/hashable/hash(into:)-v52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/swift/hashable/hash(into:)-v52&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gvaah/hyVGN5b6Kb/JWMeUbrPGTZrKabdr3xSBk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/i8q8K/hyVJV8mNm2/ZoD3zDOUtNoHfaJv6BujZk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;hash(into:) | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Hashes the essential components of this value by feeding them into the given hasher.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 값의 필수 구성 요소를 주어진 hasher에 공급하여 해시합니다.&lt;br /&gt;&lt;br /&gt;이 방법은 'Hashable' 프로토콜을 따르도록 구현합니다.&lt;br /&gt;해시에 사용되는 구성 요소는 유형의 '==' 연산자 구현에서 비교되는 구성 요소와 동일해야 합니다.&lt;br /&gt;이러한 각 구성 요소와 'hasher.combine(_:)'을 호출합니다.&lt;br /&gt;&lt;br /&gt;- 중요: hash(into:)'를 구현할 때 제공된 hasher의 인스턴스에서 finalize()를 호출하거나 다른 인스턴스로 대체하지 마십시오.&lt;br /&gt;&lt;br /&gt;그렇게 하면 나중에 컴파일 시간 오류가 발생할 수 있습니다.&lt;br /&gt;&lt;br /&gt;- Parameter hasher: 이 인스턴스의 구성 요소를 결합할 때 사용할 hasher입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드를 통해 식별 할 수 있는 identifier를 hasher에게 넘겨줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 정의 타입에서 Hashable을 채택하면 모든 프로퍼티가 Hashable을 채택했다면 hash(:into)메서드 자동 구현된다&lt;br /&gt;클래스는 원칙적으로 Hashable 지원 불가하지만 인스턴스의 유일성을 갖게 하기 위해서는 hash(into:) 메서드 직접 구현해야한다.&lt;br /&gt;열거형의 경우 연관값이 없다면 기본적으로 Equatable/Hashable하기 때문에 Hashable 프로토콜을 채택하지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711892511205&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct Person: Hashable {
    let name: String
    let gender: Gender
    let identityNumber: Int
    let house: House
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
    }
}

struct House: Hashable {
    let type: HouseType
    let name: String
    let address: String
    let cost: Int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드에서 각 프로퍼티가 모두 Hashable을 채택했기 때문에 hash(into:)를 구현하지 않아도 컴파일 에러가 나타나지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 hash(into:)를 구현 하지 않으면 컴파일러가 자동으로 어떤 규칙에 따라 해시값을 생성하기 때문에 원하는 동작을 보장하지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 나는 name을 기준으로 값을 구분하고 싶었는데 그런 디테일한 요소(?)를 직접 설정하기 어렵다.. 머 그런 의미 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 예상할 수 있는 결과를 위해서 hash(into:)를 직접 구현하여 고유한 해시값을 생성함으로써 원하는 동작을 보장하게 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Equatable protocol&lt;/h3&gt;
&lt;pre id=&quot;code_1711891713359&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public protocol Equatable {

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    static func == (lhs: Self, rhs: Self) -&amp;gt; Bool
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hashable은 Equtable 프로토콜을 준수하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 hashValue는 항상 unique하지 않기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 말했듯 hashValue가 같으면 해시 충돌이 일어날 가능성이 있기 때문에 hashValue만으로는 정확도가 떨어져서 추가로 동일한지 확인하는 Equtable이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Hashable하게 만들어주려면 두가지가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. == 함수&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Equtable 프로토콜&lt;/li&gt;
&lt;li&gt;hashValue가 같을 수 있기 때문에 이를 방지하기 위해 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. hash(into: )&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hashable 프로토콜&lt;/li&gt;
&lt;li&gt;hashValue를 만들기 위해 사용&lt;/li&gt;
&lt;li&gt;combine(value)에 들어가는 value는 == 함수의&amp;nbsp; 구현에서 비교되는 요소와 동일해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 ==함수와 hash(into:)는 컴파일 에러가 발생하지 않는 한에서 구현하지 않아도 되지만 더 정확하고 예상 가능한 결과를 위해 구현해준다 라고 이해했다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 까지 하면 예상 가능한 결과를 얻을 수 있는 안전한..!? hashable를 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://woongsios.tistory.com/145&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://woongsios.tistory.com/145&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://applecider2020.tistory.com/14&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://applecider2020.tistory.com/14&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://clamp-coding.tistory.com/357&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://clamp-coding.tistory.com/357&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://babbab2.tistory.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://babbab2.tistory.com/89&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS/Swift</category>
      <author>hilily</author>
      <guid isPermaLink="true">https://hilily.tistory.com/101</guid>
      <comments>https://hilily.tistory.com/101#entry101comment</comments>
      <pubDate>Sun, 31 Mar 2024 23:01:37 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] UIAlertViewController를 사용할 때 UIAlertAction의 handler가 초기화되는 현상</title>
      <link>https://hilily.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;UIAlertAction을 사용하면서 어떤 버튼을 눌렀을 때 UIAlertAction을 그 함수 내에서 설정하여 addAction()하는 경우로만 사용했었는데 필요한 Actions를 미리 프로퍼티로 저장해둔 후 추후 미리 만들어둔 UIAlertAction을 전달하여 UIAlertController로 띄우는 방법을 사용해보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711268201923&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var myActions: [UIAlertAction] = []&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 내가 저장할 액션 배열을 클래스 내 프로퍼티로 미리 선언해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711268201923&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;private func setupAlertActions() {
    let action1 = UIAlertAction(title: &quot;action1&quot;, style: .default, handler: { _ in
        print(&quot;select action1&quot;)
    })
    let action2 = UIAlertAction(title: &quot;action2&quot;, style: .default, handler: { _ in
        print(&quot;select action2&quot;)
    })
    let cancel = UIAlertAction(title: &quot;Cancel&quot;, style: .cancel, handler: nil)

    myActions.append(action1)
    myActions.append(action2)
    myActions.append(cancel)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 액션 배열 프로퍼티에 UIAlertAction을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 함수를 viewDidLoad() 부분에서 실행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711268201925&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;private func showAlertAction() {
    let alert = UIAlertController(title: &quot;title&quot;, message: &quot;message&quot;, preferredStyle: .actionSheet)

    myActions.forEach { action in
        alert.addAction(action)
    }

    present(alert, animated: true)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 추후 미리 만들어둔 myActions를 alert에 추가하여 present한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 함수를 어떤 버튼을 눌렀을 때 실행하도록 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 예상과 다른 부분이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 처음 showAlertAction()을 실행했을 때&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;950&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkt9nk/btsF1j61QP6/c6TwdsWOlIZjsIFK4Rsdy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkt9nk/btsF1j61QP6/c6TwdsWOlIZjsIFK4Rsdy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkt9nk/btsF1j61QP6/c6TwdsWOlIZjsIFK4Rsdy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkt9nk%2FbtsF1j61QP6%2Fc6TwdsWOlIZjsIFK4Rsdy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;438&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;950&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 showAlertAction()을 실행했을 때 handler에 주소값이 할당되어 있어 정상적으로 hander가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 select action1 이 콘솔에 찍히는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 두번 이상 showAlertAction()을 실행했을 때&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4FUDg/btsF1IrKUP9/d02zEC36RygVbvMWdR6AfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4FUDg/btsF1IrKUP9/d02zEC36RygVbvMWdR6AfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4FUDg/btsF1IrKUP9/d02zEC36RygVbvMWdR6AfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4FUDg%2FbtsF1IrKUP9%2Fd02zEC36RygVbvMWdR6AfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;429&quot; height=&quot;444&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 두번 이상 showAlertAction()을 실행했을 때 handler의 주소값이 초기화되어 hander가 더이상 작동하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사진에서는 0번째 데이터만 handler가 초기화 되었지만 다른 action데이터의 handler가 모두 초기화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;title같은 이외의 데이터는 그대로 값이 저장되어 있는 것을 보아 아마 handler는 참조형식이고, UIActionViewController 내에서 전달받은 UIAlertAction의 handler의 주소값을 초기화 해주는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플의 의도는 정확하게 알지 못했지만 아무튼 배열로 액션을 따로 저장해둔 후 필요할 때 마다 present하는 방식은 내 의도처럼 작동하기는 힘들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하기 직전에 action을 alert에 add 해주는게 애플에서 의도한 사용 방법인 것 같아 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다음부터 사용할 때는 직전에 값을 add 해 주어야 겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그러나 어거지로 해서 handler의 주소값이 초기화 되지 않게 할 수 있을 것 같아 이 부분도 한번 시도해 보았다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711268961461&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func showAlertAction() {
    let alert = UIAlertController(title: &quot;title&quot;, message: &quot;message&quot;, preferredStyle: .actionSheet)

    myActions.forEach { action in
    	// copy() 한 값을 add
        alert.addAction(action.copy() as! UIAlertAction)
    }

    present(alert, animated: true)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 addAction()하는 데이터를 copy()를 통해 복사한 값을 add한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 copy()를 통해 얕은 복사한 값을 add하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L4xaj/btsF2FuqXGp/EFTpN9pmyhx6piNSp9mfk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L4xaj/btsF2FuqXGp/EFTpN9pmyhx6piNSp9mfk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L4xaj/btsF2FuqXGp/EFTpN9pmyhx6piNSp9mfk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL4xaj%2FbtsF2FuqXGp%2FEFTpN9pmyhx6piNSp9mfk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;408&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 이미지가 같긴 한데 첫 번째 사진은 최초 실행, 두 번째 사진은 그 이후 실행 했을 때 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;copy를 한 함수와 안 한 함수를 번갈아서 테스트 했을 때 copy를 하지 않은 함수를 실행했을 때 최초 실행때는 handler의 주소값이 할당되어 있지만 그 이후에는 copy를 한 함수든 아니든 handler의 주소값이 초기화 된 것으로 보아 copy를 하지 않으면 최초 실행때만 handler가 있고, 그 때 handler의 주소값이 초기화 되는 것같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 의문인건 handler를 깊은 복사를 해야 주소값이 초기화가 되지 않을 것 같았는데 얕은 복사 만으로도 초기화가 안되는 것으로 봐서 어떤식으로 handler가 초기화 되는 건지 잘 모르겠다ㅠ&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 지식이 더 쌓이면 알게되기룰.. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/35291504/uialertcontroller-action-not-executed-if-alert-dismissed-the-first-time&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/35291504/uialertcontroller-action-not-executed-if-alert-dismissed-the-first-time&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/66613771/uialertaction-not-reusable&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/66613771/uialertaction-not-reusable&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>hilily</author>
      <guid isPermaLink="true">https://hilily.tistory.com/100</guid>
      <comments>https://hilily.tistory.com/100#entry100comment</comments>
      <pubDate>Sun, 24 Mar 2024 17:42:12 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Coordinator Pattern</title>
      <link>https://hilily.tistory.com/98</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;나는 새로운 개념을 접할 때 뭔가 이걸 사용해서 어떤 문제를 해결하는데 도움이 되고, 왜 사용하는지를 체감 했을 때 그 개념을 완전히 받아들일 수 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 내가 이 개념의 필요성을 온전히 느끼지 못하면 사용을 하면서도 마음 한켠에 의문이 작게 자리잡는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 협업을 하고 일을 하기 위해서는 내가 마음으로 완전히 받아들이지 않았음에도 그게 무엇인지 알고 빠르게 받아들일 수 있는 준비는 되어있어야 하는게 맞다고는 생각한다.(근데 그렇게 안했음ㅋㅜ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 Coordinator Pattern을 처음 공부하게 되었다. 예전에 다른 프로젝트를 통해 개념을 접한 적은 있었지만 그때는 실력이 부족해서 인가 필요성을 온전히 느끼지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 그랬는지 생각해보면 너무 작은 프로젝트에 이 개념을 도입하다는데 투머치하다는 느낌에 그랬던것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 프로젝트에서 코드를 개선하려고 내 나름 노력하긴 했는데 어떤 개념을 배우고 도입한다기보다 코드를 작성하다가 뭔가 이 방법이 좋지 않은것 같다는 생각이 들면 그때그때 해결방법 찾아가면서 조금씩 기술이나 개념을 도입했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 트랜드라는게 있는 것이고, 필요성을 느끼지 못해도 사람들이 많이 사용한다면 나중에 내가 사용하게 될 순간이 왔을 때 바로 사용할 수 있어야 한다고 생각한다. 아직 많이 부족하니까 우선 배우고 쓸지말지 생각하자!! ^...^&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 사용할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coordinator Pattern을 공부하기 전 왜 사용하는지가 가장 궁금했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면 화면관리에 대한 추적을 용이하게 하기 위해서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VC와 VC를 연결하여 뷰를 차곡차곡 쌓는 과정에서, 작은 프로젝트라면 VC2를 push 하는 방법은 VC1에서 VC2로 가는 경우밖에 없을 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 복잡하고 규모가 큰 앱에서는 VC2로 갈 수 있는 방법이 다양할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 되면 VC2를 호출하는 모든 뷰에서 VC2를 push하는 로직에 접근하게 되고, 작다면 괜찮겠지만 앱이 커질수록 이런 로직을 관리하기 어려울 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Coordinator를 추상화하기 위한 프로토콜을 보는것으로 어떤 동작이 일어나는지, 어떤 뷰가 나타날지 예측할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 프로그래밍에서 단일 책임 원칙을 생각했을 때 모든 클래스는 하나의 책임을 가져야하지만, 다른 VC를 push 로직이 있다면 이는 다른 뷰에 접근한다는 의미에서 단일 책임 원칙을 위반했다고 볼 수 있을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coordinator pattern을 처음 접했을 때 뭔지는 알겠는데 이걸 사용해야하는 필요성을 느끼지 못했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 큰 프로젝트에서 Coordinator pattern을 적용한 코드를 보았을 때 화면 전환에 대한 부분을 찾기 위해서 해당 VC의 Coordinator 파일로 가면 된다는 규칙이 생겨 쉽게 코드를 예측할 수 있었다. 굳...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Coordinator Pattern이란?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9mIe5/btsFm4WG2Vd/w6OEnsrN5WZrBxJLgIinR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9mIe5/btsFm4WG2Vd/w6OEnsrN5WZrBxJLgIinR1/img.png&quot; data-alt=&quot;네이버 영어사전 검색&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9mIe5/btsFm4WG2Vd/w6OEnsrN5WZrBxJLgIinR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9mIe5%2FbtsFm4WG2Vd%2Fw6OEnsrN5WZrBxJLgIinR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;137&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;네이버 영어사전 검색&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어 단어 자체의 뜻으로 보면 어떤 움직임들 관리하는 뉘앙스로 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뜻과 동일하게 Coordinator는 화면전환을 관리하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Coordinator Pattern은 각각의 화면 전환을 담당하는 별도의 Coordinator 객체를 사용하여 앱의 네비게이션 로직을 분리하고 모듈화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs7RE9/btsFlSP4OJp/5LOLWtbuMh9FAFUSUH4oE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs7RE9/btsFlSP4OJp/5LOLWtbuMh9FAFUSUH4oE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs7RE9/btsFlSP4OJp/5LOLWtbuMh9FAFUSUH4oE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs7RE9%2FbtsFlSP4OJp%2F5LOLWtbuMh9FAFUSUH4oE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;426&quot; height=&quot;130&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 프로젝트에서는 MainViewController -&amp;gt; WriteViewController로 이동할 때 MainViewController에서 WriteViewController을 알고 있어야 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1709368218429&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func pushSearchVC() {
    let vc = SearchVC(viewModel: self.viewModel)
    navigationController?.pushViewController(vc, animated: true)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드처럼 보통 뷰를 이동하게 작성했을텐데 그렇게 되면 이 전 VC에서 다음 뷰를 생성하여 pushViewController에 담아주기 때문에 문제가 생겼다고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 기존 프로젝트에서 뷰를 이동할 때 뷰 이동하는 부분이 거슬리는(?) 느낌이 있어서 VC파일 제일 하단에 extension으로 빼서 눈에 안들어오게 적었었는데 이 부분을 작성하지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;1224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hx0DQ/btsFlPy52cM/KhUp7m9OoivRfnWnTQIek0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hx0DQ/btsFlPy52cM/KhUp7m9OoivRfnWnTQIek0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hx0DQ/btsFlPy52cM/KhUp7m9OoivRfnWnTQIek0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHx0DQ%2FbtsFlPy52cM%2FKhUp7m9OoivRfnWnTQIek0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;406&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;1224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coordinator Pattern을 사용하면 가장 중심이 되는 AppCoordinator를 시작으로 위 그림처럼 트리 형식으로 모든 컨트롤러를 구성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 트리형식으로 뷰를 관리하게 된다면 뷰를 제거할 때 상위 Coordinator의 childCoordinator 배열에서 해당 뷰컨트롤러의 Coordinator을 삭제할 수 있어 쌓여있는 뷰에서 중간 뷰에 접근해 뷰를 삭제할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 배열에서 뷰를 제거함으로써 그 뷰에 하위 뷰를 함께 메모리 해제할 수 있어 메모리 관리도 더욱 효율적일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 공부할 경우 아래 링크의 예제를 직접 따라해보면 빠른 이해 가능!bb&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zeddios.medium.com/coordinator-pattern-bf4a1bc46930&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zeddios.medium.com/coordinator-pattern-bf4a1bc46930&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>hilily</author>
      <guid isPermaLink="true">https://hilily.tistory.com/98</guid>
      <comments>https://hilily.tistory.com/98#entry98comment</comments>
      <pubDate>Wed, 14 Feb 2024 22:37:35 +0900</pubDate>
    </item>
    <item>
      <title>[iOS/RxSwift] subscribe, bind, drive 비교하기</title>
      <link>https://hilily.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;RxSwift를 사용하게 되면 자주 접하게 되는 subscribe, bind, drive를 비교하여 정리해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. subscribe&lt;/h2&gt;
&lt;pre id=&quot;code_1705840356604&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public func subscribe(
    onNext: ((Element) -&amp;gt; Void)? = nil,
    onError: ((Swift.Error) -&amp;gt; Void)? = nil,
    onCompleted: (() -&amp;gt; Void)? = nil,
    onDisposed: (() -&amp;gt; Void)? = nil
) -&amp;gt; Disposable {
        let disposable: Disposable

        if let disposed = onDisposed {
            disposable = Disposables.create(with: disposed)
        }
        else {
            disposable = Disposables.create()
        }

        #if DEBUG
            let synchronizationTracker = SynchronizationTracker()
        #endif

        let callStack = Hooks.recordCallStackOnError ? Hooks.customCaptureSubscriptionCallstack() : []

        let observer = AnonymousObserver&amp;lt;Element&amp;gt; { event in

            #if DEBUG
                synchronizationTracker.register(synchronizationErrorMessage: .default)
                defer { synchronizationTracker.unregister() }
            #endif

            switch event {
            case .next(let value):
                onNext?(value)
            case .error(let error):
                if let onError = onError {
                    onError(error)
                }
                else {
                    Hooks.defaultErrorHandler(callStack, error)
                }
                disposable.dispose()
            case .completed:
                onCompleted?()
                disposable.dispose()
            }
        }
        return Disposables.create(
            self.asObservable().subscribe(observer),
            disposable
        )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흔히 접할 수 있고, 기본이라고 생각되는 subscribe!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내부 구현을 보면 알 수 있듯이 onNext, onComplete, onError를 지원하여 다양한 처리가 가능하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. bind&lt;/h2&gt;
&lt;pre id=&quot;code_1705840335320&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public func bind&amp;lt;Object: AnyObject&amp;gt;(
    with object: Object,
    onNext: @escaping (Object, Element) -&amp;gt; Void
) -&amp;gt; Disposable {
    self.subscribe(onNext: { [weak object] in
        guard let object = object else { return }
        onNext(object, $0)
    },
    onError: { error in
        rxFatalErrorInDebug(&quot;Binding error: \(error)&quot;)
    })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현부를 살펴보면 subscribe를 사용하여 구현되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 onNext만을 받아 처리하는것으로 보아 error나 complete와 같은 디테일한 처리까지는 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Drive&lt;/h2&gt;
&lt;pre id=&quot;code_1705840467069&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public func drive(
    onNext: ((Element) -&amp;gt; Void)? = nil,
    onCompleted: (() -&amp;gt; Void)? = nil,
    onDisposed: (() -&amp;gt; Void)? = nil
) -&amp;gt; Disposable {
    MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage)
    return self.asObservable().subscribe(onNext: onNext, onCompleted: onCompleted, onDisposed: onDisposed)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 onNext와 onComplete만을 받아 처리한다. error처리까지는 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;drive는 보통 UI를 그릴때 사용되는데 때문에 Main스레드를 통해 동작을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 share를 내부적으로 사용하고 있기 때문에 동일한 시퀀스를 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;share&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subscribe를 할 때 마다 새로운 Observable 시퀀스가 생성되지 않고, 하나의 시퀀스에서 방출되는 아이템을 공유해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>hilily</author>
      <guid isPermaLink="true">https://hilily.tistory.com/97</guid>
      <comments>https://hilily.tistory.com/97#entry97comment</comments>
      <pubDate>Sun, 21 Jan 2024 19:33:11 +0900</pubDate>
    </item>
    <item>
      <title>[iOS/RxSwift] Merge, CombineLatest, Zip 비교하기</title>
      <link>https://hilily.tistory.com/96</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자주 사용되는 Operator인데 생각해보니 차이점을 명확하게 이해한 후 사용하지 않고 그때그때 찾아서 사용했었어서 이번 기회에 명확히 개념을 정리하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Merge&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 Observable이 방출하는 항목들을 하나의 Observable이 방출하도록 병합한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJLMg/btsDGIuoyWO/j949lzaku6J67594RHxl3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJLMg/btsDGIuoyWO/j949lzaku6J67594RHxl3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJLMg/btsDGIuoyWO/j949lzaku6J67594RHxl3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJLMg%2FbtsDGIuoyWO%2Fj949lzaku6J67594RHxl3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;325&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마블 다이어그램과 같이 첫번째 스트림과 두번째 스트림을 합쳐 하나의 스트림으로 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 첫번째 스트림, 두번째 스트림에 이벤트가 발생할 때 마다 바로바로 이벤트를 방출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 두 스트림의 이벤트를 하나로 합하기 때문에 스트림을 구별 할 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705751315555&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let firstSubject = BehaviorSubject(value: 1)
let secondSubject = BehaviorSubject(value: 2)
let thirdSubject = BehaviorSubject(value: 3)

Observable.of(firstSubject, secondSubject) // of를 통해 Observable 생성
    .merge()
    .subscribe { print($0) }
    .disposed(by: bag)

firstSubject.onNext(4)
secondSubject.onNext(5)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째와 두 번째 subject를 묶어 merge후 값을 찍어보면,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가장 처음 subscribe를 한 시점에 firstSubejct의 값인 1, secondSubject의 값인 2가 차례로 찍힌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 onNext를 통해 이벤트를 전달하면 4와 5가 나타난다.&lt;/p&gt;
&lt;pre id=&quot;code_1705751315556&quot; class=&quot;stylus&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// 결과
next(1)
next(2)
next(4)
next(5)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1705751315557&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let firstSubject = BehaviorSubject(value: 1)
let secondSubject = BehaviorSubject(value: 2)
let thirdSubject = BehaviorSubject(value: 3)

Observable.of(firstSubject, secondSubject) // of를 통해 Observable 생성
    .merge()
    .subscribe { print($0) }
    .disposed(by: bag)

firstSubject.onNext(4)
secondSubject.onNext(5)

//추가
firstSubject.onCompleted()
firstSubject.onNext(6)
secondSubject.onNext(7)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가로 firstSubject를 complete한 후 firstSubject에 이벤트를 전달하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;firstSubject는 6의 값을 방출하지 않지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;complete되지 않은 secondSubject는 그대로 값 7을 방출한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;CombineLatest&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qsg4k/btsDHsrGddT/HKCRncE7YJiFm6SNF9VGDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qsg4k/btsDHsrGddT/HKCRncE7YJiFm6SNF9VGDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qsg4k/btsDHsrGddT/HKCRncE7YJiFm6SNF9VGDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQsg4k%2FbtsDHsrGddT%2FHKCRncE7YJiFm6SNF9VGDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;624&quot; height=&quot;319&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 스트림과 두 번째 스트림을 결합한 후 계산 후 새로운 Observable을 만든다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;merge와 다르게 각 스트림의 값을 그대로 전달하는게 아니라 새로운 값을 만든다. 이때 각 스트림의 값에 따라 다른 결과를 도출할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 스트림에서 1이 방출되었지만 새로운 Observable에는 아무 값도 방출되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 두 번째 스트림까지 값(A)이 방출되면 그 때부터 각 스트림에서 가장 마지막으로 방출된 값을 합해 새로운 값을 방출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705752710161&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Observable.combineLatest(message, name) { msg, name -&amp;gt; String in
    return &quot;\(msg) \(name)&quot;
}
.subscribe { print($0) }
.disposed(by: bag)

message.onNext(&quot;내 이름은 &quot;)
name.onNext(&quot;릴리&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설명처럼 message.onNext()만 실행된 시점에는 아무런 값도 출력되지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 name.onNext()까지 실행된 시점에는&lt;/p&gt;
&lt;pre id=&quot;code_1705752788976&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 결과
next(내 이름은  릴리)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 값이 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705752891646&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Observable.combineLatest(message, name) { msg, name -&amp;gt; String in
    return &quot;\(msg) \(name)&quot;
}
.subscribe { print($0) }
.disposed(by: bag)

message.onNext(&quot;내 이름은 &quot;) // 아무 값도 안 나옴
name.onNext(&quot;릴리&quot;) // print: 내 이름은 릴리

message.onNext(&quot;내 별명은 &quot;) // print: 내 별명은 릴리
name.onNext(&quot;안나&quot;) // print: 내 별명은 안나

message.onCompleted()
message.onNext(&quot;나는&quot;) // 아무 값도 안 나옴
name.onNext(&quot;별이&quot;) // print: 내 별명은 별이

name.onCompleted() // completed 출력&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 한 단계마다 주석과 같은 값이 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;message가 onComplete된 시점에 message에 이벤트를 전달해도 전달이 안되는건 merge와 동일하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;message만 onComplete이고 name은 아직 onComplete되지 않은 시점에 name에 이벤트를 전달하면 message의 가장 마지막 값인 &quot;내 별명은 &quot;으로 값이 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;message와 name 모두 complete된 시점에 완전히 complete된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 둘 중 하나라도 error가 나오면 그 아래부터는 이벤트 전달이 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Zip&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0G04L/btsDJkfd1r1/wSsiuYH8vlKNk48a2Ce7t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0G04L/btsDJkfd1r1/wSsiuYH8vlKNk48a2Ce7t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0G04L/btsDJkfd1r1/wSsiuYH8vlKNk48a2Ce7t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0G04L%2FbtsDJkfd1r1%2FwSsiuYH8vlKNk48a2Ce7t0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;339&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;combineLastest와 비슷한데 다르다. 1과 A까지 방출했을 때는 combineLatest와 동일한데 Zip은 짝을 맞추어 방출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 각 스트림에서 값을 방출하였을 때 동일한 인덱스에 해당하는 값을 묶어 값을 방출한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짝을 지어야 값이 나가는 식으로 생각하면 좋을 것 같다. 방출되는 시점은 짝이 이루어졌을 때!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서 5는 짝이 없기 때문에 구독자에게 전달되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705753609510&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let key = PublishSubject&amp;lt;Int&amp;gt;()
let value = PublishSubject&amp;lt;String&amp;gt;()


Observable.zip(key, value) { &quot;\($0) : \($1)&quot;}
    .subscribe { print($0) }
    .disposed(by: bag)

key.onNext(1)
value.onNext(&quot;하나&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key에 1을 전달한 시점에는 아무 값도 구독자에게 전달되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value에 &quot;one&quot;이 전달된 시점에 구독자에 값이 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705753668003&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 결과
next(1 : 하나)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 값이 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705753769512&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Observable.zip(key, value) { &quot;\($0) : \($1) &quot;}
    .subscribe { print($0) }
    .disposed(by: bag)

key.onNext(1) // 아무 값도 안 나옴
value.onNext(&quot;하나&quot;) // print: (1:하나)

value.onNext(&quot;둘&quot;) // 아무 값도 안 나옴
key.onNext(2) // print: (2:둘)

key.onNext(3) // 아무 값도 안 나옴
key.onNext(4) // 아무 값도 안 나옴

key.onCompleted()

value.onNext(&quot;셋&quot;) // print: (3:셋)
value.onNext(&quot;넷&quot;) // print: (4:넷)
value.onNext(&quot;다섯&quot;) // 아무 값도 안 나옴

value.onCompleted()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 결과처럼 짝을 맞추어 결과값이 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key가 onCompleted 되었더라도 그 전에 값을 방출하였는데 그 값이 짝을 못 이뤘다면 onCompleted 되었더라도 뒤늦게 방출된 value에 값과 짝을 이룰 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key와 value 모두 complete된 시점에 완전히 complete된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;combineLatest와 마찬가지로 둘 중 하나라도 error가 나오면 그 아래부터는 이벤트 전달이 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전에는 마냥 사용하기 바빳는데 한번 정리하니까 정리된 느낌이다. ㅠㅠ 대충은 알고 있긴 했는데 명확히 개념은 못잡았었는데 이번에 정리하니 차이가 명확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 많이 사용할 것 같다고 느낀게 combineLatest이고 (가장 유연한 느낌?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;zip은 인덱스 다룰 때 사용한 적이 있었는데 그런 식으로 사용하면 좋겠다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;merge는 사용해 본 경험이 없는데 은근 많이 사용할 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머리에 박아놓고 가끔 읽어봐야겠다 ㅠㅠ..&lt;/p&gt;</description>
      <category>iOS</category>
      <author>hilily</author>
      <guid isPermaLink="true">https://hilily.tistory.com/96</guid>
      <comments>https://hilily.tistory.com/96#entry96comment</comments>
      <pubDate>Sat, 20 Jan 2024 21:34:06 +0900</pubDate>
    </item>
    <item>
      <title>[OS] CPU 스케줄링</title>
      <link>https://hilily.tistory.com/92</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스케줄링?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하나의 관리자로 프로세스들의 여러 상황을 고려하여 cpu와 시스템 자원을 어떻게 배정할 지 결정하는 역할을 함&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 고수준 스케줄링&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(가장 큰 틀에서 이루어지는 스케줄링)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;어떤 작업요청이 오면 스케줄러가 시스템의 상황을 고려하여 작업을 승인할지, 거부할지 결정함(승인 스케줄링 이라고도 함)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 중간수준 스케줄링&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;전체 시스템의 활성화된 프로세스 수를 조절하여 과부화를 막음&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 저수준 스케줄링&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;어떤 프로세스에 CPU를 할당하고 어떤 프로세스를 대기 상태로 보낼지 등을 결정함&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;아주 짧은 시간에 일어나기 때문에 단기 스케줄링이라고도 함&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;스케줄링의 목적&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;근본적으로 이런 스케줄링을 왜 할까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;모든 프로세스가 공평하게 작업하도록 하기 위해서이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;엄청나게 오래 걸리는 프로세스만을 처리하기 위해 상대적으로 짧은 시간이 소요되는 프로세스를 처리하는것이 밀리는 경우, 하지만 이런 짧은 프로세스를 무한히 처리하여 오래 걸리는 프로세스라는 이유로 끊임없이 밀리는 현상, 이런 상태에 반응 시간이 느려져 응답이 없어지는 현상 등을 방지하고자 스케줄링을 사용한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;1) 공평성 - 모든 프로세스가 자원을 공평하게 배정받아야 함&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;2) 효율성 - 시스템 자원이 유휴 시간 없이 사용되도록 스케줄링 하고, 유휴 자원을 사용하려는 프로세스에 우선권을 줘야함&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;3) 안전성 - 우선순위를 설정하여 시스템 자원을 점유, 파괴하려는 프로세스로부터 자원을 보호해야함&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;4) 확장성 - 프로세스가 증가해도 시스템이 안정적으로 작동하도록 조치해야함&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;5) 반응 시간 보장 - 응답이 없는 경우 사용자는 시스템이 멈춘 것으로 가정하기 때문에 시스템은 적절한 시간 안에 프로세스의 요구에 반응해야 함&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;6) 무한 연기 방지 - 특정 프로세스의 작업이 무한히 연기되어서는 안됨&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;선점과 비선점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;선점형 스케줄링 (뺏김)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 하나의 프로세스가 CPU를 차지하고 있을 때 우선순위가 높은 다른 프로세스가 현재 프로세스를 중단시키고 CPU를 점유하는 스케줄링 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 라운드 로빈, SRT, 다단계 큐, 다단계 피드백 큐&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;비선점형 스케줄링 (안뺏김)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 하나의 프로세스가 CPU 할당을 받으면 작업 종료 후 CPU 반환 시까지 다른 프로세스가 CPU를 점유할 수 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 우선순위, 기한부, FCFS, SJF, HRN&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스케줄링 알고리즘&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) FCFS&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어온 순서대로 CPU에 할당(FIFO와 동일)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) SJF&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 도착 시점에 따라 스 당시 가장 짧은 소요 시간을 갖는 프로세스가 종료시까지 CPU 점유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 도착한거 먼저 처리하고 다 끝나면 도착해 있는 프로세스 중 제일 짧은거 쭉 처리함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비선점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 라운드로빈&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 점유 시간을 정해 정한 시간 만큼 프로세스를 처리하고 만약 시간 내 처리하지 못하면 제일 뒤로 밀림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) SRT&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 처리 중 가장 짧은 시간이 걸리는 프로세스를 먼저 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 가장 먼저 도착한거 먼저 처리하다가 큐에 프로세스가 들어왔을 때 들어온 프로세스가 현재 처리중인 프로세스보다 처리 시간이 더 짧으면 짧은거 먼저 처리함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) HRN&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최고 응답률 우선 스케줄링이라고도 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SJF의 아사현상을 해결하기 위함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 =&amp;gt; 우선순위 - (대기시간 + CPU사용시간) / CPU 사용시간 을 계산하여 우선순위가 높을 수록 먼저 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 정리&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 시스템 내 전체 프로세스의 수를 조절하는 것으로, 장기 스케줄링 또는 작업 스케줄링이라 불리는 스케줄링 수준은 무엇인가?&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;고수준 스케줄링&lt;/b&gt;&amp;nbsp;(가장 큰 틀에서 이루어지는 스케줄링)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 어떤 프로세스에 CPU를 할당하고 어떤 프로세스를 대기 상태로 보낼지 등을 결정하는 스케줄링 수준은 무엇인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;저수준 스케줄링&lt;/b&gt; (가장 작은 단위의 스케줄링)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 어떤 프로세스가 CPU를 할당받아 실행 중이더라도 운영체제가 CPU를 강제로 빼앗을 수 있는 스케줄링은 무엇인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;선점형 스케줄링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 현재 입출력을 진행하는 프로세스로, 사용자와 상호작용이 가능하여 상호작용 프로세스라고도 불리는 것은 무엇인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;전면 프로세스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5. 준비 큐에 도착한 순서대로 CPU를 할당하는 비선점형 스케줄링 알고리즘은 무엇인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;FCFS&lt;/b&gt;(First-Come-First-Served) 스케줄링&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(=FIFO)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6. 준비 큐에 있는 프로세스 중 실행시간이 가장 짧은 작업부터 CPU를 할당하는 비선점형 스케줄링 알고리즘은 무엇인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;SJF&lt;/b&gt;(Shortest Job First) 스케줄링&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;7. SJF 스케줄링 알고리즘의 단점으로 크기가 큰 작업이 계속 뒤로 밀리는 현상을 무엇이라 하는가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;아사현상&lt;/b&gt;(starvation) (계속 밀려서 굶어 죽는 듯), 무한 봉쇄 현상&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;8. 아사 현상을 해결하는 방법을 설명하시오.&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;에이징&lt;/b&gt;(오랫동안 기다린 프로세스에게 우선순위를 높여줌)&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;9. 서비스를 받기 위해 대기한 시간과 CPU 사용시간을 고려하여 우선순위를 정하는 스케줄링 알고리즘은 무엇인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;HRN&amp;nbsp;스케줄링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;SJF 스케줄링 기법의 약점인 긴 작업과 짧은 작업 사이의 불평등을 보완&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선순위:&amp;nbsp;(대기시간&amp;nbsp;+&amp;nbsp;서비스(실행)시간)&amp;nbsp;/&amp;nbsp;서비스(실행)시간&amp;nbsp;=&amp;nbsp;시스템&amp;nbsp;응답시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;시스템 응답시간이 커질수록 우선순위가 높아짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;10. 프로세스가 할당받은 시간(타임 슬라이스) 동안 작업하다가 작업을 완료하지 못하면 준비 큐의 맨 뒤로 가서 다음 자기 차례가 올 때까지 기다리는 선점형 스케줄링 알고리즘 중 가장 단순한 것은 무엇인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;라운드 로빈(Round Robin) 스케줄링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;11. 타임 슬라이스의 크기와 문맥 교환의 관계를 설명하시오.&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;타임&amp;nbsp;슬라이스가&amp;nbsp;작으면&amp;nbsp;문맥&amp;nbsp;교환이&amp;nbsp;많아진다.&lt;br /&gt;타임 슬라이스가 크면 문맥 교환이 적어진다.&lt;br /&gt;=&amp;gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;타임 슬라이스의 크기와 문맥 교환은&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;반비례 관례&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;12. 기본적으로 라운드 로빈 방식을 사용하지만, CPU를 할당받을 프로세스를 선택할 때 남아있는 작업 시간이 가장 적은 것을 선택하는 스케줄링 알고리즘은 무엇인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;SRT 스케줄링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;13. 우선순위에 따라 준비 큐를 여러 개 사용하며 고정형 우선순위를 적용하는 스케줄링 알고리즘은 무엇인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;다단계 큐 스케줄링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;14. 우선순위에 따라 준비 큐를 여러 개 사용하며 프로세스가 CPU를 사용한 후 우선순위가 낮아지는 특성을 가진 스케줄링 알고리즘은 무엇인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;다단계 피드백 큐 스케줄링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;15. 다단계 피드백 큐 스케줄링에서 마지막 큐에 있는 프로세스(우선순위가 가장 낮은 프로세스)의 타임 슬라이스 크기는 얼마인가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;무한대&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;16. 다단계 피드백 큐 스케줄링에서 우선순위가 낮아질수록 타임 슬라이스의 크기는 어떻게 변하는가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;커진다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;17. 다단계 피드백 큐 스케줄링에서 마지막 큐(우선순위가 가장 낮은 큐)는 어떤 스케줄링 알고리즘처럼 동작하는가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;FCFS 스케줄링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Science/Operating System</category>
      <author>hilily</author>
      <guid isPermaLink="true">https://hilily.tistory.com/92</guid>
      <comments>https://hilily.tistory.com/92#entry92comment</comments>
      <pubDate>Wed, 13 Dec 2023 00:12:26 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] NIB View 생성자로 인스턴스 생성하기 (BaseView, 클린코드 독후감..)</title>
      <link>https://hilily.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;클린코드 독후감 .. :)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;클린코드를 읽고 더 좋은 코드에 관심이 생기게 되었다. 기존 개발 방식은 구현을 위주로 고민을 하였는데, 클린코드에 나온 내용을 내 프로젝트에 직접 적용해보는 과정을 통해 더 좋은 코드를 작성하고 싶다는 고민을 하게 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;클린코드에 나와있는 내용들이 전부 다 맞는 말이라고 생각하지 않는다. 상황에 따라 좋지 못한 방법, 비효율적인 방법이 효율적일 수 있고, 효율적인, 다른사람들이 선호하는 그런 방식들이 비효율적일 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를들어 어떤 러닝커브가 높은 아키텍처를 도입하기 위해 팀원들이 많은 시간과 노력을 소비해야 한다면, 그런데 이 때 프로젝트 기한이 짧다면 과연 그 아키텍처를 도입하는게 올바른 선택일까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재의 나는 올바르지 않다고 생각한다. 좋은 아키텍처, 고민한 코드들도 물론 중요하지만 가장 중요한건 이 서비스를 실제로 사용 할 사용자들이 출시일에 서비스를 이용할 수 있도록 편의를 제공하는게 가장 중요하다고 생각한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개인앱을 만들면서 다양한 기술을 도입하고 싶어서 멀쩡히 잘 돌아가는 코드를 몇 번이나 리팩토링 했다. 그 결과 몇몇 릴리즈 버전에서 버그가 발생하는 경우가 발생했는데, 개발자 입장으로는 다양한 기술을 사용하고 싶은 욕심으로 코드를 수정했지만 사용자 입장에서는 멀쩡하던 앱이 갑자기 튕기는 등의 버그를 마주치니 불편함을 느낄 수 밖에 없다. 어찌보면 더 좋은 코드, 더 다양한 기술을 사용한 서비스를 만드는건 개발자 욕심이 아닐까..?ㅎㅎ 이로인해 기한이 늦춰지거나 없던 버그가 발생한다면..! 이라는 생각도 들었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 내가 생각하는 클린코드란 협업에 도움을 주는 도구라고 생각한다. 협업에 있어 정해진 규칙에 맞게 작성하고, 클린코드의 내용을 하나의 도구로 사용하여 함께 작성하는 코드를 더 보기좋고 유지보수하기에 편한 코드로 만드는데 기여할 수 있는 도구라 생각한다. (웅애 개발자의 작은 생각입니다 ㅎ.ㅎ.. 나중에 바뀔지 모름!)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 리팩토링 하면서 불필요한 코드, 사용하지 않는 코드, 불필요하게 선언된 변수, 함수 등등 너무 많은 레거시 코드 때문에 그렇게 복잡한 UI가 아님에도 프로젝트가 무거워 보이는 느낌을 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 기존 코드를 따라가기 급급했는데 시간이 지나면서 불필요한 코드들이 계속 눈에 보여서 선임분께 동의를 구하고 리팩토링을 진행하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 정리하면서 재사용성, 확장성이 고려되지 않은 부분이 종종 보였고 그 중 하나였던 Action Sheet 리팩토링에 도전하여따!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfdGUJ/btsA1diJgle/Xl6PZoyLHPxX9f5BXJadDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfdGUJ/btsA1diJgle/Xl6PZoyLHPxX9f5BXJadDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfdGUJ/btsA1diJgle/Xl6PZoyLHPxX9f5BXJadDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfdGUJ%2FbtsA1diJgle%2FXl6PZoyLHPxX9f5BXJadDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;406&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플에서 기본으로 제공해주는 Action Sheet가 있지만 회사 프로젝트는 요구되는 디자인이 있었기 때문에 커스텀으로 구현되어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구되었던 상황은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 아이템 1개&lt;br /&gt;2. 아이템 1개 + 취소 버튼&lt;br /&gt;3. 아이템 2개 + 취소 버튼&lt;br /&gt;4. 아이템 3개 + 취소 버튼&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 구현방식은 하나의 뷰 컨트롤러에 StackView를 넣고, 그 안에 아이템에 해당하는 뷰를 필요한 개수만큼 미리 추가한 상태였다. 그리고 쓰임에 맞게 isHidden true/false하는 방식으로 구성되어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 상황에 따른 함수를 오버로딩을 통해 이름이 같은 여러개의 함수를 정의해두고 사용하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 구현방식도 무언가 엄청난 이유로 짜놓았겠지만, 좀 더 사용성 있게 짜볼 수 있을 것 같아 다음과 같은 방식으로 재 설계한 후 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 아이템은 [String] 형태로 사용자에게 입력받는다.&lt;br /&gt;- 취소버튼의 여부를 Bool 형태로 사용자에게 입력받는다.&lt;br /&gt;- item 하나를 컴포넌트 단위로 쪼개 사용한다.&lt;br /&gt;- 전달받은 아이템 [String]을 [CustomItemView] 형태로 변환하여 ViewController의 StackView에 추가한다.&lt;br /&gt;- 앞으로 구현하게 될 다른 뷰들도 컴포넌트 단위로 쪼개 사용할 가능성이 높으므로 BaseView를 추가로 생성하여 상속받는다.&lt;br /&gt;- BaseView를 상속받은 뷰를 사용할 때는 생성자 호출하는 것만으로 사용 가능하게 구성한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 정의한 다른 요구사항들은 순조롭게 진행했으나 마지막 조건인 &amp;ldquo;BaseView를 상속받은 뷰를 사용할 때는 생성자 호출하는 것만으로 사용 가능하게 구성한다.&amp;rdquo; 의 과정에서 문제가 생겼다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

class BaseView: UIView {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        if let view = UINib(nibName: String(describing: type(of: self)), bundle: nil).instantiate(withOwner: self, options: nil).first as? UIView {
            view.frame = self.bounds
            addSubview(view)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 BaseView를 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

final class CustomView: BaseView {
    
    @IBOutlet weak var btnItem: UIButton!
    
    convenience init(content: String) {
        self.init()
        
        initUI(content: content)
    }
    
    func initUI(content: String) {
        btnItem.setTitle(content, for: .normal)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 뷰를 만들고&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let itemView = CustomView(content: &quot;cancel&quot;.localized)
itemStackView.addArrangedSubview(itemView)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 stackView에 추가해주려고 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 BaseView에 commonInit()함수가 무한루프에 빠져 앱이 종료되는 상황이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 init() (처음 생성자 호출)-&amp;gt; commonInit() (Base에서 nib초기화) -&amp;gt; init() (다시 생성자 호출) -&amp;gt; &amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 호출되고 있었기 때문에 무한루프에 빠진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1068&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLI4HD/btsA2tsavhz/0JxUbf4c6D9sUTcTCmg7a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLI4HD/btsA2tsavhz/0JxUbf4c6D9sUTcTCmg7a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLI4HD/btsA2tsavhz/0JxUbf4c6D9sUTcTCmg7a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLI4HD%2FbtsA2tsavhz%2F0JxUbf4c6D9sUTcTCmg7a0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;377&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1068&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 이 무한루프를 해결하려면 File's Owner에서 각각의 Outlet 변수를 연결해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진처럼 File's Owner에서 우클릭하면 작은 창이 나타나고 각 요소를 뷰에 연결해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 nib 파일로 만든 뷰를 생성자를 통해 초기화하여 사용할 수 있다.!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/49127025/instantiate-uiview-from-nib-causes-infinite-loop-issue&quot;&gt;https://stackoverflow.com/questions/49127025/instantiate-uiview-from-nib-causes-infinite-loop-issue&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://swiftlyanand.medium.com/loading-custom-views-in-ios-the-right-way-bedfc06a4fbd&quot;&gt;https://swiftlyanand.medium.com/loading-custom-views-in-ios-the-right-way-bedfc06a4fbd&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>hilily</author>
      <guid isPermaLink="true">https://hilily.tistory.com/89</guid>
      <comments>https://hilily.tistory.com/89#entry89comment</comments>
      <pubDate>Mon, 27 Nov 2023 23:36:29 +0900</pubDate>
    </item>
    <item>
      <title>[OS] 컴퓨터의 구조와 성능 향상</title>
      <link>https://hilily.tistory.com/86</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. CPU의 구성에 대해 설명하시오&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;- CPU는 명령어를 해석하여 실행하는 장치이며 핵심 요소라 볼 수 있다. 산술논리 연산장치, 제어장치, 레지스터로 구성되며 이것들이 협업하여 작업을 처리한다.&lt;br /&gt;&lt;br /&gt;1) 산술논리 연산장치&lt;br /&gt;데이터를 연산하는 장치로 덧셈, 뺄셈, 곱셈, 나눗셈과 같은 산술연산, AND, OR과 같은 연산 수행&lt;br /&gt;&lt;br /&gt;2) 제어장치&lt;br /&gt;작업을 지시하는 부분&lt;br /&gt;&lt;br /&gt;3) 레지스터&lt;br /&gt;데이터를 임시로 보관하는 곳&lt;br /&gt;-&amp;gt; 왜? 임시로 보관해야 할까?&lt;br /&gt;-&amp;gt; 연산을 하기 위해 필요한 데이터를 CPU로 가져와 임시로 보관해야 함 (저장되어 있는 값을 CPU로 가져와서 계산해야 하는데 계산할 때 둘 곳이라고 생각!)&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. 폰노이만 구조의 가장 중요한 특징을 설명하시오&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;CPU, 메모리, 입출력장치, 저장장치가 버스로 연결되어 있는 구조 -&amp;gt; 핵심은 CPU, 메모리, 입출력장치, 저장장치와 같은 구조를 가진다는 것&quot;모든 프로그램은 메모리에 올라와야 실행할 수 있다&quot;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://m.hanbit.co.kr/channel/series/series_view.html?cms_code=CMS4316945379&amp;amp;hcs_idx=23&quot;&gt;https://m.hanbit.co.kr/channel/series/series_view.html?cms_code=CMS4316945379&amp;amp;hcs_idx=23&lt;/a&gt;&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. 버스의 종류를 나열하시오&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;버스(시스템 버스)는 CPU와 메모리, 주변장치 간에 데이터를 주고받을 때 사용&lt;br /&gt;&lt;br /&gt;1) 제어버스 (양방향)&lt;br /&gt;다음에 어떤 작업을 할지 지시하는 제어신호가 오고감 =&amp;gt; 데이터, 주소 버스를 제어하기 위해 제어신호들을 전송하는 통로&lt;br /&gt;&lt;br /&gt;2) 주소버스 (단방향)&lt;br /&gt;메모리의 데이터를 읽거나 쓸 때 어느 위치에서 작업할 것인지 알려주는 위치 정보가 오고감&lt;br /&gt;&lt;br /&gt;3) 데이터 버스 (양방향)&lt;br /&gt;제어버스가 신호를 보내고 주소 버스가 위치 정보를 전달하면 데이터가 데이터 버스에 실려 목적지로 이동&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;4. 단방향 버스에 대해 설명하시오&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;한 방향으로만 신호를 전송할 수 있는 버스&lt;br /&gt;주소버스가 단방향인 이유는 주소를 표현하는 신호가 CPU에서 다른 장치로 향하지만 반대로 다른장치에서 CPU로는 주소 정보를 전송할 필요가 없기 때문&lt;br /&gt;&lt;br /&gt;데이터버스는 데이터를 주고 받아야 하기 때문에 양방향 버스, 제어버스는 읽기/쓰기 동작을 모두 수행하기 때문에 양방향 버스이다.&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. 다음에 실행할 명령어의 주소를 가지고 있는 레지스터는 무엇인가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;프로그램 카운터(PC)&lt;br /&gt;01행을 처리 후 02행을 처리해야하기 때문에 PC가 필요함&lt;br /&gt;다음에 실행할 명령어의 주소를 기억하고 있다가 제어장치에 알려줌&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;6. 다음에 실행할 명령어를 보관하는 레지스터는 무엇인가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;명령어 레지스터(IR)&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;7.&amp;nbsp; 메모리 주소를 보관하는 레지스터는 무엇인가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;주소 레지스터(AR)&lt;br /&gt;데이터 또는 명령어가 저장된 메모리의 주소는 주소 레지스터에 저장된다.&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;8.&amp;nbsp; 메모리에 저장할 데이터나 메모리에서 가져온 데이터를 임시로 보관하는 레지스터는 무엇인가?&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&amp;nbsp;데이터 레지스터(DR)&lt;br /&gt;메모리에서 가져온 데이터를 임시로 보관할 때 사용&lt;br /&gt;CPU에 있는 대부분의 레지스터는 데이터 레지스터 =&amp;gt; 일반 레지스터 or 범용 레지스터라 부름&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;9. 주소 버스와 연결되어 있는 레지스터는 무엇인가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;메모리 주소 레지스터(MAR)&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;10.&amp;nbsp; 데이터 버스와 연결되어 있는 레지스터는 무엇인가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;메모리 버퍼 레지스터(MBR)&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;11.&amp;nbsp; 프로그램을 보호하기 위해 사용하는 두 가지 레지스터는 무엇인가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;경계 레지스터, 한계 레지스터&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;12. 속도 차이가 나는 두 장치 사이에서 속도를 완화하는 장치를 무엇이라고 통칭하는가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;경계 레지스터, 한계 레지스터&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;13. 응용 프로그램과 프린터 사이에서 속도 차이를 완화하는 소프트웨어를 무엇이라고 하는가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;스풀&lt;br /&gt;CPU와 입출력장치가 독립적으로 동작하도록 고안된 소프트웨어 버퍼&lt;br /&gt;버퍼와 다른점? -&amp;gt; 버퍼는 프로그램들이 버퍼를 공유함, 스풀러는 프로그램 간에 배타적&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;14.&amp;nbsp; 부팅 시 운영체제를 메모리로 가져오기 위해 사용하는 작은 프로그램은 무엇인가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;부트스트랩&lt;br /&gt;시스템의 부팅을 도와주는 제일 먼저 실행되는 코드&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;15.&amp;nbsp; 속도가 빠르고 값이 비싼 저장장치를 CPU 가까운 쪽에 두고, 값이 싸고 용량이 큰 저장장치를 반대쪽에 배열하는 방식을 무엇이라고 하는가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;저장장치 계층 구조&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;16.&amp;nbsp; 작업의 효율성을 높이기 위해 CPU가 입출력 관리자에게 입출력 작업을 요청하면 작업을 마친 입출력 관리자는 CPU에 무엇을 보내는가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;인터럽트&lt;br /&gt;프로세스 실행 도중 예기치 않은 상황(키보드입력, 클릭 등등..)이 발생할 때 발생한 상황을 처리한 후 실행 중인 작업으로 복귀하는 것 -&amp;gt; 정상적인 CPU 프로그램 실행을 방해함&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;17.&amp;nbsp; CPU 도움 없이도 메모리를 사용할 수 있도록 입출력 관리자에게 주는 권한을 무엇이라고 하는가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;직접 메모리 접근&lt;br /&gt;특정 하드웨어 하위 시스템이 CPU와 독립적으로 메인 시스템 메모리에 접근할 수 있게 해주는 컴퓨터 시스템의 기능&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;18.&amp;nbsp; CPU와 입출력 관리자가 서로 다른 메모리 영역을 사용하는 기법을 무엇이라고 하는가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;메모리 매핑 입출력&lt;br /&gt;마이크로프로세서(CPU)가 입출력 장치를 액세스할 때, 입출력과 메모리의 주소 공간을 분리하지 않고 하나의 메모리 공간에 취급하여 배치하는 방식&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;19.&amp;nbsp; CPU가 병렬 처리를 지원하지 않을 때 소프트웨어적으로 병렬 처리를 하는 기법을 무엇이라고 하는가?&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;VLIW 기법&lt;br /&gt;CPU가 병렬처리를 지원하지 않을 경우 소프트웨어적으로 병렬 처리를 하는 방법&lt;br /&gt;동시에 수행할 수 있는 명령어들을 컴파일러가 추출하고 하나의 명령어로 압축하여 실행함&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;심화문제 --&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;01.&amp;nbsp; 컴퓨터에서 클록이 하는 역할을 설명하시오.&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;CPU의 속도와 관련된 단위 =&amp;gt; 클록이 CPU가 작업할 때 일정한 박자를 만들어냄&lt;br /&gt;클록이 일정간격을 틱을 만들면 거기에 맞춰 CPU 안의 모든 구성 부품이 작업을 함&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;02.&amp;nbsp; 프로그램 카운터 레지스터의 역할을 설명하시오.&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;다음에 어떤 명령어를 처리해야할 지 -&amp;gt; 다음에 실행할 명령어의 주소를 기억하고 있다가 제어장치에 알려줌&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;03.&amp;nbsp; 제어 버스, 주소 버스, 데이터 버스에 대해 설명하시오.&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;제어버스: 다음에 어떤 작업을 할지 지시하는 신호가 오고감&lt;br /&gt;주소버스: 메모리의 데이터를 읽거나 쓸 때 어느 위치에서 작업할 것인지 알려주는 위치 정보가 오고감&lt;br /&gt;데이터 버스: 데이터 버스에 데이터가 실려 목적지까지 이동&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;04.&amp;nbsp; 휘발성 메모리와 비휘발성 메모리를 비교하여 설명하시오.&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;휘발성 메모리: DRAM(동적 램), SRAM(정적 램) -&amp;gt; 전력이 끊기면 데이터가 사라짐&lt;br /&gt;비휘발성 메모리: 플래시 메모리, FRAM, PRAM -&amp;gt; 전력이 끊겨도 데이터가 유지됨&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;05.&amp;nbsp; DDR SDRAM에 대해 설명하시오.&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;CPU와 램의 속도차이 완화, SDRAM의 대역폭을 늘려 데이터 입출력 속도를 빠르게 한 것&lt;br /&gt;클록틱 한번에 2개의 워드 저장&lt;/blockquote&gt;</description>
      <category>Computer Science/Operating System</category>
      <author>hilily</author>
      <guid isPermaLink="true">https://hilily.tistory.com/86</guid>
      <comments>https://hilily.tistory.com/86#entry86comment</comments>
      <pubDate>Wed, 1 Nov 2023 10:27:27 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] CollectionView Pagination과 Kingfisher로 이미지 캐싱(Kakao Search Image API)</title>
      <link>https://hilily.tistory.com/85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;예전에도 Pagination은 해 본 경험이 몇 번 있었지만 뭔가 아직 개념이 명확하게 잡히지 않았다는 생각에 뚱땅뚱땅 Pagination 실습을 진행하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 정의한 기능은 다음과 같다. (출처 내 머리)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 텍스트 필트에 텍스트를 한 글자 입력할 때 마다 결과가 로드된다.&lt;br /&gt;2. 입력 텍스트가 변경되면 데이터를 다시 받아온다.&lt;br /&gt;3. UI는 image가 한줄에 3개씩, API 1회 호출 시 데이터가 20개씩 추가된다.&lt;br /&gt;4. 가장 아래로 스크롤 하면 다음 결과를 불러온다.&lt;br /&gt;5. 이미지는 캐싱처리되어 이후 필요할 경우 캐싱된 데이터를 사용한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용한 라이브러리는 Moya로 Api 호출을 하고, Kingfisher로 이미지 로드와 캐싱을 처리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습에는 Kakao Search Image API를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide#search-image&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide#search-image&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1698125130822&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Kakao Developers&quot; data-og-description=&quot;카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.&quot; data-og-host=&quot;developers.kakao.com&quot; data-og-source-url=&quot;https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide#search-image&quot; data-og-url=&quot;https://developers.kakao.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zCQKq/hyUgIq96NS/6xiDV7W53kpZQSGKa11Dr1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/DNGas/hyUj7JzVfk/lpezvn5tRYWKRXVyKuA3S0/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000,https://scrap.kakaocdn.net/dn/bYgEOH/hyUgNzdAYZ/3iWUROoeaO4zBHDynLO5Lk/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000&quot;&gt;&lt;a href=&quot;https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide#search-image&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide#search-image&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zCQKq/hyUgIq96NS/6xiDV7W53kpZQSGKa11Dr1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/DNGas/hyUj7JzVfk/lpezvn5tRYWKRXVyKuA3S0/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000,https://scrap.kakaocdn.net/dn/bYgEOH/hyUgNzdAYZ/3iWUROoeaO4zBHDynLO5Lk/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Kakao Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과화면은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;이미지캐싱함.gif&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQrMHE/btsyXXwAs7g/CWDQFZVwTZKA9DHRZBNR8k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQrMHE/btsyXXwAs7g/CWDQFZVwTZKA9DHRZBNR8k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQrMHE/btsyXXwAs7g/CWDQFZVwTZKA9DHRZBNR8k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bQrMHE/btsyXXwAs7g/CWDQFZVwTZKA9DHRZBNR8k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;336&quot; height=&quot;683&quot; data-filename=&quot;이미지캐싱함.gif&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI는 최소한으로 구성하여 그다지 신경쓰지 않았다.. 껄껄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 하나하나 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1.&amp;nbsp; &lt;span style=&quot;text-align: left;&quot;&gt;텍스트 필트에 텍스트를 한 글자 입력할 때 마다 결과가 로드된다.&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;2.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;입력 텍스트가 변경되면 데이터를 다시 받아온다.&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1698122816902&quot; class=&quot;applescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;homeView.searchBar.textfield.rx.text.orEmpty
    .observe(on: MainScheduler.asyncInstance)
    .subscribe(onNext: { [weak self] text in
        guard let self else { return }

        viewModel.clearPageInfo()
        requestApiFirstStep(text: text)
    })
    .disposed(by: disposeBag)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;텍스트가 하나씩 입력될 때마다 api를 호출하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;2. UI는 image가 한줄에 3개씩, API 1회 호출 시 데이터가 20개씩 추가된다.&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1698122959905&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension HomeVC: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -&amp;gt; Int {
        return viewModel.searchDatas.count 
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -&amp;gt; UICollectionViewCell {
        guard let cell = homeView.collectionView.dequeueReusableCell(withReuseIdentifier: HomeCVCell.identifier, for: indexPath) as? HomeCVCell else { return UICollectionViewCell() }
        
        let urlString = viewModel.searchDatas[indexPath.row].imageURL
        DispatchQueue.main.async {
            cell.imageView.setImage(urlString: urlString)
        }
        
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -&amp;gt; CGSize {
        let numberOfItemsPerRow: CGFloat = 3
        let spacingBetweenItems: CGFloat = 10
        let totalSpacing = (numberOfItemsPerRow - 1) * spacingBetweenItems
        let availableWidth = homeView.collectionView.frame.width - totalSpacing
        let calculatedItemWidth = availableWidth / numberOfItemsPerRow
        
        return CGSize(width: calculatedItemWidth,
                      height: calculatedItemWidth)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;3개씩 한줄로 만드는 코드는 sizeForItemAt에 작성된 부분이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;컬렉션 뷰의 너비에서 한 줄에 들어가는 셀 간의 간격의 크기를 뺀다. 이는 numberOfItemsPerRow - 1 의 값이 되며 이 값에 셀 간에 간격에 해당하는 spacingBetweenItems의 값을 곱해서 뺀 후 전체 컬렉션뷰 너비에서 내가 한줄에 배치할 아이템의 개수 만큼 나누면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;4. 가장 아래로 스크롤 하면 다음 결과를 불러온다.&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1698123908981&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;extension HomeVC : UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offsetY = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height
        let height = scrollView.frame.height
        let isEndPosition = offsetY &amp;gt; (contentHeight - height)
        
        if isEndPosition &amp;amp;&amp;amp; viewModel.isEnabledPaging {
            let text = homeView.searchBar.textfield.text ?? &quot;&quot;
            requestApiMoreStep(text: text)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 테이블 뷰, 컬렉션 뷰에는 기본으로 스크롤뷰가 내장되어 있음을 알아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;컬렉션 뷰의 가장 아래로 스크롤하는 상황을 그림으로 그리면 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2882&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTX61G/btsy3Q4uleU/Fgb8gKof7K1SIBeTO2Hze1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTX61G/btsy3Q4uleU/Fgb8gKof7K1SIBeTO2Hze1/img.png&quot; data-alt=&quot;내가 그린 기린 그림&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTX61G/btsy3Q4uleU/Fgb8gKof7K1SIBeTO2Hze1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTX61G%2Fbtsy3Q4uleU%2FFgb8gKof7K1SIBeTO2Hze1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;391&quot; height=&quot;547&quot; data-origin-width=&quot;2882&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내가 그린 기린 그림&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림처럼 휴대폰에서 컬렉션 뷰가 차지하는 높이는 717이라는 값이고, 내가 api를 호출하여 데이터를 불러올 때 마다 추가로 들어오는 데이터에 대한 컨텐츠 높이가 876인 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림 점선으로 표시한 것과 같이 화면에는 보이지 않지만 api 호출 할 때마다 876만큼의 컨텐츠 높이가 추가된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ6XAJ/btsyYplXSl7/x7Ols7guMmlJmYartDOI0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ6XAJ/btsyYplXSl7/x7Ols7guMmlJmYartDOI0k/img.png&quot; data-alt=&quot;api 1회 호출 collectionView Position&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ6XAJ/btsyYplXSl7/x7Ols7guMmlJmYartDOI0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ6XAJ%2FbtsyYplXSl7%2Fx7Ols7guMmlJmYartDOI0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;24&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;api 1회 호출 collectionView Position&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 스크롤 한 offsetY는 198.xx, 데이터가 그려진 contents의 높이는 876.xx, 그리고 화면의 높이(화면에 보이는 컬렉션뷰의 높이)는 717이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;offsetY는 내가 스크롤 할 때 마다 변경될 것이고, contentHeight는 api가 호출 될 때마다 876만큼 더 추가될 것이고, height는 변하지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면을 처음 키면 내 화면에는 876만큼의 컨텐츠 내용 중 717의 높이만큼 보일 것이고, 이때 offsetY는 0이다. 따라서 보이지 않는 876 - 717 = 159 만큼의 영역을 내가 스크롤 할 수 있는 영역이 되고, 그러므로 offsetY가 159보다 커지게 되면 컬렉션 뷰 가장 아래에 도달했음을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxK3Gq/btsy7QbbQm3/c6RtWToKM3hE2NqdyZ8CLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxK3Gq/btsy7QbbQm3/c6RtWToKM3hE2NqdyZ8CLk/img.png&quot; data-alt=&quot;가장 아래로 스크롤 -&amp;amp;gt; api 호출 -&amp;amp;gt; collectionView Position&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxK3Gq/btsy7QbbQm3/c6RtWToKM3hE2NqdyZ8CLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxK3Gq%2Fbtsy7QbbQm3%2Fc6RtWToKM3hE2NqdyZ8CLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;565&quot; height=&quot;23&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가장 아래로 스크롤 -&amp;gt; api 호출 -&amp;gt; collectionView Position&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 아래에 도달 한 후 값을 보면 내 예상과 다르게 api 3회 호출 만큼의 컨텐츠(876 * 3 = 2628)가 추가되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해 가장 아래에 도달하여 api를 호출하자마자 isEnabledPaging라는 프로퍼티를 만들어 isEnabledPaging = false로 설정하여 이중 호출을 방지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api 호출 완료 후 isEnabledPaging = true로 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;5. 이미지는 캐싱처리되어 이후 필요할 경우 캐싱된 데이터를 사용한다.&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1698124335196&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension UIImageView {
    func setImage(urlString: String) {
        ImageCache.default.retrieveImage(forKey: urlString) { result in
            switch result {
            case .success(let value):
                // 캐시 존재
                if let image = value.image {
                    self.image = image
                } else {
                    //캐시 미 존재
                    guard let url = URL(string: urlString) else { return }
                    self.kf.setImage(with: url)
                }
                
            case .failure(let error):
                print(error)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;요 단계에서 Kingfisher를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;urlString을 전달하여 이미지를 설정하면 Kingfisher가 알아서 이미지 캐시가 있으면 캐시데이터를 던져주고 아니면 새로 생성한다. 굳굳&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kakao Image Search API를 사용하면서 몇몇 이미지 url이 http여서 화면에 나타나지 않는 이슈가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxpb1h/btsy7eQPdiO/R7p1m1hFIqoSghd0m38gj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxpb1h/btsy7eQPdiO/R7p1m1hFIqoSghd0m38gj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxpb1h/btsy7eQPdiO/R7p1m1hFIqoSghd0m38gj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcxpb1h%2Fbtsy7eQPdiO%2FR7p1m1hFIqoSghd0m38gj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;941&quot; height=&quot;207&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;353&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rwmHg/btsy051UOnq/Fiq1XhaP17av494ZvFd58k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rwmHg/btsy051UOnq/Fiq1XhaP17av494ZvFd58k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rwmHg/btsy051UOnq/Fiq1XhaP17av494ZvFd58k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrwmHg%2Fbtsy051UOnq%2FFiq1XhaP17av494ZvFd58k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;353&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;353&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Target - Info에서 App Transport Security Settings -&amp;gt; Allow Arbitrary Loads = True로 하면 http 이미지도 로드가 가능하다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 요기!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/hililyy/ios-practice/tree/main/Pagination&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/hililyy/ios-practice/tree/main/Pagination&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>hilily</author>
      <guid isPermaLink="true">https://hilily.tistory.com/85</guid>
      <comments>https://hilily.tistory.com/85#entry85comment</comments>
      <pubDate>Tue, 24 Oct 2023 14:25:40 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Keychain(키체인) 알아보기</title>
      <link>https://hilily.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 저장하는 방법에는 UserDefaults, CoreData, Keychain... 등 여러가지 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 데이터의 종류가 다양하기 때문에 각각의 방법들의 쓰임새가 다른데 많고 많은 방법 중 키체인 방식에 대해 알아보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Keychain(키체인)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/security/keychain_services&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/security/keychain_services&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690773320057&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Keychain Services | Apple Developer Documentation&quot; data-og-description=&quot;Securely store small chunks of data on behalf of the user.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/security/keychain_services&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/security/keychain_services&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cMszvm/hyTveilu0u/XAXQ11OkG4Ts70u0PK3sF0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/vCt1k/hyTttg85zx/DrdycWKGtRw4rqmFd15Knk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/security/keychain_services&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/security/keychain_services&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cMszvm/hyTveilu0u/XAXQ11OkG4Ts70u0PK3sF0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/vCt1k/hyTttg85zx/DrdycWKGtRw4rqmFd15Knk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Keychain Services | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Securely store small chunks of data on behalf of the user.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;503&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zDlBc/btspxp8uPap/ct6YjWsKKyGnAK2SkKx6MK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zDlBc/btspxp8uPap/ct6YjWsKKyGnAK2SkKx6MK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zDlBc/btspxp8uPap/ct6YjWsKKyGnAK2SkKx6MK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzDlBc%2Fbtspxp8uPap%2Fct6YjWsKKyGnAK2SkKx6MK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;276&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;503&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플 공식 문서에 따르면 안전하게 저장해야하는 작은 데이터를 저장할 때 사용한다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림처럼 비밀번호, 암호키, 인증키, 메모 등의 정보를 암호화하여 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 든 생각은 키체인이 안전하게 데이터를 관리한다고 하는데 어떻게 안전하게 관리하는지 작은 데이터를 저장할때는 UserDefaults를 사용한다고 했는데 두개가 무슨 차이점이 있는건지 궁금했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 UserDefaults와 Keychain등 애플에서 만든것이기 때문에 차이점은 있을테니 하나씩 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UserDefaults vs Keychain&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserDefaults와 어떤 차이가 있을까? 왜 키체인을 사용할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 키체인과 비교하기 전 &lt;a href=&quot;https://developer.apple.com/documentation/foundation/userdefaults&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;UserDefaults&lt;/a&gt;를 알아보면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UserDefaults&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- plist파일에 데이터가 저장되어 유저가 탐색기를 통해 접근이 가능 -&amp;gt; (보안이 중요하지 않은 데이터만 저장 권장)&lt;br /&gt;- 앱 삭제 시 데이터가 함께 사라짐 -&amp;gt; (SandBox 내부 영역에 데이터가 저장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 앱이 시작될 때 plist파일이 로드되어 싱글톤 형태로 접근 가능 -&amp;gt; (너무 데이터가 거대하면 로드 시 앱 성능 저하)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://developer.apple.com/documentation/xcode/configuring-app-groups&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AppGroup&lt;/a&gt;을 통해 App과 Extension간 UserDefaults 데이터 공유가 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbBpnK/btspCmcO1ic/g39Mku1M7KNAFJmFsInbY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbBpnK/btspCmcO1ic/g39Mku1M7KNAFJmFsInbY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbBpnK/btspCmcO1ic/g39Mku1M7KNAFJmFsInbY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbBpnK%2FbtspCmcO1ic%2Fg39Mku1M7KNAFJmFsInbY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;97&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것저것 특징이 많긴 했지만 중요하다고 생각되는 부분 몇개만 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 키체인의 특징은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Keychain&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 앱을 삭제해도 데이터가 남아 있음 -&amp;gt; (SandBox 밖에 데이터 저장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 디바이스가 Lock 시 키체인도 Lock, 디바이스가 unLock시 키체인도 unLock 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://support.apple.com/ko-kr/guide/security/sec59b0b31ff/web&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Secure Enclave&lt;/a&gt;라는 암호화 기능을 사용하는 하드웨어에서 키체인 데이터를 관리함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://developer.apple.com/documentation/xcode/configuring-keychain-sharing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Keychain Sharing&lt;/a&gt;을 통해 동일한 개발자에 속한 서로 다른 앱 간 키체인 공유가 가능함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjsm9B/btspFjtqb0X/ScblLsj0hQuDv4mKWZmm61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjsm9B/btspFjtqb0X/ScblLsj0hQuDv4mKWZmm61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjsm9B/btspFjtqb0X/ScblLsj0hQuDv4mKWZmm61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjsm9B%2FbtspFjtqb0X%2FScblLsj0hQuDv4mKWZmm61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;101&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Keychain item&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6zSdL/btspCmDK7oM/9k95loDCgdv6GbIRFHWVfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6zSdL/btspCmDK7oM/9k95loDCgdv6GbIRFHWVfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6zSdL/btspCmDK7oM/9k95loDCgdv6GbIRFHWVfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6zSdL%2FbtspCmDK7oM%2F9k95loDCgdv6GbIRFHWVfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;160&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터를 Item으로 패키징하며, 저장하려는 데이터 뿐만 아니라 Attributes도 함께 저장됨 -&amp;gt; 이 Attribute를 통해 Data에 접근한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJeTqi/btspCmcGSwG/GxxAYsA5Pko8SsK2L3OmVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJeTqi/btspCmcGSwG/GxxAYsA5Pko8SsK2L3OmVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJeTqi/btspCmcGSwG/GxxAYsA5Pko8SsK2L3OmVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJeTqi%2FbtspCmcGSwG%2FGxxAYsA5Pko8SsK2L3OmVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;226&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp; Involve the User When Needed (오른쪽 그림)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에 권한이 처음 필요할 때는 키체인 암호가 저장 안됨 -&amp;gt; 사용자에게 권한 요청 -&amp;gt; 사용자 권한 허용 -&amp;gt; 앱은 권한 저장(SecItemAdd(_:_:)) -&amp;gt; 추후 키체인에서 권한 검색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Avoid&amp;nbsp;Bothering&amp;nbsp;the&amp;nbsp;User&amp;nbsp;in&amp;nbsp;the&amp;nbsp;Common&amp;nbsp;Case&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키체인에서 비밀번호 검색(SecItemCopyMatching(_:_:)) -&amp;gt; 앱에서 인증 -&amp;gt; 사용자 개입없이 진행 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Handle&amp;nbsp;Changes&amp;nbsp;Gracefully&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 앱 영역 밖에서 비밀번호 변경 -&amp;gt;새로운 비밀번호를 받아 기존 저장 값 수정 (SecItemUpdate) / 키체인에서 비밀번호 완전 삭제(SecItemDelete)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Secure Enclave&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1303&quot; data-origin-height=&quot;1569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXB3iE/btspFjUg5XC/6po5pB9pbcIJgb8kAzRFlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXB3iE/btspFjUg5XC/6po5pB9pbcIJgb8kAzRFlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXB3iE/btspFjUg5XC/6po5pB9pbcIJgb8kAzRFlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXB3iE%2FbtspFjUg5XC%2F6po5pB9pbcIJgb8kAzRFlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;526&quot; height=&quot;633&quot; data-origin-width=&quot;1303&quot; data-origin-height=&quot;1569&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진 파란색 영역이 Secure Enclave라고 설명하고 있다. 해당 영역은 메인 프로세서와 격리되어 추가적인 보안 계층을 제공하여, 커널이 손상된 경우라고 해도 사용자의 데이터를 안전하게 보관할 수 있도록 설계되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Secure Enclave라는 영역에 Touch id, Face id, Security Framework, Cryptokit과 같은 것들이 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Secure Enclave는 import, export를 하지 않기 때문에 다른곳에서 생성한 키를 저장하고 빼낼 수 없다. -&amp;gt; 키를 생성한 디바이스에서만 암호화가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;KSecClass&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 아이템이 어떤 종류의 정보인지 나타난다. 종류는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1690784013036&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@available(iOS 2.0, *)
public let kSecClassInternetPassword: CFString

@available(iOS 2.0, *)
public let kSecClassGenericPassword: CFString

@available(iOS 2.0, *)
public let kSecClassCertificate: CFString

@available(iOS 2.0, *)
public let kSecClassKey: CFString

@available(iOS 2.0, *)
public let kSecClassIdentity: CFString&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Predefined item class constants used to get or set values in a dictionary.&amp;nbsp;&lt;br /&gt;The&amp;nbsp;kSecClass&amp;nbsp;constant&amp;nbsp;is&amp;nbsp;the&amp;nbsp;key&amp;nbsp;and&amp;nbsp;its&amp;nbsp;value&amp;nbsp;is&amp;nbsp;one&amp;nbsp;of&amp;nbsp;the&amp;nbsp;constants&amp;nbsp;defined&amp;nbsp;here.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 아래 목록 중 적절한 것을 골라 사용하면 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. kSecClassInternetPassword&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인터넷 password item&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. kSecClassGenericPassword&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 일반 password item&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. kSecClassCertificate&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인증서 item&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. kSecClassKey&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- key item&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. kSecClassIdentity&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- id item&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고.. 이 5가지 Class안에 많은 attributes가 있는데 너무 많아서 공식문서 참고하는게 좋겠다!! (&lt;a href=&quot;https://developer.apple.com/documentation/security/keychain_services/keychain_items/item_attribute_keys_and_values&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/security/keychain_services/keychain_items/item_attribute_keys_and_values&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 마지막으로 키체인 CRUD 메소드를 알아보쟈..~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;KeyChain Method&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Create&lt;/h3&gt;
&lt;pre id=&quot;code_1690783081991&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var account = &quot;&quot; // 고유한 키
    
func create(_ valueData: String) {

    let data = valueData.data(using: String.Encoding.utf8)! // item Class
    let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                  kSecAttrAccount: account, // 계정 ID 정보
                                  kSecValueData: data] // 저장하려는 데이터 (비밀번호)

    let status = SecItemAdd(query as CFDictionary, nil)

    switch status {
    case errSecSuccess:
        print(&quot;등록 성공&quot;)
    case errSecDuplicateItem: // 중복
        update(valueData)
    default:
        print(&quot;등록 실패&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/security/1401659-secitemadd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SecItemAdd(_:_:)&lt;/a&gt; 를 사용하면 키체인 등록이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. read&lt;/h4&gt;
&lt;pre id=&quot;code_1690783154932&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var account = &quot;&quot; // 고유한 키

func read() -&amp;gt; Any? {
    let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                  kSecAttrAccount: account,
                                  kSecReturnAttributes: true,
                                  kSecReturnData: true]

    var item: CFTypeRef?
    if SecItemCopyMatching(query as CFDictionary, &amp;amp;item) != errSecSuccess { return nil }

    guard let existingItem = item as? [String: Any],
          let data = existingItem[kSecValueData as String] as? Data,
          let valueData = String(data: data, encoding: .utf8) else { return nil }

    return valueData
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/security/1398306-secitemcopymatching&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SecItemCopyMatching(_:_:)&lt;/a&gt;를 사용하면 키체인 조회가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. update&lt;/h4&gt;
&lt;pre id=&quot;code_1690783169490&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func update(_ valueData: Any) {
    let previousQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword, kSecAttrAccount: account]
    let updateQuery: [CFString: Any] = [kSecValueData: valueData]

    let status = SecItemUpdate(previousQuery as CFDictionary, updateQuery as CFDictionary)

    switch status {
    case errSecSuccess:
        print(&quot;수정 성공&quot;)
    default:
        print(&quot;수정 실패&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/security/1393617-secitemupdate&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SecItemUpdate(_:_:)&lt;/a&gt;를 사용하면 키체인 수정이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. delete&lt;/h4&gt;
&lt;pre id=&quot;code_1690783186400&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func delete(key: String) {
    let deleteQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                        kSecAttrAccount: key]
    let status = SecItemDelete(deleteQuery as CFDictionary)

    switch status {
    case errSecSuccess:
        print(&quot;삭제 성공&quot;)
    default:
        print(&quot;삭제 실패&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/security/1395547-secitemdelete&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SecItemDelete(_:)&lt;/a&gt;를 사용하면 키체인 삭제가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://rhammer.tistory.com/392&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://rhammer.tistory.com/392&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://applecider2020.tistory.com/48&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://applecider2020.tistory.com/48&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>hilily</author>
      <guid isPermaLink="true">https://hilily.tistory.com/83</guid>
      <comments>https://hilily.tistory.com/83#entry83comment</comments>
      <pubDate>Mon, 31 Jul 2023 15:25:41 +0900</pubDate>
    </item>
  </channel>
</rss>