---
# System prepended metadata

title: "\U0001F525불타는 토요스터디_A반_2\U0001F525"
tags: [study]

---

# 🔥불타는 토요스터디_A반🔥

[1차 정리 링크](https://hackmd.io/gU8gQdVNQaWbhEnIBN20yw?edit)

# 토요스터디 양식

🔥 불타는 토요스터디 9기 A반🔥 
📆 2023.08.12 토) Week #14
🫂 스터디원: Zion👑 , MINT😈 , yyss99🐣 , Yetti🦊 , redmango🥭 , jusbug😃

[금주의 실험 주제] : 


# 11주차_1: Protocol Oriented Programming
## 발표자 Zion
pop를 구성하는 핵심 적인 요소는 기능별로 작은 조각들로 나뉜 프로토콜과 기본구현(extension)이다.

먼저 공통적으로 가질 수 있는 부분들을 기능별로 Protocol로 묶어주고 묶어준 컴포넌트들을 사용해서 기본구현을 통해서 확장성 있게 해당 Protocol을 채택하고 있는 타입이면 누구든 해당 기능을 사용할 수 있도록 정의할 수 있다.

```swift
protocol ContainContents {
    var contents: [Content] { get }
    var numberOfContents: Int { get }
}

extension ContainContents {
    var contents: [Content] {
        return TimeLineContentObject.shared.contents
    }
    
    var numberOfContents: Int {
        return contents.count
    }
}
```
ViewController가 가지고 있어야하는 공통적인 contents를 담고 있고 Contents를 가지고 오는 역할을 하는 Protocol이다. 프로젝트에서 가지고 있는 ViewController(TableView, CollectionView, DetailView)에서 사용하고 있는 contents를 정의하고 extension을 통해 공통적으로 가져올 수 있도록 설계햇다.

```swift
protocol MediaContainer {
    var content: Content? { get set }
    var media: ContentPresentable { get }
    var note: UILabel { get set }
    
    var videoLayer: AVPlayerLayer { get }
    var mediaImageView: UIImageView { get }
    
    func contentChanged()
}

extension MediaContainer {
    func contentChanged() {
        guard let content = content else {
            mediaImageView.isHidden = true
            mediaImageView.image = nil
            videoLayer.isHidden = true
            videoLayer.player = nil
            return
        }
        
        switch content.type {
        case .image:
            mediaImageView.isHidden = false
            videoLayer.isHidden = true
            
            let image = UIImage(contentsOfFile: content.urlString)
            
            mediaImageView.image = image
        case .video:
            mediaImageView.isHidden = true
            videoLayer.isHidden = false
            
            let videoURL = URL(filePath: content.urlString)
            let player = AVPlayer(url: videoURL)
            
            videoLayer.player = player
        }
        
        note.text = content.note
    }
    
    var media: ContentPresentable {
        switch content!.type {
        case .image:
            return mediaImageView
        case .video:
            return videoLayer
        }
    }
}
```
Cell (TableView, CollectionView) 및 DetailView에서 직접적으로 보여줘야하는 content를 가지고 있고 content가 변경되었을 경우 존재하는 content에 대한 기본 setting을 진행하는 MediaContrainer Protocol이다. Extension을 통해서 content에 따른 기본 세팅을 기본구현되어있다. content로 들어오는 data가 image뿐만아니라 동영상도 들어올 수 있도록 유연하게 설계될 수 있어야하므로 ContentPresentable Protocol을 사용해서 해당 Protocol을 채택한 어떤 타입이든 적용되어 사용될 수 있도록 할 수 있다. 현재는 image, 동영상만 사용할 수 있도록 대응한 상태이다.

Protocol을 사용하면 공통적으로 묶을 수 있는 부분에 대한 코드사용 및 기능의 사용을 통일화 시킬 수 있다. 하지만 Protocol Extension을 통해 기본구현을 하게될 경우 해당 Protocol에서 명시하고 있는 프로퍼티들에서만 사용이 가능한데 where Clause와 함께 사용하게 된다면 where Clause를 통해 추가적으로 제한된 기능을 같이 사용할 수 있으므로 extension을 통한 기본구현 사용시 where Clause를 같이 사용하는 것도 좋을 것 같다.

사용한코드: https://github.com/LeeZion94/POPExample.git

<출처>
발표자료 : https://www.slideshare.net/JoSeongGyu/protocol-oriented-programming-in-swift
참고한 예시 코드 : https://bitbucket.org/yagom/popsocialmediaexample/src/master/
참고영상 : https://www.youtube.com/watch?v=9gkzHUsQiUc 


# 11주차_2: Protocol Oriented Programming 실험
## 발표자 🥭READMANGO🥭
<details>
<summary>야곰 실험실 기반 실습(일부수정)</summary>

Protocol Oriented Programming(프로토콜 지향 프로그래밍)
 - Protocol Oriented Programming(POP) 탄생 배경
일반적으로 OOP에서 클래스의 상속 개념을 통해 공통 기능들을 모듈화하여 사용할 수 있었으나, 구조체 혹은 열거형의 경우에는 상속이 불가하기에 같은 타입에 대해서 공통적인 기능 구현을 위해서는 다른 방법을 이용해야 합니다.

- 이를 해결하기 위한 방안이 Apple WWDC15(Swift 2)에서 Extension 발표되었습니다.
Protocol(청사진)을 구현하고 Extension으로 해당 프로토콜의 기능을 확장하는 형태로 사용합니다.
이를 프로토콜 초기 구현(Protocol Default Implementation)이라고 표현합니다.
---

```swift
protocol MacBookChargeabel {
    var maximumWattPerHour: WattPerHour { get }
    
    //충전기와 기기의 허용 와트시를 검사해서, 기기의 허용 와트시가 더 낮다면, 
    //기기에 맞추어 와트시를 낮추어 반환합니다.
    func convert(chargeableWattPerHour: WattPerHour) -> WattPerHour
}

```
위와 같이 `Chargeabel` 프로토콜을 정의했다.

```swift
struct Charger: MacBookChargeabel {
    var maximumWattPerHour: WattPerHour
 
    func convert(chargeableWattPerHour: WattPerHour) -> WattPerHour {
        
        if maximumWattPerHour > chargeableWattPerHour {
            return chargeableWattPerHour
        }
        
        return maximumWattPerHour
    }
}

struct TroubleMacBookCharger: MacBookChargeabel {
    var maximumWattPerHour: WattPerHour
    let troublenProbability: Int = Int.random(in: 1...100)
    var isTrouble: Bool {
        if self.troublenProbability > 50 {
            return true
        }
        return false
    }
    
    func convert(chargeableWattPerHour: WattPerHour) -> WattPerHour {
        guard isTrouble else {
            if maximumWattPerHour > chargeableWattPerHour {
                return chargeableWattPerHour
            }
            return maximumWattPerHour
        }
        return maximumWattPerHour
    }
}
```
`MacBookChargeabel`프로토콜을 채택한 `Charger`타입과 `TroubleMacBookCharger`가 있다고 해보자.

