---
# System prepended metadata

title: "일기장\U0001F4D2"

---


# 일기장 📔
2023-08-28 ~ 2023-09-15

## Ground Rules
### 규칙
- 월/목
    - 오전은 각자 학습활동 예습
    - 학습활동 14:00 ~ 17:00
    - 저녁식사 17:00 ~ 18:00
    - 스크럼 18:00 
    - 팀프로젝트 18:05 ~ 20:00
- 화/수/금
    - 스크럼 이후 프로젝트 진행
    - 스크럼 10:00 
    - 팀프로젝트 10:05 ~ 12:00
    - 점심식사 12:00 ~ 13:00
    - 팀프로젝트 13:00 ~ 18:00
- 주말은 특별한 일 없으면 자유시간

### 일정상 특이사항
- Max
    - 9/1(금) 오전 11시부터 가능! (문제출제 관련으로 한시간만 비우겠습니다ㅠ)
    - 9/8(금) 금요일 오후에 피치못할 사정이 있습니다ㅠㅠ 아침 리드미 정리는 가능할거같애용
    - 9/14(목) 저녁에 약속이 있습니다!(학습활동 끝나고 다시 모이기 어려울 것 같아요ㅠ)
- Hemg
    - 9.4일 월요일 저녁에는 약속이 있습니다.

### 스크럼(5분 내외)
- 월/목 18:00
- 화/수/금 10:00

### 프로젝트 규칙
- 네이밍 준수하기(가이드 라인)
- 브랜치 - 스텝별로
- 커밋 메시지 - 카르마스타일
- 리드미작성과 PR정리를 위해 코드에 대한 기록 그때 그때 하기
- 중간중간 리드미 작성

## 일일 스크럼
### 🙌 08/28(월)
- 오늘의 컨디션
    - Max🤖: 쉬고 왔더니 머리가 멍하네요... 그치만 힘내보겠습니당ㅎㅎㅎ
    - Hemg🐻: 아주 좋습니다. 비가 와서 푹자고 일어났네요
- 특이사항
    - Max🤖: 없습니다!
    - Hemg🐻: 오늘은 딱히 없습니다! 
- 오늘 할 일 
    - [x] 활동학습 예습
    - [x] STEP 1 파악
    
### 🙌 08/29(화)
- 오늘의 컨디션
    - Max🤖: 컨디션 나쁘지 않습니다! 근데 비가 와서 좀 늘어지네요ㅠ
    - Hemg🐻: 평범한거 같습니다 ㅋ
- 특이사항
    - Max🤖: 없습니당
    - Hemg🐻: 방학 이후 주라서 그런가 정말 집중이 잘안되긴하네요ㅠ
- 오늘 할 일 
    - [x] STEP1 작업
    - [x] STEP1에 필요한 문서 확인

### 🙌 08/30(수)
- 오늘의 컨디션
    - Max🤖: 컨디션 괜찮습니다
    - Hemg🐻: 어제 커피를 늦게 마셔서 그런가 잠을 좀 못잤습니다 ㅋ
- 특이사항
    - Max🤖: 없습니다!
    - Hemg🐻: 따로 없습니다!
- 오늘 할 일 
    - [x] STEP1 PR 진행
    - [ ] (리뷰 오면) STEP1 수정 진행
    - [ ] CoreData 공부

### 🙌 08/31(목)
- 오늘의 컨디션
    - Max🤖: 컨디션 괜찮습니당!
    - Hemg🐻: 푹자고 일어나서 좋습니다. 
- 특이사항
    - Max🤖: 없습니다
    - Hemg🐻: 없습니다
- 오늘 할 일 
    - [x] 활동학습 예습
    - [ ] CoreData 공부

### 🙌 09/01(금)
- 오늘의 컨디션
    - Max🤖: 컨디션은 좋습니다 날씨도 좋네용
    - Hemg🐻: 헤롱헤롱 합니다
- 특이사항
    - Max🤖: 없습니다~
    - Hemg🐻: 없습니다. coreData Manual/None // class definition
- 오늘 할 일 
    - [x] STEP 1 추가 수정사항 진행
    - [x] STEP 2 : CoreData 연결

### 🙌 09/02(월)
- 오늘의 컨디션
    - Max🤖: 컨디션 좋습니당
    - Hemg🐻: 좋습니다!
- 특이사항
    - Max🤖: 없습니다
    - Hemg🐻: 없습니다.
- 오늘 할 일 
    - [ ] STEP 2 진행
    - [ ] .

### 🙌 09/03(화)
- 오늘의 컨디션
    - Max🤖: 괜찮습니당
    - Hemg🐻: 좋습니다.
- 특이사항
    - Max🤖: 없습니다!
    - Hemg🐻: 없습니다.
- 오늘 할 일 
    - [x] STEP 2 진행

### 🙌 09/04(수)
- 오늘의 컨디션
    - Max🤖: 신납니당
    - Hemg🐻: 너무 좋습니다.
- 특이사항
    - Max🤖: 모각코
    - Hemg🐻: 합정으로 가요. ~~지각..~~
- 오늘 할 일 
    - [x] STEP 2 PR 진행
    - [x] README 작성


---
# PR

## STEP 1
> 일기장 [STEP 1] Hamg, maxhyunm

안녕하세요 그린!(@GREENOVER)
이번 리뷰를 받게 된 Max, Hemg 입니다.
3주간 잘 부탁드립니다. 감사합니다!🙇‍♂️

일기장 첫 PR 진행했습니다.
시간 되실때 확인 부탁드립니다!

## 고민했던 점
### 🔹 키보드 높이에 따른 텍스트뷰 크기 변경
키보드가 화면에 나타나고 사라짐에 따라 텍스트뷰 영역의 크기가 함께 바뀌도록 작업해야 했는데, 아래 두 가지 방법 중에서 고민이 있었습니다.
- `NotificationCenter`를 활용하여 키보드가 활성화될 때마다 알림을 받아 `TextView`의 `contentInset`을 변경하는 방법
- `TextView`의 `bottomAnchor`를 `keyboardLayoutGuide`의 `topAnchor`와 맞추는 방법
    - 예시)
    ```swift
        textView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor)
    ```

두 번째 방법은 코드 한 줄만 추가되면 되기 때문에 훨씬 심플하지만 `iOS 15` 버전 이상에서만 활용이 가능하여, 해당 부분은 일단 전자로 진행하였습니다.

### 🔹 테이블 뷰 셀 contentView
테이블뷰 셀에서 `label` 내용들을 `cell` 자체에 `addSubview`로 추가할지, 아니면 `contentView`에 추가할지 고민하였습니다.
`cell` 자체에 `addSubview`를 하면 `safeAreaLayoutGuide`를 기준으로 레이아웃을 잡을 수 있고, `contentView`에 추가하면 `contentView`를 기준으로 레이아웃을 잡게 됩니다. 저희는 `safeArea`를 활용하고 싶어서 처음에는 `cell`에 직접 추가하는 방식을 고려했지만 `contentView`의 공식문서를 확인해 보니 아래와 같이 정리되어 있었습니다.
> 셀의 사용자 지정 컨텐츠를 추가할 수 있는 메인 뷰.
> 셀을 구성할 때, 셀에 들어가야 하는 컨텐츠를 이 뷰에 추가하면 셀은 자동으로 배경에 들어가는 다른 뷰들보다 앞쪽에 이 컨텐츠들을 보여줍니다.

`label`들은 가장 앞쪽에 보여져야 하는 내용이므로, `contentView`에 추가하는 쪽으로 수정하여 진행하였습니다.
```swift
contentView.addSubview(titleLabel)
titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor)
```