```swift 
 struct MacBook: Portable {
    private let 기기허용충전와트시: Watt = 20
    private let 현재저장된배터리용량 : Watt = 20
    private let 최대배터리용량: Watt = 100

    mutating func chargerBattery(charger: TroubleMacBookCharger) {
        let 필요한배터리량 = 최대배터리용량 - 현재저장된배터리용량
        let 충전되는배터리량 = charger.convert(chargeableWattPerHour: 기기허용충전와트시)
        
        guard 충전되는배터리량 <= 기기허용충전와트시 else {
            print("과충전으로 맥북의 회로가 타버렸습니다. 유감입니다.")
            return
        }
        
        let 완충까지걸린시간 = 필요한배터리량/충전되는배터리량
        print("완충까지 걸린 시간: \(완충까지걸린시간)시간")
    }
}
```
`MacBook`타입은 배터리충전기능 사용시 `TroubleMacBookCharger`타입을 필요로한다. 

```swift
var macBook = MacBook()

let 애플워치충전기: Charger = Charger(maximumWattPerHour: 5)
let 아이폰고속충전기: Charger = Charger(maximumWattPerHour: 18)
let 아이패드충전기: Charger = Charger(maximumWattPerHour: 30)
let 노트북충전기: Charger = Charger(maximumWattPerHour: 96)
let 고속노트북충전기: Charger = Charger(maximumWattPerHour: 106)
let 낡은맥북충전기: TroubleCharger = TroubleCharger(maximumWattPerHour: 96)

macBook.chargerBattery(charger: 낡은맥북충전기) // 4시간 or 일정확률로 회로가 구워진 맥북
macBook.chargerBattery(charger: 노트북충전기) // error / 호환이 안된다!
```
이런 경우 맥북을 충전할때마다 충전이 될지 일정확률로 장작이 되어버릴지 몰라 벌벌떨게 될것이다. 그렇지만 애플에 문의해도 충전할때 `TroubleCharger`만 쓸 수 있다고 하니 어쩔 수 없다. 맥북이 장작이 되면 애플본사도 장작으로 만들어주는 수 밖에...
하지만 얼마 지나지 않아 애플도 문제를 파악했는지 맥북을 전부 수거해 개조(?)를 해 줬다!
충전시 다른 충전기를 사용할 수 있게 해준것이다! 물론 애플이 제시한 충전기 규약 `MacBookChargeabel`프로토콜을 채택해 준수하는 충전기만 사용 가능하다고 했지만 이게 어디인가!

```swift
struct MacBook: Portable {
    private let 기기허용충전와트시: Watt = 20
    private let 현재저장된배터리용량 : Watt = 20
    private let 최대배터리용량: Watt = 100

    mutating func chargerBattery(charger: MacBookChargeabel) {
        let 필요한배터리량 = 최대배터리용량 - 현재저장된배터리용량
        let 충전되는배터리량 = charger.convert(chargeableWattPerHour: 기기허용충전와트시)
        
        guard 충전되는배터리량 <= 기기허용충전와트시 else {
            print("과충전으로 맥북의 회로가 타버렸습니다. 유감입니다.")
            return
        }
        
        let 완충까지걸린시간 = 필요한배터리량/충전되는배터리량
        print("완충까지 걸린 시간: \(완충까지걸린시간)시간")
    }
}
``` 
이제 우린 문제있는 충전기를 갖다 버리고 안전한 충전기로 맥북을 충전할 수 있다. 애플 만세!

```swift
macBook.chargerBattery(charger: 노트북충전기) // 4시간
```

위와 같이 프로토콜을 활용할 경우 일단 우린 타입을 알 필요가 없어진다.
그래서 Protocol을 이용하면 개발자는 구체적인 Type에 일일히 대응하거나 특정 구조체(Type)를 return하는 경우들 하나하나에 연연하지 않을 수 있다.
기존에 구체적인 타입 하나하나에 의존했다면 이젠 추상적인 프로토콜만 의존하면 된다는 뜻이다. 

만일 프로토콜을 사용하지 않았다면 맥북은 여러방법으로 문제를 해결해야했을것이고 그중 하나의 방법이 아래와 같을 수 있다.
```swift
struct MacBook: Portable {
    let ...

    mutating func chargerBatteryPortA(charger: TroubleMacBookCharger) {
       ...
    }

    mutating func chargerBatteryPortB(charger: Charger) {
        ...
    }
}
``` 

똑같은 코드를 매개변수의 타입만 바꿔서 추가한것이다. 프로토콜을 사용하면 이러한 중복된 코드를 피할 수 있게 해준다. 즉 재사용성이 높아진다.
그리고 만약 `Charger`타입의 기능이 변경되거나 삭제된다면 `chargerBatteryPortB`메서드의 작동에 문제가 생길 수 있는데, 프로토콜을 채택 및 준수한다는것은 채택한 타입에서 기능이 구현 되어있고 변경되거나 삭제될리 없다는걸 보장함으로 이러한 문제와 관련해선 메서드의 작동에 문제가 생길일이 없어진다.

아래는 POP의 장점을 간략하게 정리해본것이다.
- 코드의 중복을 최소화 할 수 있다.
- 가볍고 안전하다.
    - 상속과 달리 필요한 것만 골라서 쓸 수 있다.
    - 상속의 경우, 참조 타입이므로 참조 추적을 위한 비용이 많이 발생하기 때문에 속도측면에서 다소 느리다.
- 값 타입의 상속 효과
    - 공통된 기능을 쉽게 구현할 수 있다.
- 수평적인 확장 기능
    - Class는 하나의 상속만 가능하고 수직적인 구조가 나타나지만 Protocol을 이용하면 수평적인 확장이 가능하다.
- 제네릭의 활용
    - 예를 들어, Swift의 Array의 경우 데이터 타입의 관계 없이 만들 수 있고 이들 모두가 그에 따른 메서드를 사용할 수 있다.
</div>
</details>

# 12주차: Responder Chain
## 🔥실험목표
### 👨‍🔬 실험 1: UIKit이 가장 끝 View를 찾아내는 방법
### 👨‍🔬 실험 2: ResponderChain 활용해보기