### 🔹 반복적인 날짜 포매팅 처리
요구사항을 확인했을 때, 일기 리스트 화면과 새로운 일기를 생성하는 화면에서 모두 아래와 같이 날짜 포매팅을 사용해야 하는 것을 알 수 있었습니다.
```swift
 private let formatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.locale = Locale(identifier: "ko_kr")
    formatter.dateFormat = "yyyy년MM월dd일"
    return formatter
}()
```
동일한 코드가 두 개의 `ViewController`에서 반복되는 것을 막기 위해 해당 코드를 분리하고자 했습니다.
이때 단순히 똑같은 형태의 포매터를 싱글톤 방식으로 만들지, 아니면 다양한 포맷으로 활용할 수 있도록 `extension`으로 메서드를 만들지 고민하였지만 확장성을 고려하여 후자를 선택하였습니다.

```swift
extension DateFormatter {
    func formatToString(from date: Date, with format: String) -> String {
        self.dateFormat = format
        
        return self.string(from: date)
    }
}

DateFormatter().formatToString(from: entity.createdAt, with: "YYYY년 MM월 dd일")
```


## 조언이 필요한 점
### 키보드 활용시 시뮬레이터 에러 발생
- command + k 로 진행하여 키보드를 불러 왔을때 아래와 같은 문구가 나타나고 있습니다. 시뮬레이터 에러로 확인되는데, 혹시 이부분에 대해서는 따로 더 해결 할 수 있는 방법이 있을까요?
```
 Diary[88951:12721534] [HardwareKeyboard] -[UIApplication getKeyboardDevicePropertiesForSenderID:shouldUpdate:usingSyntheticEvent:], failed to fetch device property for senderID (778835616971358209) use primary keyboard info instead.
```

---

# PR

## STEP 2
> 일기장 [STEP 2] Hamg, maxhyunm

안녕하세요 그린!(@GREENOVER)
일기장 STEP 2 PR 올려드립니다.
코어 데이터 처리로 많이 헤맸던 것 같습니다😂
이번 스텝도 리뷰 잘 부탁드립니다!🙇‍

## 고민했던 점
### 🔹 Core Data 처리용 로직 위치
처음에는 `SceneDelegate` 또는 `AppDelegate`에 `PersistentContainer`와 `saveContext` 메서드를 두려고 했습니다. 하지만 이 두 가지 내용을 포함하여, CRUD 작업 전반과 관련된 내용을 별개의 타입으로 분리하여 `CoreDataManager`를 만들면 좀 더 깔끔히 관리할 수 있을 것 같다는 생각이 들었습니다. 이에 따라 아래와 같이 새로운 타입을 생성하였습니다.
Manager 타입은 하나의 인스턴스를 앱 전체가 공통적으로 사용하므로 싱글톤 처리를 하였습니다.
```swift
class CoreDataManager {
    static let shared = CoreDataManager()
    private init() {}
        
    func fetchDiary() throws -> [Diary] {
        ...
    }
    
    func createDiary() -> Diary {
        ...
    }
    
    func deleteDiary(_ diary: Diary?) {
        ...
    }
    
    // MARK: - Core Data stack
    lazy var persistentContainer: NSPersistentContainer = {
        ...
    }()

    func saveContext () {
        ...
    }
}
```

### 🔹 일기 생성/수정 처리 방식
일기 신규 생성과 기존 일기 수정 작업은 모두 `CreateDiaryViewController`를 통해 구현됩니다.
이때 신규/수정 여부를 구분하기 위해, 수정 작업인 경우에는 `init`을 통해 수정할 일기 인스턴스를 전달해줄 수 있도록 새로운 생성자를 추가하였습니다. 또한 신규 작업인 경우에는 `init`에서 uuid, 날짜만 입력된 새로운 `diary` 객체를 생성하도록 하였습니다.
```swift
 init() {
     self.diary = CoreDataManager.shared.createDiary()
     self.isNew = true
     super.init(nibName: nil, bundle: nil)
 }
 
 init(_ diary: Diary) {
     self.diary = diary
     self.isNew = false
     super.init(nibName: nil, bundle: nil)
 }
```

### 🔹 일기 자동저장 구현 방식
요구사항에 따른 자동저장 시기는 다음과 같았습니다.
- 사용자가 입력을 멈추는 경우(키보드가 사라지는 경우)
- 앱이 백그라운드로 진입하는 경우
- 이전 화면(리스트 화면)으로 이동하는 경우

이에 따라 `UITextViewDelegate`의 `textViewDidEndEditing` 메서드를 활용하여 다음과 같이 구현하였습니다.
```swift
func textViewDidEndEditing(_ textView: UITextView) {
    let contents = textView.text.split(separator: "\n")
    guard !contents.isEmpty else { return }

    CoreDataManager.shared.saveContext()
}
```
위의 작업을 통해 키보드 비활성화와 화면 뒤로가기 작업시에는 자동으로 내용을 title / body 로 분리하여 저장할 수 있었습니다.
하지만 앱이 백그라운드에 진입하는 경우는 `SceneDelegate`의 `sceneDidEnterBackground` 메서드를 통해 작업하는 것으로 알고 있어, `CreateDiaryViewController` 내부의 textView 내용에 접근하는 방법을 찾기가 어려웠습니다. 싱글톤으로 만들어진 `CoreDataManager`의 `saveContext`메서드를 통해 해당 일기 객체를 코어데이터에 저장할 수는 있었지만, 현재 작성된 일기 내용을 title과 body로 나누어 업데이트하는 작업이 불가능했습니다. 
이를 해결하기 위해 처음에는 `CreateViewController`의 `viewWillDisappear` 메서드에서 저장처리를 진행할 수 있도록 구현해 보았습니다.
```swift
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    saveDiary()
}
```
하지만 이렇게 하니 일기 삭제 처리를 한 뒤 뷰컨트롤러를 pop할 때에도 저장처리를 거치게 되어, 오류를 피하려다 보니 오히려 로직이 복잡해졌습니다.
때문에 저희는 `TextView`가 수정될 때마다 뷰컨트롤러가 가지고 있는 일기 객체의 내용을 바꿔주고, 저장이 필요한 순간에 `saveContext` 처리만 진행할 수 있도록 아래와 같이 구현하였습니다.
```swift
func textViewDidChange(_ textView: UITextView) {
    let contents = textView.text.split(separator: "\n")
    guard !contents.isEmpty,
          let title = contents.first else { return }

    let body = contents.dropFirst().joined(separator: "\n")

    diary.title = "\(title)"
    diary.body = body
}
```

### 🔹 alert과 swipe 코드 분리
반복적인 alertController 생성 작업, 그리고 swipe 코드를 뷰컨트롤러에서 조금이라도 분리하면 좋을 것 같아 protocol과 extension을 활용하였습니다.
- `Alert` 분리전 코드
```swift
private func showDeleteConfirmation() {
    let alertController = UIAlertController(title: "진짜요?", message: "정말로 삭제하시겠습니까?", preferredStyle: .alert)
    
    let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
    alertController.addAction(cancelAction)
    
    let deleteAction = UIAlertAction(title: "삭제", style: .destructive) { [weak self] _ in
        guard let self else { return }
        self.performDelete()
    }
    alertController.addAction(deleteAction)
    
    present(alertController, animated: true, completion: nil)
}
```

- 분리 후 코드
```swift
protocol AlertDisplayable {
    func showAlert(title: String?, message: String?, actions: [UIAlertAction])
}

extension AlertDisplayable where Self: UIViewController {
    func showAlert(title: String?, message: String?, actions: [UIAlertAction]) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        actions.forEach { alertController.addAction($0) }
        present(alertController, animated: true, completion: nil)
    }
}
```
- 구현부 코드
```swift
private func showDeleteConfirmation() {
        let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        let deleteAction = UIAlertAction(title: "삭제", style: .destructive) { [weak self] _ in
            self?.performDelete()
        }
        
        showAlert(title: "진짜요?", message: "정말로 삭제하시겠습니까?", actions: [cancelAction, deleteAction])
    }
```