## 💻 발표자료
![](https://hackmd.io/_uploads/BydNtlfjn.png)

![](https://hackmd.io/_uploads/Hkk7qezin.png)
**< Outline >**
1.Responder chain
2.hitTest 
3.hitTest Logic
4.hitTest Code
![](https://hackmd.io/_uploads/BkewYefih.png)
**Responder**: UIResponder는 일련의 이벤트를 받고 처리하거나 다른 객체에 이벤트를 전달하는 역할을 합니다.
**Responder chain**:여기서 이벤트를 처리하지 못해 넘겨질 경우 다음 개체로 연결되어 이동하는데 이처럼 이벤트 처리를 목적으로 연결되어 있는 Responder 개체들의 연결 관계가 Responder Chain 입니다.

![](https://hackmd.io/_uploads/Hko8YgMjh.png)
**First Responder**: UIKit은 자동으로 이벤트를 처리하기에 가장 적절한 Responder 객체를 찾아 이벤트를 전달하는데 이 객체가 First Responder입니다.

![](https://hackmd.io/_uploads/HkvOKgfj3.png)
UIEvent 타입의 종류와 이벤트를 전달 받는 First Reponder

![](https://hackmd.io/_uploads/HkskixMih.png)
**< Responder chain Logic >**
1. 이벤트가 발생하면, 해당 이벤트는 이벤트가 발생한 뷰를 기준으로 Responder Chain을 따라 이동합니다.
2. 뷰 계층 구조에서 가장 앞쪽에 있는 뷰부터 이벤트에 응답할 수 있는지 확인하고, 응답할 수 있다면 이벤트를 처리합니다. 응답할 수 없는 경우에는 상위 뷰로 이벤트가 전달됩니다.
3. 이벤트는 뷰 계층 구조에서 상위로 전달되며, UIView는 UIResponder의 서브클래스이므로, 이벤트를 처리할 수 있는 객체가 UIResponder의 서브클래스 중 하나라면 해당 이벤트를 처리할 수 있습니다.
4. 만약 이벤트가 최상위까지 전달되어도 해당 이벤트를 처리할 수 있는 객체를 찾지 못하면 이벤트는 무시됩니다.

![](https://hackmd.io/_uploads/r1utKefo3.png)
**hitTest**: UIView클래스에 정의된 메서드로, 터치 이벤트가 발생한 포인트에 해당하는 최상단의 뷰를 탐색하는 역할을 갖습니다. 이벤트를 처리하기 위해서는 사용자가 터치한 좌표가 어떤 뷰에 위치하는지 파악해야하는데 여기서 **hitTest**를 통해 탐색한 뷰에 대한 터치 이벤트를 처리할 수 있습니다.
![](https://hackmd.io/_uploads/BkWj6lMsh.png)
지금 보이는 코드는 UIView 클래스의 **hitTest** 메서드를 오버라이드하여 정의한 구현을 보여주고 있습니다. **hitTest** 메서드는 특정 좌표에 해당하는 뷰를 찾는 역할을 하죠. 이 코드는 뷰 계층 구조에서 터치 이벤트가 발생한 좌표에 해당하는 뷰를 찾기 위해 사용됩니다.

- `isUserInteractionEnabled`: false일 경우, 해당 뷰와 그 하위 뷰들은 사용자의 상호작용을 받을 수 없습니다.
- `isHidden`: true일 경우, 해당 뷰와 그 하위 뷰들은 화면에 표시되지 않습니다.
- `alpha`: 뷰의 불투명도를 나타냅니다. 0.0부터 1.0 사이의 값을 가지며, 0.01보다 작으면 뷰는 거의 투명한 상태가 됩니다.

만약 해당 뷰가 사용자 상호작용을 받을 수 없거나, 화면에 표시되지 않거나, 거의 투명한 상태라면, 해당 뷰는 이벤트를 처리할 수 없으므로 nil을 반환합니다.

그 다음, 이벤트가 발생한 좌표(point)가 현재 뷰의 범위에 속하는지 (self.point(inside: point, with: event))를 확인합니다. 만약 해당 좌표가 뷰의 범위에 속한다면, 뷰 계층 구조에서 하위 뷰들을 거꾸로 순회하며 터치 이벤트가 발생한 좌표에 해당하는 뷰를 찾습니다. 그리고 해당 뷰에 대해서 다시 **hitTest** 메서드를 재귀적으로 호출하여 하위 뷰들을 순회하고, 이벤트를 처리할 수 있는 뷰를 찾아서 반환합니다.

위의 코드를 요약하면, 터치 이벤트가 발생한 좌표를 기준으로 뷰 계층 구조에서 정의 조건에 따라 이벤트를 처리할 수 있는 뷰를 찾고, 해당 뷰를 반환하는 메서드입니다. 이를 통해 iOS 앱은 적절한 뷰에 대해 이벤트를 처리하고, 사용자와의 상호작용을 효과적으로 제어할 수 있습니다.

![](https://hackmd.io/_uploads/r1aqpgzin.png)
1. touches에서 첫 번째 터치 정보를 가져옵니다.
2. 좌표 정보를 가져옵니다.
3. hitTest를 통해서 해당 좌표를 포함하고 있는 뷰를 탐색합니다.
4. 만약 해당 뷰가 좌표를 갖고 있지 않다면 nil을 반환합니다.

![](https://hackmd.io/_uploads/Bye5Kgfin.png)
위와 같이 사용자가 View_B1(green)을 터치하면 상위뷰 계층부터 탐색하면서 내려오는데 View_C(grey)는 터치된 point를 갖고 있지 않기 때문에 false 반환하고 View B로 넘어와 hitTest를 수행하게 되면서 View_B1(green)를 반환합니다.

### 📋 실험 View
![](https://hackmd.io/_uploads/BJ3cYgzj3.png)

### 📋 실험 1
![](https://hackmd.io/_uploads/SJJjJ-fih.png)
사용자가 터치한 좌표를 포함하는 최상위 뷰의 `backgroundColor`가 출력되도록 코드를 수정헀습니다.

### 📋 실험 2
![](https://hackmd.io/_uploads/SyZCIWMo2.png)
GestureRecognizer 생성하고, 이벤트가 발생할 때 해당 `greenView`의 배경색을 black과 교차로 변경해주는 `tapView()`가 호출되도록 설정하였습니다. 

**✏️Tip**
- 뷰 객채의 배경 색상이 `System Color`일 경우에는 색상 변경이 불가능합니다. 따라서 해당 색을 `green`으로 수정해주었습니다.

### 실행화면
![](https://hackmd.io/_uploads/By9jwbGj3.gif)

# 13주차_1: Collection View 
## 발표자 Yetti
### Collection View?
![](https://hackmd.io/_uploads/S15rism33.png)

![CollectionView](https://hackmd.io/_uploads/SyVCqo7hn.png)
- 정렬된 데이터 항목 모음을 그리드나 다른 커스텀 레이아웃으로 표시하는 유연하고 강력한 UI요소
- 테이블 뷰처럼 data source로 데이터를 관리하고 delegate로 유저와의 상호작용을 관리
![](https://hackmd.io/_uploads/HJ_aws7nn.png)
![](https://hackmd.io/_uploads/BJ0avjmhn.png)
### Collection View 구성요소 4가지
1. 셀(Cell)
2. 보충 뷰(Supplementary View)
3. 데코레이션 뷰(Decoration View)
4. 레이아웃 객체(Layout Object)
![](https://hackmd.io/_uploads/ryvOtiQh3.png)
- Line같은 경우도 메신저 내에서 테이블뷰를 사용하다가 콜렉션뷰로 리팩토링하였다.
![CollectionViewFlowLayout](https://hackmd.io/_uploads/Bk6_qiQh3.png)
![](https://hackmd.io/_uploads/rymKqiQnh.png)
![](https://hackmd.io/_uploads/B1vKcjXn2.png)

### CollectionView 구성단계
1. FlowLayout객체 생성 후 CollectionView에 할당
2. 셀의 너비와 높이를 설정
3. 필요시 item, lines의 간견을 설정
4. header, footer사용시 사이즈 명시
5. 레이아웃의 스크롤 방향 설정. 기본값은 세로
![](https://hackmd.io/_uploads/BJKK9sQh3.png)
![](https://hackmd.io/_uploads/rJyc5om2n.png)
![](https://hackmd.io/_uploads/S1Xq5om3h.png)
### UICollectionViewDataSource
- 여러가지 메서드를 통해 UICollectionView의 데이터와 뷰를 관리
- 데이터 변경 시에는 reloadData()를 호출하여 전체 데이터를 다시 로드해야함
- 세부적인 데이터 변경을 처리하기 위해서는 추가적인 로직 및 애니메이션 코드가 필요
    - 따로 애니메이션 코드가 없다면 뚝뚝 끊겨서 보이는 단점 -> 사용자 경험(UX) 감소
- 복잡한 데이터 구조를 다루기에는 유지보수가 어려울 수 있음
### UICollectionViewDiffableDataSource
- UICollectionViewDiffableDataSource클래스를 사용하여 데이터를 관리하며, 데이터 변경을 더 쉽게 처리할 수 있음
- CollectionView와 DataSource 간에 데이터 자동으로 동기화되어 편리함
- 데이터 스냅샷(snapshot)을 사용하여 섹션과 아이템의 구성을 관리 (스냅샷: 현재 UICollectionView의 표시 상태를 뜻함)
- 데이터 변경 사항을 처리하는 데 있어서 별도의 라이브러리 없이도 빠른 성능 및 적절한 애니메이션을 자동으로 제공 -> 사용자 경험(UX) 향상
- 복잡한 데이터 구조의 경우에도 더 효율적으로 데이터 관리 및 업데이트를 할 수 있음
![](https://hackmd.io/_uploads/BySq9sQnh.png)
![](https://hackmd.io/_uploads/BJqq9o7nh.png)
![](https://hackmd.io/_uploads/r1TcqjX2n.png)
- 유튜브뮤직, 인스타그램, 앱스토어 등등 많은 곳에서 사용되고 있다.
![](https://hackmd.io/_uploads/Bygicj733.png)


# 13주차_2: URL Session
## 발표자 yyss99

![](https://hackmd.io/_uploads/HJXd60V33.png)
![](https://hackmd.io/_uploads/H1NKp0N3n.png)

![](https://hackmd.io/_uploads/HkGqpRVn3.png)
![](https://hackmd.io/_uploads/H17jp0Vnh.png)

![](https://hackmd.io/_uploads/rke2a0N23.png)
![](https://hackmd.io/_uploads/S1onpC4nn.png)

![](https://hackmd.io/_uploads/r1UTp04n2.png)
![](https://hackmd.io/_uploads/SybCpC43h.png)

![](https://hackmd.io/_uploads/SJTCpCVhh.png)
![](https://hackmd.io/_uploads/H1k4R0Vhn.png)
![](https://hackmd.io/_uploads/rJaN0043n.png)

![](https://hackmd.io/_uploads/H1jBCAN23.png)
![](https://hackmd.io/_uploads/ry6vR0V23.png)
![](https://hackmd.io/_uploads/S1puRRE22.png)
![](https://hackmd.io/_uploads/rk3tCRE2n.png)

![](https://hackmd.io/_uploads/ry2qCCVhn.png)
![](https://hackmd.io/_uploads/rkFjAANn3.png)

![](https://hackmd.io/_uploads/H1j2C0Nnn.png)
![](https://hackmd.io/_uploads/Bk6RRA433.png)
![](https://hackmd.io/_uploads/ryiJJyrh2.png)
![](https://hackmd.io/_uploads/BJPgJyS3h.png)

![](https://hackmd.io/_uploads/r1xWbJ1H3h.png)
![](https://hackmd.io/_uploads/r1Uzk1Bnn.png)








# 14주차: Cache 민트 화이팅! 헤헤 감쟈감쟈요~
## 발표자 MINT
![](https://hackmd.io/_uploads/H1whE-Whn.png)
![](https://hackmd.io/_uploads/BJahVW-2h.png)
데이터들은 어디에 저장되고 어떻게 사용될까요? 먼저 하드 디스크에 우리의 데이터들이 전부 저장됩니다. 때문에 하드 디스크는 용량이 매우 크고, 기억을 하는 것에 치중되어 있습니다. 
RAM, 메모리는 그러한 하드 디스크에서 앱을 실행하는데에 필요한 중요 데이터들을 들고 있습니다. 그러나 여전히 내용은 방대하고, RAM은 디스크보단 기억할 수 있는 양이 적고, CPU와 가까워서 빠르지만 어찌 되었건 기억을 하는 것에만 능력이 치중되어 있습니다.
CPU는 RAM에서 받은 데이터들을 이용해 앱을 실행합니다. 기억보다는 성능이 중요해, 꾸준하게 성능이 업그레이드 되어 왔습니다. 때문에 RAM과 CPU는 기억과 성능으로 각자의 목적이 다르기에 서로서로 성능차이가 점점 심하게 나게 됩니다. RAM이 CPU를 쫓아가지 못해 CPU가 RAM이 데이터를 찾아서 주기를 기다려야 하는 병목 현상이 발생한 것입니다. 
![](https://hackmd.io/_uploads/H1DyBWZ22.png)
이를 해결하기 위해 완충기의 역할을 하는 캐시 메모리가 등장했습니다. 캐시 메모리는 데이터를 가져오는 속도를 높이고 비용을 낮추기 위해 사용합니다. CPU의 일부라고도 볼 수 있게 작은 칩으로 CPU에 직접 붙어 있거나 연결되어 있습니다.
![](https://hackmd.io/_uploads/S1rZyGZh2.png)
캐시 메모리는 저장할 때 기본적인 규칙이 있습니다. 애초에 캐시라는 개념 자체가 만들어진 이유가 필요한 데이터를 빠르게 가져오기 위해서였기에 앞으로 쓰일 데이터들을 미리 예상하여 캐시에 넣어놓을 때의 규칙으로 컴퓨터의 성능을 향상시키는 역할을 합니다.
시간 지역성
공간 지역성
![](https://hackmd.io/_uploads/H1obsb-3n.png)
지금까지의 캐시 메모리는 하드웨어적으로 존재하는 캐시 메모리에 대한 내용이었습니다. 그러나 캐시는 `자주 사용하는 데이터나 값을 미리 복사해놓는 임시 공간`을 뜻하는 말로 소프트웨어적인 부분에서도 사용될 수 있습니다. 때문에 CPU, 메모리, 디스크 세 곳 어디든 임시 공간을 만들어 데이터를 저장하면 캐시를 저장할 수 있는 것입니다.
![](https://hackmd.io/_uploads/Sk9YnWW33.png)
지금까지 캐시라는 개념에 대해서 알아보았습니다. 다음은 저희가 사용하는 iOS에서 캐싱할 수 있게 해주는 두가지의 클래스이자 캐싱 방식인 NS Cache와 URL Cache입니다.
![](https://hackmd.io/_uploads/SybFKQbn2.png)
먼저 NS Cache입니다. key-value 타입으로 메모리에만 저장되는 메모리 캐싱 방식입니다. 메모리 캐싱이기에 앱을 종료하면 데이터가 날아갑니다. 
![](https://hackmd.io/_uploads/r1DaItMhn.png)
내부에 링크드 리스트처럼 연결되어 있으며 여러가지 설정을 할 수 있는데 이중 눈여겨볼 것으로 totalCostLimit가 있습니다. 캐시는 작은 공간이기에 매번 모든 데이터를 들고 있을 수 없습니다. 때문에 용량이 가득 차면 기존에 있던 것들을 삭제해서 자리를 만들어줘야 합니다. 
![](https://hackmd.io/_uploads/By5C8tz2n.png)
기본적으로 NSCache는 용량이 가득 찼을 때 자동으로 기존 데이터들을 삭제해주는데 이때 어떠한 것부터 삭제할 것인지를 판별하는 기준을 주는 것이 바로 cost입니다. 가장 작은 cost가 제일 불필요한 데이터로 판단해 삭제합니다. 때문에 totalCostLimit를 설정해주었다면 매번 캐시할 때 cost값도 같이 넘겨줘야합니다. 
![](https://hackmd.io/_uploads/BkroPYM33.png)
그러나 이것을 설정해주지 않고 만들었었기에 setObject에서 cost값을 넘겨줄 필요가 없고 이 경우 만일 용량이 가득 차면 NSCache는 캐싱된 Object의 순서대로 자동 삭제합니다.
![](https://hackmd.io/_uploads/Sy1ZuYG3h.png)
다음은 URL Cache입니다. URL Cache는 NS Cache보다 조금 더 나중에 만들어졌어요. 두 가지 큰 차이점이 있는데 NS Cache는 key - value로 data를 넘겨주었다면 URL Cache는 Request를 그대로 사용하여 확인한다는 점입니다. Request에 알맞는 response가 저장되어 있다면 그것을 반환합니다. 또한 메모리 캐싱 뿐만 아니라 디스크 캐싱도 가능해 앱을 종료해도 캐시 데이터가 날아가지 않고 설정에 따라 앱을 삭제해도 데이터를 보존할 수도 있다.
![](https://hackmd.io/_uploads/rkFdFFGh2.png)
shared가 기본구현되어 있기에 이를 사용해서 선언해주었습니다. 이때 캐싱된 데이터들은 메모리와 디스크 중 어디에 저장될까요? 공식 문서에 나와있는 기본 정책 값은 .allowed로 두 위치 모두 사용할 수 있다고 되어 있습니다. 
![](https://hackmd.io/_uploads/H1uTKtfnh.png)
그러나 내부 구현을 보면 두 위치 모두 4 MB, 20 MB로 용량이 할당되어 있습니다. 다만 response가 캐시 용량의 5% 이상인 경우는 저장되지 않고 매번 새로운 request를 보냅니다. 때문에 우리의 이미지는 용량이 1.5MB 정도 되기 때문에 메모리가 아닌 디스크에 저장될 것입니다. 기본적인 디스크 캐시 디렉토리에 하위 디렉토리를 넣어주고 싶다면 nil 대신 diskPath를 만들어 넣어주면 됩니다.
![](https://hackmd.io/_uploads/B1p-qtM3h.png)
request를 넣어 저장된 response가 있다면 해당 response를 사용하여 이미지를 불러옵니다.
![](https://hackmd.io/_uploads/B1145YG3n.png)
그러나 만일 없다면 새롭게 서버에서 받아오며 storeCachedResponse를 이용해 request와 response를 저장합니다.
![](https://hackmd.io/_uploads/HJwd9Yf23.png)
![](https://hackmd.io/_uploads/ryptcYzh2.png)
URL Request에도 캐시 정책을 설정할 수 있습니다. 기본적인 설정은 이 그림과 같습니다.
Q. 그런데 위의 실험에서 제시한 이미지 URL을 URLRequest에 담아서 요청을 할 경우, 이미지가 자동으로 캐싱이 되지 않는 것을 확인할 수 있습니다. 그 이유는 무엇일까요? -> 해결 못 함.
![](https://hackmd.io/_uploads/SJ_EsFz23.png)
지금까지 캐시에 대해서 알아보았습니다. 성능 향상을 위해 자주 사용하거나 최근 사용하거나 사용할 것 같은 데이터들을 임시 공간에 저장하는 내용이었습니다. 이처럼 목적은 조금 다르지만 일부의 정보를 저장하는 방법에는 다른 것들도 있습니다. 
![](https://hackmd.io/_uploads/H1cFjFGh2.png)
먼저 쿠키입니다. HTTP는 Stateless라고 합니다. 이는 클라이언트가 누구인지, 어떤 상태인지에 대한 정보를 포함하지 않는다는 뜻입니다. 때문에 클라이언트가 누구인지를 서버가 알 수 있게 쿠키가 등장했습니다. 한번이라도 해당 서버에 클라이언트가 접근한다면 서버는 response에 쿠키를 들려서 보냅니다. 클라이언트는 해당 쿠키를 가지고 있으면서 기본적인 정보, 설정 등이 쿠키에 저장됩니다. 추후에 서버에 클라이언트가 요청을 할 때 자동으로 쿠키도 함께 요청을 통해 서버로 넘어갑니다. 서버는 해당 쿠키를 받고 기존에 접속했던 사용자임과 동시에 어떠한 설정을 해놓았는지 알게 됩니다.
![](https://hackmd.io/_uploads/Sky2pFz22.png)
그러나 쿠키는 브라우저에서만 사용가능하기 때문에 브라우저를 사용하는 것이 아닌 네이티브 앱에서는 사용할 수 없습니다.
![](https://hackmd.io/_uploads/Hkt_g5znh.png)
참고로 저희가 사용하는 URL Session에서도 쿠키 정책을 가지고 있습니다.
![](https://hackmd.io/_uploads/SJe6atMnh.png)
이러한 쿠키의 단점을 어느 정도 해결하는 친구들이 등장했습니다. 클라이언트만 알고 있게 하는 Web Storage와 서버만 알고 있게 하는 Session입니다. Web Storage는 클라이언트에 저장만 할 뿐 매번 다시 서버에 전송하는 쿠키와 다르게 서버에는 전송하지 않습니다. 저장 용량도 5MB로 더 큽니다. 다만 독립된 storage로 각각 공유할 수가 없고 만료 기간을 설정해 줄 수도 없습니다. 
![](https://hackmd.io/_uploads/SJELAYf32.png)
앞서 쿠키는 클라이언트가 수정할 수 있는 정보입니다. 그러나 어떠한 결제를 했었고와 같은 중요한 정보들은 서버만 알고 있어야 하는 경우가 있습니다. 이런 경우를 위해 서버에서만 알고 있는 Session을 사용합니다. Session은 서버에 Session DataBase라는 저장공간을 만듭니다. 이곳에는 사용자들에 대한 중요한 정보들이 있습니다. 클라이언트가 아이디와 비번을 쳐서 로그인하면 Session DataBase에 사용자에 대한 정보가 생성됩니다. 그리고 이 세션 id를 쿠키에 들려서 클라이언트에게 보냅니다. 클라이언트는 오직 이 세션 id만 알고 있게 됩니다. 그러나 다시 서버에 요청을 할 때 클라이언트는 쿠키에 세션 id를 같이 보내게 되고 이 세션 id를 보고 서버는 세션 DB에서 클라이언트의 정보를 찾아 사용합니다.
![](https://hackmd.io/_uploads/rytSg9M2n.png)
그러나 Session은 DB가 필요한 만큼 수없이 많은 유저 정보를 만들어줄 수는 없습니다. 비용이 매우 많이 들게 되기 때문입니다. 
![](https://hackmd.io/_uploads/HyElmczh3.png)
이러한 점들을 보완한 것이 토큰입니다. 토큰은 서버가 DB를 가지고 있지 않습니다. 쿠키처럼 클라이언트가 정보를 가지고 있는데 특수한 처리가 되어 있는 것입니다. 클라이언트가 로그인해서 생겨난 유저 data를 서버는 사인을 해서 클라이언트에 보냅니다. 암호화하는 것은 아닙니다. 중간에 가로채거나 했을 때 데이터를 전부 확인할 수 있습니다. 다만 변경할 수 없습니다. 내용을 변경하게 되면 사인의 유효성을 검사했을 때 통과할 수 없습니다. 때문에 서버는 단순히 토큰의 유효성을 검사만 할 뿐 저장하고 있지는 않게 되서 비용이 적게 듭니다.
![](https://hackmd.io/_uploads/S1e3X5z33.png)
![](https://hackmd.io/_uploads/S1f3YvNhh.png)
지금까지 캐시, 쿠키, 세션, 토큰 등 다양한 저장 및 인증방식에 대해 알아보았습니다. 감사합니다~


### 참고자료
[🍎 NS Cache](https://developer.apple.com/documentation/foundation/nscache)
[📖 NS Cache](https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/Foundation/NSCache.swift)
[🍎 URL Cache](https://developer.apple.com/documentation/foundation/urlcache)
[📖 URL Cache](https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/FoundationNetworking/URLCache.swift)
[캐시메모리](https://www.techtarget.com/searchstorage/definition/cache-memory)

# 15주차:
## 발표자 Zion
# KeyChain Service 구현(add, update,read )

- KeyChain Service를 사용하여 암호화가 필요한 데이터를 저장하고 업데이트하고 읽어오는 코드를 직접작성해보고 KeyChain Service를 이해해보자.

### KeyChainService란 무엇일까?

- 중요!!! KeyChain은 App이 삭제되더라도 Data가 남아있다.
- https://medium.com/@LeeZion94/keychain-service-vs-userdefaults-1a550227d749

### KeyChain에 Data를 추가해보자

```swift
@IBAction func addNewPassword(_ sender: Any) {
    guard let password = pwTextField.text else { return }
    
    let passwordData = password.data(using: .utf8)!
    let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                kSecAttrAccount as String: keyName,
                                kSecValueData as String: passwordData]
    let status = SecItemAdd(query as CFDictionary, nil)
    
    switch status {
    case errSecSuccess:
        print("성공")
    case errSecDuplicateItem:
        updatePasswordOnKeyChain(keyName: keyName, password: passwordData)
    default:
        print("KeyChain 등록 실패")
    }
}
```

- 위의 코드는 KeyChainService를 활용하여 KeyChain에 User가 사용하는 Password를 저장(Add)하는 코드입니다. 유저가 입력한 password를 받아서 이를 data화 시킨뒤 keychain에 저장하기 위한 query로 변경하여 CFDictionary의 형태로 저장하고 저장된 결과에 대한 처리를 수행하는 코드입니다.

```swift
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                      kSecAttrAccount as String: keyName,
	                      kSecValueData as String: passwordData]
```

- kSecClass는 Keychain에 저장하려고 하는 data의 종류에 해당합니다. 해당 예시에서는 Keychain으로 User가 사용하는 Password를 저장하고 있으므로 kSecClassGenericPassword를 Value값으로 설정하였습니다.
- kSecAttr 키워드가 붙은 kSecAttrAccount는 Data를 구분짓기 위한 특성 값에 해당하는 값입니다. kSecAttr 키워드가 붙은 값들은 해당 Data를 구분 짓는 특성 값으로 활용될 수 있고 위의 예시에서는 User의 Account 값을 Data를 구분짓기 위한 특성 값으로 활용하고 있으므로 kSecAttrAccount 값을 특성 값으로 넣어주고 있습니다.
- kSecValueData는 실질적으로 저장하려고 하는 Data입니다. kSecClass에서 User의 Password를 저장하기 위해 kSecClassGenericPassword를 Value값으로 넣어줫으므로 Data 값으로는 User가 입력한 Password를 값으로 넣어줍니다.

```swift
let status = SecItemAdd(query as CFDictionary, nil)
        
switch status {
case errSecSuccess:
    print("성공")
case errSecDuplicateItem:
    updatePasswordOnKeyChain(keyName: keyName, password: passwordData)
default:
    print("")
}
```

- SecItemAdd은 지금까지 만든 query 값을 CFDictionary로 변환하여 실질적으로 Keychain으로 저장하게 만들어주는 메서드입니다. return 값으로 OSStatus 값을 가지며 저장이 올바르게 되었는지, 올바르게 되지 않았다면 어떤한 이유로 되지 않았는지를 status 값으로 return 해줍니다.
- errSecSuccess는 KeyChain에 data가 올바르게 저장이 되었다는 상태값입니다.
- errSecDuplicateItem는 해당 attr(속성) 값으로 이미 저장된 keyChain data가 존재한다는 상태값입니다. 아무런 처리를 하지 않을 수도 있지만 등록했었던 특성 값을 활용하여 기존에 존재했던 값을 새롭게 입력 받은 값으로 Update 해줄 수 있습니다.

### KeyChain에 Data를 업데이트 해보자

```swift
private func updatePasswordOnKeyChain(keyName: String, password: Data) {
    let previousQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                        kSecAttrAccount as String: keyName]
    let updateQuery: [String: Any] = [kSecValueData as String: password]
    let state = SecItemUpdate(previousQuery as CFDictionary, updateQuery as CFDictionary)
    
    switch state {
    case errSecSuccess:
        print("update 성공")
    default:
        print("실패")
    }
}
```

- KeyChain 저장시 해당 attr 속성값에 대한 데이터가 이미 존재하는 경우 이를 업데이트하는 query를 만들어서 해당 KeyChain 속성 값에 저장된 data 값을 업데이트하는 코드입니다.

```swift
let previousQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
	                            kSecAttrAccount as String: keyName]
let updateQuery: [String: Any] = [kSecValueData as String: password]
```

- previouQuery는 어떤 data를 업데이트 할 것 인지 저장된 data의 종류와 속성 값을 명시해줘서 어떤 data를 업데이트 할 것인지를 query로 나타낼 수 있습니다.
- updateQuery의 경우에는 실질적으로 update 하려는 data의 값을 나타내고 갱신시켜줍니다.

```swift
let state = SecItemUpdate(previousQuery as CFDictionary, updateQuery as CFDictionary)

switch state {
case errSecSuccess:
    print("update 성공")
default:
    print("실패")
}
```

- SecItemUpdate 메서드를 사용하여 첫번째 인자로는 data를 찾아가기 위한 query, 두번째 인자로는 갱신하려고 하는 data의 query를 넣어줍니다.
- state 값을 return 값으로 받아와서 업데이트에 성공했는지 실패했는지 알 수 있습니다.

### KeyChain에서 Data를 읽어보자

```swift
private func readPasswordOnKeyChain(_ inputPassword: String) {
    let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                kSecAttrAccount as String: keyName,
                                kSecReturnAttributes as String: true,
                                kSecReturnData as String: true]
    var item: CFTypeRef?
    let status = SecItemCopyMatching(query as CFDictionary, &item)
    
    switch status {
    case errSecSuccess:
        guard let existingItems = item as? [String: Any],
              let passwordData = existingItems[kSecValueData as String] as? Data,
              let password = String(data: passwordData, encoding: .utf8), inputPassword == password else {
            fallthrough
        }
        
        print("비밀번호 - \(password)")
    default:
        print("로그인 실패")
    }
}
```

- 해당 로직은 저장된 키체인의 값을 부여한 attr 속성 값을 통해서 읽어오는 로직입니다.

```swift
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
	                    kSecAttrAccount as String: keyName,
                kSecReturnAttributes as String: true,
	                    kSecReturnData as String: true]
```

- 먼저 읽을 data의 종류를 kSecClassGenericPassword로 명시하고 등록한 Attr 값을 통해 어떠한 속성값을 가지는 Data를 읽어올지 설정합니다.
- 또한 kSecReturnAttributes 해당 옵션 값을 통해서 data를 읽어 올 때 등록해 두었던 attr 속성 값 또한 같이 Dictionary로 받아올지 지정해 줄 수 있습니다. true로 준다면 data를 저장할 때 같이 등록했던 attr 속성 값 또한 같이 load되고 false를 부여한다면 read 하기를 원했던 data만 받아오게 됩니다.
- kSecReturnData data 값을 받아오겠다는 옵션 값에 해당합니다.

```swift
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
```

- SecItemCopyMatching 메서드를 사용해서 KeyChain에 저장된 값을 읽어오게 됩니다.
- 등록된 값을 읽어오기위해 만들어주엇던 query를 해당 메서드의 파라미터로 넣어주고, data 값을 받아오기위해 만들어 두었던 item변수의 주소값을 해당 메서드에 전달해주면서 item 값을 통해 원하는 data 및 attr 속성 값을 받아올 수 있게 됩니다.

```swift
case errSecSuccess:
  guard let existingItems = item as? [String: Any],
        let passwordData = existingItems[kSecValueData as String] as? Data,
        let password = String(data: passwordData, encoding: .utf8), inputPassword == password else {
      fallthrough
  }
```

- item을 올바르게 Read료 했다면 부여했던 옵션 값을 통해 data와 attr 속성 값들을 Dictionary로 전달받아오게 됩니다.
- kSecReturnAttributes 옵션 값을 true로 부여했으므로 Dictionary로 형변환 작업이 필요합니다. false로 부여했다면 attr 속성 값이 같이 read 되지 않기 때문에 Dictionary로 형변환을 할 필요가 없이 Data만 받게 됩니다.
- Data에 해당하는 Key값을 사용하여 data를 받고 이를 다시 encoding한다면 저장되어 있었던 password 값을 올바르게 읽어올 수 있습니다.

---
# 16주차
## 발표자 - redmango
# CoreData

![](https://hackmd.io/_uploads/HJ4hLOzyp.jpg)

---
![](https://hackmd.io/_uploads/SkN28OMkT.jpg)

---
![](https://hackmd.io/_uploads/HJlV2LuzJT.jpg)

- 객체 관리 그래프란: 객체간의 관계를 그래프로 나타낸 것
---
![](https://hackmd.io/_uploads/SylNhLdGJa.jpg)

- DB언어를 배울 필요가 없다는 점에서 매우 편리
---

![](https://hackmd.io/_uploads/r1lEnUdfyp.jpg)

---
![](https://hackmd.io/_uploads/HybV38dzya.jpg)

- 스레드 세이프를 고려하지 않았다는걸 주의
- mainQueue는 아무래도 UI때문인것으로 추측(데이터 처리때문에 UI먹통 될수도..)
---
![](https://hackmd.io/_uploads/HJNhL_zJp.jpg)

---
![](https://hackmd.io/_uploads/HJWV38dfJp.jpg)

- 마이그레이션을 하지 않을 경우 데이터가 날아가는 경우가 발생 할 수 있다.
---
![](https://hackmd.io/_uploads/ryEnLuGka.jpg)
---
![](https://hackmd.io/_uploads/ByeEnI_Gkp.jpg)

---
# 17주차:
## 발표자: Jusbug
# UIView Animations

![Main Page](https://hackmd.io/_uploads/rJtTIjNka.png)

![Abstract](https://hackmd.io/_uploads/HJ0pIjVJ6.png)

![Animation](https://hackmd.io/_uploads/Hyg0UiEkp.png)
화면 요소의 움직임과 변화를 부드럽게 처리하는 데 사용되는 강력한 도구입니다. UIView 애니메이션은 시각적 효과를 생성하거나 사용자 인터페이스 요소의 상태를 변경하는 데 사용됩니다. UIView 애니메이션은 기본적으로 UIView 클래스의 일부 메서드와 클로저를 사용하여 작성됩니다.

![Animate()-1](https://hackmd.io/_uploads/H11QvsEy6.png)

![Animate()-2](https://hackmd.io/_uploads/HJDC8sVyT.png)

![AnimateKeyframes-1](https://hackmd.io/_uploads/S15A8j4kT.png)
다단계 애니메이션을 만들고 실행하는 데 사용되는 강력한 기능을 제공하는 메드입니다. 이를 사용하면 여러 단계로 구성된 애니메이션을 구현하고 각 단계에서 다양한 애니메이션 효과를 적용할 수 있습니다.
- `animateKeyframes()` 메서드는 뷰의 키프레임 기반 애니메이션을 생성하는데 사용됩니다.
- 여러 개의 키프레임을 정의하고, 각 키프레임에서 뷰의 속성을 변경하며, 각 키프레임 사이의 전환을 제어합니다.
- iOS 7 이상에서 사용 가능하며, 더 복잡한 애니메이션 시나리오에 유용합니다.

![AnimateKeyframes-2](https://hackmd.io/_uploads/BkCC8iNk6.png)
주요 목적은 여러 단계로 구성된 복잡한 애니메이션을 만드는 것입니다. 각 단계에서 addKeyframe를 사용하여 시간에 따른 속성의 변화를 정의하고, 전체 지속 시간 내에서 다양한 애니메이션 효과를 적용할 수 있습니다.

![CGAffineTransform-1](https://hackmd.io/_uploads/BkZJwiNyT.png)
Core Graphics 프레임워크에서 제공하는 변환 매트릭스를 나타내는 구조체입니다. 이 매트릭스는 2D 공간에서 객체의 변환(이동, 회전, 크기, 기울임 등)을 표현하는 데 사용됩니다.

![CGAffineTransform-2](https://hackmd.io/_uploads/r1SkPi4yT.png)
translation(위치), scaling(크기), rotation(회전)의 요소를 변환하여 애니메이션을 다룰 수 있습니다. 그리고 Concatenation(합병 애니메이션)을 이용하여 여러 애니메이션 변환을 하나로 결합하여 동시에 실행하게 만들 수도 있어 더 복잡한 애니메이션 효과를 구현할 수 있습니다.

![frame vs transform](https://hackmd.io/_uploads/rkF1DjVka.png)
애니메이션에서 `frame` 속성을 변경하는 것은 실제 해당 뷰의 크기와 위치 등을 직접 변경하는 것입니다. 그렇게 될 경우, 다른 하위 뷰와 컨텐츠에 영향을 미칠 수 있는 리스크가 존재합니다. 반면, `transform`을 변환하는 것은 실제 위치와 크기가 변경되는 것이 아니라 렌더링에만 영향을 주기 때문에 다른 뷰와의 간섭이 일어날 우려가 없어 여러 애니메이션을 구현할 때에는 `transform`을 변환하는 것이 더 안전합니다.

![identity & removeAnimation](https://hackmd.io/_uploads/S1jJDo4kp.png)
`identity`는 애니메이션 동작 이전 뷰의 정보를 갖고 있는 속성으로 이를 이용하여 애니메이션 동작 후 초기 뷰로 되돌릴 때 사용할 수 있습니다. 단 위치, 크기, 회전에 대한 정보만 갖고 있기 때문에 alpha, image와 같은 애니메이션 요소들은 `removeAnimation()`메서드를 이용해서 애니메이션을 직접 정지시키는 방법으로 제어할 수 있습니다.

![Quiz](https://hackmd.io/_uploads/BkR1DoVJa.png)
1. Completion Handler
2. 여러 단계의 애니메이션을 구현하기에 용이하다.
3. 위치, 크기, 회전
4. frame, transform
5. alpha, image...

![Last Page](https://hackmd.io/_uploads/BkexwoV1a.png)

### 👨‍🔬 실험 목표
UIView Animation을 활용할 수 있다.

### 👨‍🔬 실험 1: 이모티콘 만들기 - 에러를 만난 야곰
```swift
@IBAction func tapErrorYagom(_ sender: Any) {
    UIView.animateKeyframes(withDuration: 0, delay: 0, options: [.repeat, .autoreverse], animations: {
        
        UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1/4) {
            self.YagomImageView.transform = CGAffineTransform(translationX: 10, y: 0)
        }
        UIView.addKeyframe(withRelativeStartTime: 1/4, relativeDuration: 3/4) {
            self.YagomImageView.transform = CGAffineTransform(translationX: -20, y: 0)
        }
        UIView.addKeyframe(withRelativeStartTime: 3/4, relativeDuration: 1/4) {
            self.YagomImageView.transform = CGAffineTransform(translationX: +10, y: 0)
        }
    })
}
```
### 👨‍🔬 실험 2: 이모티콘 만들기 - 새 맥북을 구매한 야곰
```swift
@IBAction func tapNewMacbookYagom(_ sender: Any) {
    UIView.animateKeyframes(withDuration: 0.5, delay: 0, options: [.repeat, .autoreverse], animations: {
        
        UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1/2) {
            self.YagomImageView.transform = CGAffineTransform(scaleX: 1, y: 2) // 배율 값
        }
        UIView.addKeyframe(withRelativeStartTime: 1/2, relativeDuration: 1/2) {
            self.YagomImageView.transform = CGAffineTransform(scaleX: 2, y: 1)
        }
    })
}
```
### 👨‍🔬 실험 3 (선택): Custom 이모티콘 만들기
```swift
@IBAction func tapcustomYagom(_ sender: Any) {
    UIView.animateKeyframes(withDuration: 0.5, delay: 0, options: [.repeat, .autoreverse], animations: {
        
        UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1/4) {
            //self.YagomImageView.image = .checkmark // 이미지 변경
            //self.YagomImageView.image = .add // 이미지 변경
            self.YagomImageView.alpha = 0.5 // 0.0 ~ 1.0
            self.YagomImageView.transform = CGAffineTransform(rotationAngle: 45.0)
            self.YagomImageView.transform = CGAffineTransform(translationX: 20, y: -20)
        }
        UIView.addKeyframe(withRelativeStartTime: 1/4, relativeDuration: 3/4) {
            self.YagomImageView.alpha = 0.2 // 0.0 ~ 1.0
            self.YagomImageView.transform = CGAffineTransform(rotationAngle: -90.0)
        }
        UIView.addKeyframe(withRelativeStartTime: 3/4, relativeDuration: 1/4) {
            self.YagomImageView.alpha = 1.0 // 0.0 ~ 1.0
            self.YagomImageView.transform = CGAffineTransform(rotationAngle: 45.0)
        }
    })
}
```

### 애니메이션 정지 버튼
```swift=
@IBAction func tapBackToNormal(_ sender: Any) {
    self.YagomImageView.layer.removeAllAnimations() // 애니메이션 중지
    
    UIView.animate(withDuration: 0.5, delay: 0) {
        self.YagomImageView.transform = .identity // 뷰를 원래대로 복원
    }
}
```
---
# 18주차:
## 발표자: Yetti
# SWiftUI
![SwiftUI](https://hackmd.io/_uploads/HkU82HV1a.png)
Better apps. Less Code.라는 표어가 SwiftUI를 잘 알려주는 것 같습니다.
![Contents](https://hackmd.io/_uploads/Hk582SEyp.png)
![SwiftUI 소개](https://hackmd.io/_uploads/ry6U3r4kp.png)
선언형 UI인것과 멀티 플랫폼인 점이 눈에 띕니다
![코드길이비교](https://hackmd.io/_uploads/H1BDnBE1a.png)
일반적으로 같은 UI을 구현했을 떄 UIKit보다 확연히 적은 코드를 보여줍니다.
![UIKit과 비교](https://hackmd.io/_uploads/SJvv2rNya.png)
여러차이점이 있겠지만 4가지정도로 구분해 보았습니다.
![명령형과 선언형 UI 차이](https://hackmd.io/_uploads/HyYw3HVk6.png)
![명령형 UI](https://hackmd.io/_uploads/HynvhH41p.png)
명령형은 UI를 구현하기 위한 행위를 하나하나 알려줘야하는 것 같습니다.
![선언형 UI](https://hackmd.io/_uploads/ry7T2SEk6.png)
최종적으로 무엇을 할지 알려주면 그 목표에 도달하기까지의 동작은 알아서 해주는 느낌?이랄까
![이벤트, 데이터 중심](https://hackmd.io/_uploads/S1w6hrEka.png)
![클래스 기반, 구조체 프로토콜 기반](https://hackmd.io/_uploads/r1KTnrNJT.png)
![layout 3단계](https://hackmd.io/_uploads/ryip3S4J6.png)
![1단계](https://hackmd.io/_uploads/r1T6hSN1p.png)
![2단계](https://hackmd.io/_uploads/rkJ0hHN16.png)
![3단계](https://hackmd.io/_uploads/SJfR2B41a.png)
![some](https://hackmd.io/_uploads/SJ_X6r4JT.png)
![property wrapper1](https://hackmd.io/_uploads/r1aQpB4J6.png)
![property wrapper2](https://hackmd.io/_uploads/SyRmpSVJT.png)
![observedObject, StateObject비교](https://hackmd.io/_uploads/HyZEaHNy6.png)
![그취뭐](https://hackmd.io/_uploads/H1EVpH41a.png)
![주관적 의견](https://hackmd.io/_uploads/r1IN6SN1a.png)