### 🔹 일기 리스트 새로고침 시점
처음에는 `DiaryListViewController`와 `CreateDiaryViewController`를 `delegate`로 연결하여 생성화면이 pop될 때 리스트 화면의 `readCoreData` 메서드를 호출하도록 구현하였습니다. 하지만 이보다는 뷰의 생명주기를 활용하는 편이 더 깔끔할 것 같다는 생각이 들었습니다. 이에 따라 `viewWillAppear` 메서드에서 아래와 같이 처리해주었습니다.
```swift
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    readCoreData()
}
```

## 조언을 얻고 싶은 점
### 빈 화면에서 뒤로가기를 눌렀을 때의 처리방법
요구사항에 따르면, 일기장의 + 버튼을 눌러 새 일기장을 생성했을 경우 키보드가 자동으로 올라오게 됩니다. 그리고 뒤로가기 처리를 하거나 백그라운드 상태로 변환되는 등 현재 화면에서 벗어나면 작업중인 내용이 자동으로 저장이 되어야 합니다. 그렇다면 + 버튼을 눌렀다가 아무런 내용을 작성하지 않은 채 뒤로가기를 눌러도 제목/내용이 없는 빈 일기가 생성되는 것이 맞을까요? 저희는 이 부분이 조금 어색한 것 같아서 아예 비어있는 내용은 context에서 save처리를 하지 않도록 만들어두었습니다. 그린의 의견이 궁금합니다😭

---


### STEP2 할일
1. Foundation이 꼭 필요한가? 아니라면 왜일까?
    - CoreData에서 자체적으로 Foundation을 import하고 있기 때문에 지워도 상관없습니다! 이 부분 삭제처리했습니다.

2. NSManagedObject가 어떤 역할을 하는가?
    - NSManagedObject는 CoreData 모델을 사용하게 될 경우 상속을 진행해야하는 기본 클래스입니다. 엔티티명, 어트리뷰트명, 어트리뷰트 간의 관계 등을 포함한 (NSObject 객체들의)메타정보를 갖고 있습니다.
    - [NSManagedObject](https://developer.apple.com/documentation/coredata/nsmanagedobject)

3. 개행 이유
    - Diary에 코드를 추가보려다가 삭제하면서 실수로 남긴 것 같습니다. 개행 삭제했습니다!
    - 
4. Diary: Identifiable 은 필요한 내용인가? 없으면 어떤 문제가 생기나?
    - Identifiable은 해당 프로토콜을 채택한 타입에서 Hashable 값으로 id 프로퍼티를 만들도록 강제합니다. 이를 통해 모든 객체를 id로 구분할 수 있도록 하기 위함입니다. 만약 id로 구분할 수 있는 값이 없을 경우 데이터를 특정할 수 없어 문제가 생기는 것으로 알고 있습니다. 저희의 Diary는 uuid로 id 프로퍼티를 갖고 있기에, 기능상 문제는 발생하지 않는 것 같습니다. 하지만 Identifiable의 성질을 명시적으로 보여주는 것이 좋은 것 같아서 삭제하지 않고 그대로 두었습니다!
    
5. ShareDiary 프로토콜 네이밍: ShareDisplayable (Alert 파일명도 수정)
    - 두 가지 네이밍을 비슷하게 맞추는 편이 좋았을 것 같네요...!! 수정 진행하였습니다!

6. shareText 가독성 높이는 방법
    - 그린께서 말씀하신 수정점이 어딜까에 대하여 많은 고민을 하였습니다 😢 처음에는 share할 항목을 따로 정리해두어야 하나? 라는 생각도 했는데, 고민 끝에 '가독성'에 초점을 맞추는 것으로 결론을 내렸습니다.(혹시 의도하신 바가 이게 아니시라면 추가로 말씀 부탁드려요!!) 이에 따라 기존에는 한 줄로 작성되었던 부분을 여러 줄 String 형식으로 변경하여 작성하였습니다.
    ```swift
    let shareText = "제목: \(title)\n작성일자: \(date)\n내용: \(body)"
    ```
    ```swift
     let shareText = """
                     제목: \(title)
                     작성일자: \(date)
                     내용: \(body)
                     """
    ```

7. preferredStyle 파라미터로 보내기
    - 코드 추가 후 변경 완료 했습니다 감사합니다!!

8. Create, delete 에러 발생하지 않는지?
    - create 시에는 단순히 객체를 생성만 하는 것이라 오류가 나지 않지만, delete 시에는 오류가 날 수 있을 것 같아서 에러타입을 추가하였습니다.

9. Fatalerror 사용 이유 -> 그대로 긁어와서임 ㅠ
    - persistentContainer의 경우 싱글톤 객체 내부 프로퍼티 형식으로 되어 있어, 추가 오류처리가 어렵다고 판단되어 fatalError를 사용하였습니다. 해당 부분에서 오류가 난다면 저장소 자체를 불러오지 못한 케이스라서 정상적으로 앱이 작동하지 못할 것이라고 생각되었습니다. 반면 saveContext에 있던 fatalError의 경우는 자체적으로 save 실패 오류를 만들어 뷰컨트롤러로 throw해주면 앱을 강제종료시키는 대신 얼럿 처리로 대체할 수 있을 것 같아서 수정 진행했습니다!

10. 개행 유지하기 (하나만 components로 바꾸기 & cell에 보내기 전에 처리 추가)
    - 말씀 듣고 보니 의도적인 개행은 유지하는 것이 좋을 것 같아서 수정 진행하였습니다!

11. 네임스페이스 만들기
    - 네임스페이스를 쓰면 관리의 용이성은 올라가지만 코드의 가독성이 오히려 떨어지는 단점도 함께 오는 것 같아서, 처음에는 네임스페이스 없이 작업을 했었습니다...! 그린의 의견을 듣고 고민을 해보다가 일단 매직 리터럴들을 네임스페이스로 구분하여 옮겨보았습니다. (다만 오류와 관련된 alert 내용을 모두 오류 타입으로 옮겼더니 네임스페이스의 구분이 다소 애매해진 것 같네요😭) 

12. 강종 테스트 -> 어떻게 할지?
    - SceneDelegate의 applicationWillTerminate 메서드에 저장 내용을 추가해 보았는데, 시뮬레이터 자체를 강제종료시키는 식으로 테스트를 진행했더니 앱만 강제종료될 경우에 대한 테스트가 제대로 이루어지지 않는 것 같습니다😭 확인이 어려워서 해당 내용은 다시 원복해 두었습니다.
    ```swift
    func applicationWillTerminate(_ application: UIApplication) {
        CoreDataManager.shared.saveContext()
    }
    ```
13. createVC 네이밍: DiaryDetailViewContoller
    - 두 가지 다른 작업을 진행하는 뷰컨트롤러의 네이밍을 정하기가 쉽지 않아서 고민이 있었습니다😭 결과적으로 List라는 이름에 대비되는 Detail이라는 명칭을 활용하였습니다.


---
# PR

## STEP 3
> 일기장 [STEP 3] Hamg, maxhyunm

안녕하세요 그린!(@GREENOVER)
일기장 STEP 3 PR 올려드립니다.
이번 스텝도 잘 부탁드립니다🙇‍

## 고민했던 점
### 🔹 CoreLocation 활용
#### CoreLocation을 통해 정보를 받아오는 위치
실질적으로 Location 정보가 필요한 것은 DiaryDetailViewController에서 날씨 API를 호출할 때입니다. 그렇지만 DiaryDetailViewController에서 위치정보 활용 동의를 받고 업데이트를 진행하는 것보다는 앱의 첫 화면에서 해당 내용을 진행하는 편이 더 자연스러울 것 같다고 판단되었습니다. 이에 따라 위치 정보 업데이트 자체는 첫 화면인 DiaryListViewController에서 진행하고, DiaryDetailViewController에서는 API 통신에 필요한 위도, 경도 데이터만 넘겨받을 수 있도록 구현하였습니다.
```swift
let createDiaryView = DiaryDetailViewController(latitude: self.latitude, longitude: self.longitude)
self.navigationController?.pushViewController(createDiaryView, animated: true)
```
또한 위치정보 활용에 동의하지 않은 경우에도 일기 자체는 작성 가능하도록 구현하기 위해(날씨 이모티콘만 제외) 위도, 경도 데이터는 nil로도 전달될 수 있도록 하였습니다.

```swift
init(latitude: Double?, longitude: Double?) {
    self.diary = CoreDataManager.shared.createDiary()
    self.isNew = true
    self.latitude = latitude
    self.longitude = longitude

    super.init(nibName: nil, bundle: nil)
    fetchWeather()
}
```

#### 시뮬레이터의 위치 정보 설정
시뮬레이터로 CoreLocation 기능을 테스트하면 시뮬레이터 자체에 설정된 Location 정보에 따라 위치를 표시하게 됩니다. 따라서 이 설정이 None으로 되어있을 경우에는 위치가 정상적으로 불러와지지 않습니다. 이 사실을 간과하여 테스트 과정에서 많은 시행착오를 거쳤습니다.
결과적으로는 Custom Location을 활용하여 정상적으로 테스트를 진행할 수 있었습니다.
<img src="https://hackmd.io/_uploads/BJ20Xkgyp.png" width="500">
<br/>

### 🔹 아이콘 캐싱
현재 저희의 일기장 앱은 모든 셀이 서버통신을 통해 아이콘을 가지고 오도록 구현되어 있습니다. 하지만 날씨 아이콘은 몇 개의 정해진 아이콘이 반복되어 활용되고 있기에, 이미지 캐싱으로 저장하여 바로 보여주면 좋겠다고 생각했습니다.

```swfit
class ImageCachingManager {
    static let shared = NSCache<NSString, UIImage>()
   ...
}
```

```swift
     switch result {
            case .success(let data):
                guard let image = UIImage(data: data) else { return }
                DispatchQueue.main.async {
                    ImageCachingManager.shared.setObject(image, forKey: NSString(string: icon))
                    self?.weatherIconImageView.image = image
                }
```

## 조언이 필요한 점
### 🔹 DTO 활용
날씨 서버의 API에는 저희가 사용하고자 하는 `Weather`를 제외하고도 많은 내용이 포함되어 있습니다. 저희는 이를 활용하기 위해 일단 API에서 전달받는 모든 내용을 Decoding할 수 있도록 관련 타입을 모두 생성하였습니다.
```swift
struct WeatherResult: Decodable {
    let coord: Coord
    let weather: [Weather]
    let base: String
    let main: Main
    let visibility: Int
    let wind: Wind
    let clouds: Clouds
    let date: Int
......
}
```
하지만 모든 내용을 구현하지 않고 아래와 같이 필요한 정보만 골라 DTO 타입을 만들어도 정상적으로 데이터가 불러와지는 것을 확인하였습니다. 
```swift
struct WeatherResult: Decodable {
    let weather: [Weather]
}
struct Weather: Decodable {
    let id: Int
    let main: String
    let description: String
    let icon: String
}
```
여기서 전체를 다 생성하여 유연성 있게 사용할지 아니면 
필요한 부분만 만들어서 사용해도 되는 것인지 고민이 됩니다.
보편적으로 어떻게 선택하시는지 궁금합니다!
