# 📖 일기장
## Ground Rules
[README]("https://hackmd.io/l9RKpEBrRSyB_a6N_VovQg")
### 📢 스크럼
- 오전 9시 디스코드에서 진행
- 금일 진행 사항 공유하기(오늘의 할일)
### 📖 프로젝트 규칙
- 네이밍 준수하기(가이드 라인)
- 브랜치 - 스텝별로
- 코드에 대한 기록 그때그때 하기
- 중간중간 리드미 작성
- 타입 생성자로 생성된 프로퍼티는 타입 생략
- 사용자 정의 함수의 매개 변수가 4개 이상일 때만 개행
- 시스템 내부 함수 일때 매개변수가 4개 이상이면 개행
- 단, 매개 변수 갯수와 상관없이 120자가 넘는 다면 개행
### 커밋 규칙
- ✨feat: 기능 추가
- ♻️refactor: 기능과 관련된 개선/전면수정
- 🩹chore: 네이밍, 컨벤션 등 수정
- 🐛fix: 오류 수정
- 📝docs: 문서 추가 및 수정
- ✅test: 테스트 추가
- 🚚build: 파일그룹화
- 커밋 단위: 기능 단위, chore 묶어서 커밋
### 👩🏻⚖️ 팀 규칙
- TIL, 일일 회고 작성 시간(매일 22시부터 1시간 작성 진행)
- 컨디션이 좋지 않을 때는 꼭 말해주기!!
### 🔖 스케줄
- 월목
| 시간 | 내용 | 상세 |
| :--------: | :--------: | :--------: |
| 09:00-09:30 | 스크럼 | |
| 09:30-12:00 | **활동학습 예습** | |
| 14:00-17:00 | **활동학습** | |
| 19:00-22:00 | 프로젝트 | 필요시 개인공부 |
| 그 후 | TIL 및 개인시간 | |
- 화수금
| 시간 | 내용 | 상세 |
| :--------: | :--------: | :--------: |
| 09:00-09:30 | 스크럼 | |
| 09:30-12:00 | 프로젝트 | |
| 14:00-17:00 | 프로젝트 및 개인공부 | |
| 19:00-22:00 | **활동학습 예습** | 필요시 프로젝트 |
| 그 후 | TIL 및 개인시간 | |
- 예외사항
- 리뷰를 받으면 유동적으로!
## 일일 스크럼
### 🙌 08/28 월
- 오늘의 컨디션
- Dasan🌳: 너어어어어무 좋습니다! 경민🥹 영광이에요 진짜루(경민말투)
- Kyungmin🐼: 행복해용👉🏼👈🏼
- 특이사항
- Dasan🌳: 경민과 함께해서 3학기가 두렵지 않아졌어요 응애👶
- Kyungmin🐼: 다산 🫣 푸근☺️
- 오늘 할 일
- [X] 그라운드룰 정하기
- [ ] Step1-1 진행
- [ ]
### 🙌 08/29 화
- 오늘의 컨디션
- Dasan🌳: 우우 아무말
- Kyungmin🐼: 다산을 생각하면서 잠들어요🥹(feat. 새벽 감성)
- 특이사항
- Dasan🌳: 바운즈 뿌실 예정!! ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ
- Kyungmin🐼: 아무말 아님
- 오늘 할 일
- [ ] Step1-1 진행
- [ ]
### 🙌 08/30 수
- 오늘의 컨디션
- Dasan🌳: 어제 일찍 자서 아주 개운합니다!!😆
- Kyungmin🐼: 너무 좋아요~ 이제 일찍 주무세요 하하😆 아니 경민 컨디션을 적어주셔야죠!
- 특이사항
- Dasan🌳: ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 다행~ 이다
- Kyungmin🐼: 내 컨디션_매우 좋음
- 오늘 할 일
- [ ] Step1-1
- [x] 날짜 포맷
- [ ] (선택) delete 만들기
- [x] 네비게이션 타이틀 및 + 버튼 추가
- [ ] 에러 얼럿 구현
- [ ]
### 🙌 08/31 목
- 오늘의 컨디션
- Dasan🌳: 좋아영!! 경민~~ 제가 세상 불편한 사람이 되어볼게요😎 잔소리 장전
- Kyungmin🐼: 요즘 너무 편해서 잠이 많아 졌어여 😭 아무것도 하기 싫음ㅠ
- 특이사항
- Dasan🌳: 오늘 슈퍼 블루문이었어요! 엄청 예쁨ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
- Kyungmin🐼: 복숭아 알러지가 심한걸 깨달음ㅋㅋㅋㅋㅋㅋㅋ
- 오늘 할 일
- [ ] 활동학습 함 - Localization
- [ ] Step1-2 구현
### 🙌 09/01 금
- 오늘의 컨디션
- Dasan🌳: 불금은 언제나 신나죠 경민~~
- Kyungmin🐼: 다산~ 저한테 전화좀해주세요 ㅠㅠ 흐멩ㄴㄹ 😓😢😭😭😢😓😰😪🤕
- 특이사항
- Dasan🌳: ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 잘하셨어요 경민ㅋㅋㅋㅋ 아닙니다! 사람은 24시간도 잘 수 있는걸여
- Kyungmin🐼: 9시간을 잤어요..ㅠㅠ 사람이 아니에요
- 오늘 할 일
- [ ] Step1-2 마무리(전체적인 리펙토링)
- [ ] PR보내기
- [ ] README 작성!
- [ ] 다하고 게임! (좋아여><)
### 🙌 09/04 월
- 오늘의 컨디션
- Dasan🌳: 월요일은 피곤합니다😌
- Kyungmin🐼: 오랜만에 다산과 짝프를해서 즐거워요 (feat. 오후 10시 42분)
- 특이사항
- Dasan🌳: 저도 경민과 짝프해서 즐거워요!!
- Kyungmin🐼: 다산께서 엄청난 배려를 해주십니다.
- 오늘 할 일
- [ ] step1 pr 피드백 반영 (행복)
- [ ]
- [ ]
- [ ]
### 🙌 09/05 화
- 오늘의 컨디션
- Dasan🌳: 아주 좋아요!
- Kyungmin🐼: 담에 다시 해여 ㅎㅎ ㅋㅋㅋㅋㅋㅋㅋ
- 특이사항
- Dasan🌳: 드디어 모각코! 경민ㅋㅋㅋㅋㅋㅋ 경민과 만나서 행복합니다?!ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
- Kyungmin🐼:
- 오늘 할 일
- [ ] Core Data
- [ ] Making Apps with Core Data
- [ ] UITextViewDelegate
- [ ] UISwipeActionsConfiguration
- [ ] 놀기ㅋㅋㅋㅋㅋㅋㅋ 경민 체력 다하면 꼭 말씀해주세요
### 🙌 09/06 수
- 오늘의 컨디션
- Dasan🌳:
- Kyungmin🐼: 이날부터 시작인건가 ㅠ
- 특이사항
- Dasan🌳:
- Kyungmin🐼:
- 오늘 할 일
- [ ]
- [ ]
- [ ]
- [ ]
### 🙌 09/07 목
- 오늘의 컨디션
- Dasan🌳: 호호홓ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
- Kyungmin🐼: 호호홍ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
- 특이사항
- Dasan🌳:
- Kyungmin🐼:
- 오늘 할 일
- [ ]
- [ ]
- [ ]
- [ ]
### 🙌 09/08 금
- 오늘의 컨디션
- Dasan🌳: 헤헤헿
- Kyungmin🐼: 헤헿
- 특이사항
- Dasan🌳:
- Kyungmin🐼:
- 오늘 할 일
- [ ] 코어데이터 적용
- [ ]
- [ ]
- [ ]
### 🙌 09/11 월
- 오늘의 컨디션
- Dasan🌳: 아주 좋습니다! 이히힣
- Kyungmin🐼: 오늘은 활동학습 뺏습니다.
- 특이사항
- Dasan🌳: 뭔가 즐겁게 놀다가 공부하는 느낌이라 슬퍼졌어요
- Kyungmin🐼:헿 왜 웃으시죵?? 메리가 하품하셨습니다
- 오늘 할 일
- [ ] 예습
- [ ] STEP2 합치기
- [ ]
- [ ]
### 🙌 09/12 화
- 오늘의 컨디션
- Dasan🌳: 저 완전 멀쩡합니다!
- Kyungmin🐼: 다산 메리 초크걸고 싶다
- 특이사항
- Dasan🌳: 경미이이이인~~
- Kyungmin🐼: 12시까지 기절시켜놓고 싶다
- 오늘 할 일
- [ ] STEP2 PR
- [ ]
- [ ]
- [ ]
### 🙌 09/13 수
- 오늘의 컨디션
- Dasan🌳: ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
- Kyungmin🐼: 다산 대전으로 도망가신날
- 특이사항
- Dasan🌳: 이게 꿈인가 아닌가 헷갈립니다
- Kyungmin🐼: 다산웨일메리 영원하라!!
- 오늘 할 일
- [ ] STEP2 마무리하기
- [ ] STEP2 PR 보내기
- [ ]
### 🙌 09/14 목
- 오늘의 컨디션
- Dasan🌳: 아주 좋습니다!
- Kyungmin🐼: 매우 안좋음 << 왜요 경민 왜 매우 안좋으세요!
- 특이사항
- Dasan🌳: 경민...🥹 경민 다산 영원하라!! 뽀에버
- Kyungmin🐼: 다산 노느라 정신없는 날!! 😔😔😞😠 << 경민...저 놀기만 하지 않았어요ㅠㅠㅋㅋㅋㅋㅋ
- 오늘 할 일
- [ ] 자소서 작성 << 쓰다 포기
- [ ] 내일 예습
- [ ]
- [ ]
### 🙌 09/15 금
- 오늘의 컨디션
- Dasan🌳:
- Kyungmin🐼: 심기 불편 << 아니 경민 컨디션을 작성해주셔야죸ㅋㅋㅋㅋㅋ
- 특이사항
- Dasan🌳:
- Kyungmin🐼: 메리랑 웨일이랑 점심먹는날
- 오늘 할 일
- [ ]
- [ ]
- [ ]
---
# STEP 1
## 📖 일기장
안녕하세요 제임스!
일기장 리뷰를 받게 된 Kyungmin🐼, Dasan🌳입니다.
step1 PR이 조금 늦어졌지만 잘 부탁드립니다🙏🏻
많은 조언 부탁드립니다☺️
3주동안 잘 부탁드립니다 🙇🏻♀️
---
<br>
## 고민했던 점 🤔
### 1️⃣ 타입 분리
✨ 모델 타입 별 기능
- `DiaryData` : `JSON` 데이터를 디코딩할때 사용, `DTO`
- `Diary` : 실제 `VC`에서 사용할 데이터
- `AssetDataManager` : `filename`을 통해 `Asset`데이터를 받아와 디코딩해주는 역활
- `DataManager` : `DTO`를 `Diary` 타입으로 변환 해주는 역활
- `DateManager` : 필요한 날짜를 원하는 템플릿으로 변환하여 `String`으로 반환 해주는 역활
- `DiaryManager`: `VC`와 `Model` 사이에서 필요한 데이터나 이벤트를 역활에 맞는 `Manager`에게 전달 해주는 역활
🚨 **문제점** <br>
- `DTO`인 `DiaryData`와 `Diary`를 통합해서 하나의 타입으로 사용 할지, 분리해서 사용할 지에 대해 고민했습니다.
💡 **해결방법** <br>
- 통합하여 사용 하면 `cellRegistration`에서 `cell`에 데이터를 입력시켜 줄 때마다 데이터 변환을 `DiaryManager`를 통해 요청해줘야 했고, 분리하여 사용 시 `DiaryData`를 `fetch`해 올 때 `Data`를 정제 시켜줘야 했습니다.
- 각 셀에 데이터를 주입해 줄 때마다 `DiaryManager`를 통해 데이터를 요청하는 것보다 fetch할때 정제해주는것이 오버헤드가 더 적다고 판단하였기 때문에 `DiaryData`와 `Diary`를 분리하여 사용했습니다.
### 2️⃣ TextView와 Keyboard
편집중인 텍스트가 키보드에 의해 가리지 않도록하기 위하여 `diaryTextView`와 `keyboard` 사이에 레이아웃 설정이 필요했습니다.
🚨 **문제점** <br>
- diaryTextView와 `keyboardLayoutGuide` 사이에 constraint를 아래와 같이 잡아주었습니다.
- 키보드의 위치를 추적하는 `keyboardLayoutGuide`와 constraint를 설정하였습니다.
- 더불어 readability에 최적화된 width를 제공해주는 `readableContentGuide`와 constraint를 설정하여, **긴 글을 쉽게 읽을 수 있도록** 하였습니다.
```swift
NSLayoutConstraint.activate([
diaryTextView.topAnchor.constraint(equalTo: view.readableContentGuide.topAnchor),
diaryTextView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor),
diaryTextView.leftAnchor.constraint(equalTo: view.readableContentGuide.leftAnchor),
diaryTextView.rightAnchor.constraint(equalTo: view.readableContentGuide.rightAnchor)
])
```
- 하지만 `keyboardLayoutGuide` 같은 경우 `iOS 15`에서부터 적용할 수 있습니다.
- 아래와 같이 `iOS15` 이상을 사용하는 사람들이 대부분이지만 그 이하 버전을 사용하는 `6%`를 위하여 다른 방법을 모색할 필요가 있었습니다.

💡 **해결방법** <br>
- iOS 15 이상일 때와 그 미만 버전일 때를 나누어 레이아웃을 적용해주었습니다.
- iOS 15이상: keyboardLayoutGuide 적용
- iOS 15미만: diaryTextView의 contentInset 변경
- iOS 15미만일 때에는 `NotificationCenter`를 통해 키보드가 나타나고 사라질 때를 추적하여, `diaryTextView`의 `contentInset.bottom`을 키보드 높이만큼 변경하였습니다.
```swift
// DiaryDetailViewController.swift
override func viewWillAppear(_ animated: Bool) {
if #unavailable(iOS 15.0) {
addKeyboardObserver()
}
}
private func addKeyboardObserver() {
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
}
@objc private func keyboardWillShow(_ notification: Notification) {
guard let userInfo = notification.userInfo as NSDictionary?,
let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
return
}
diaryTextView.contentInset = UIEdgeInsets(
top: .zero,
left: .zero,
bottom: keyboardFrame.size.height,
right: .zero
)
}
```
### 3️⃣ Locale 적용
🚨 **문제점** <br>
- Date 포맷을 DateFormatter의 lacale을 `ko_KR`로 적용하였을 때 지역화가 되지 않는 문제점이 발생했습니다.
```swift
dateFormatter.locale = Locale(identifier: "ko_KR")
```
💡 **해결방법** <br>
- **프로퍼티를 읽을 당시 사용자의 지역 설정을 나타내는** Locale의 `current` 프로퍼티를 사용하여 지역화 문제를 해결했습니다.
```swift
dateFormatter.locale = Locale.current.identifier
```
<br>
## 조언을 얻고 싶은 점 🙏🏻
### 1️⃣ 버전 별로 다르게 적용 되는 keyboardWillShow 메서드 버그
✨ **적용사항**
- textView에 font를 따로 적용하지 않음
- 키보드가 보여질때 textView에 Inset을 키보드의 크기만큼 bottom에 적용
```swift
diaryTextView.contentInset = UIEdgeInsets(
top: .zero,
left: .zero,
bottom: keyboardFrame.size.height,
right: .zero
)
```
| ios 14 버전 xs | ios 15 버전 14 pro |
| :--------: | :--------: |
| <Img src = "https://cdn.discordapp.com/attachments/1100965172086046891/1147059269913563156/14_.gif" width="250"/>| <Img src = "https://cdn.discordapp.com/attachments/1100965172086046891/1147059198132224071/4aaf56a73a36825d.gif" width="250"/>|
🚨 **문제점** <br>
`iOS 15`버전에서 `inset`을 주는 코드를 적용한 뒤, 텍스트 뷰의 중간 쯤에서 개행을 하면 텍스트 뷰가 의도치 않게 올라가는 버그가 발생했습니다. `iOS 14` 버전에서는 문제 없이 실행되는데 버그의 원인을 찾지 못했습니다.
<br>
## 🔗 참고링크
- [🍎Apple Docs: CoreData](https://developer.apple.com/documentation/coredata)
- [🍎Apple Docs: KeyboardLayoutGuide](https://developer.apple.com/documentation/uikit/uiview/3752221-keyboardlayoutguide)
- [🍎Apple Docs: UITextView](https://developer.apple.com/documentation/uikit/uitextview)
- [🍎Apple Docs: current](https://developer.apple.com/documentation/foundation/locale/2293654-current)
- [🍎Apple Docs: DateFormatter](https://developer.apple.com/documentation/foundation/dateformatter)
----
# STEP 2
## 📖 일기장
안녕하세요 제임스:)
드디어 step2로 인사드립니다!
Step2가 많이 늦어져서 걱정되셨을텐데 기다려주셔서 감사합니다🥹
이번 스텝도 많은 조언 부탁드립니다🙏
감사합니다 제임스!💚
---
<br>
# 고민했던 점 🤔
## 1️⃣ 100Kg DiaryManager (Model과 ViewController사이의 중간 객체)
### 🚨 **문제점** <br>
- 기존의 `DiaryManager`타입에서 `DiaryViewController`와 `DiaryDetailViewController`의 비지니스 로직을 모두 가지고 있도록 구현한 뒤 각각의 `ViewController`에 주입시켜줬습니다. 각각의 `ViewController`의 비지니스 로직은 분리시켜줄 수 있었지만 `DiaryManager`가 무거워지는 문제가 발생했습니다.
### 💡 **해결방법** <br>
- 각각의 `ViewController`마다 `UseCase`를 따로 만들어 `DiaryManager`가 가지고 있는 로직을 분리해준 뒤, 다른 `UseCase`끼리 통신하기 위해서는 `ViewController`의 `Delegate`를 이용하여 통신할 수 있도록 하여 `DiaryManager`의 복잡성을 낮췄습니다.

## 2️⃣ FetchedResultsController를 사용하기엔 무리가 있었습니다.
### 🚨 **문제점** <br>
두번째 화면(일기 화면)에서 작성 및 수정된 text를 첫번째 화면(리스트 화면)에 반영해주기 위하여 아래와 같은 방법들을 시도해보았습니다.
- 기본적으로 CollectionView에서 `DiffableDataSource`를 사용하고 있으므로 `snapshot`을 활용하여 CollectionView의 data를 업데이트 해주고 있습니다.
#### 1. FetchedResultsController
- 적용
- `FetchedResultsController`는 Core Data fetch requset 요청의 결과를 관리하고 사용자에게 데이터를 표시하는 데 사용하는 컨트롤러입니다. 이 컨트롤러의 delegate는 **fetch results가 변경되었을 때** fetched results controller가 호출할 메서드를 가지고 있습니다.
- 메서드들 중 아래 메서드를 활용한다면 fetch results가 변경되었을 때 `쉽게` snapShot을 apply를 해줄 수 있을 것 같아 적용해보기로 하였습니다.
```swift
optional func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeContentWith **snapshot**: NSDiffableDataSourceSnapshot
)
```
- 하지만 해당 controller 메서드의 snapshot를 활용하려고 하였을 때, apply하는 부분에서 아래와 같은 오류가 계속 발생하였습니다🥲(이 부분의 문제 해결을 위하여 오랫동안 붙잡고 있었으나, 결국 원인을 찾지 못하였습니다.) 이에 매개변수의 snapshot를 활용하는 대신 기존에 구현해 놓은 applySnapshot()를 호출하였습니다.
```swift
Could not cast value of type 'NSTemporaryObjectID_default' (0x1ba22d4c8) to 'Diary.DiaryEntity' (0x104c15c10).
```
<details>
<summary> 코드 보기 </summary>
```swift
// DiaryViewController.swift
private func applySnapshot() {
guard let diaryDataSource,
let fetchedObjects = fetchedResultsController?.fetchedObjects else { return }
var snapshot = NSDiffableDataSourceSnapshot<Section, DiaryEntity>()
snapshot.appendSections([.main])
snapshot.appendItems(fetchedObjects)
diaryDataSource.apply(snapshot, animatingDifferences: true)
}
```
```swift
// DiaryViewController.swift
extension DiaryViewController: NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
diaryDataSource?.apply(snapshot as NSDiffableDataSourceSnapshot<DiaryViewController.Section, DiaryEntity>, animatingDifferences: true)
}
}
```
</details>
- 장점
- fetch results의 변경시점을 신경쓰지 않아도 되어 변경할 때 하고 싶은 작업을 수행할 수 있었습니다.
- 단점
- `diaryDataSource?.apply(snapshot as...)` 대신 기존에 구현해놓은 `applySnapshot`를 활용하면 fetch results가 변경되었을 때 첫번째 화면에 데이터를 반영하는 것에는 문제가 없었지만, controller 매개변수가 제공하는 sanpshot를 활용하지 못해 해당 contorller를 **사용하는 의미가 없다**고 판단하였습니다.
- 또한 controller 같은 메서드를 사용한다면 ViewController가 특정 delegate를 의존하고 있는 것이므로 **의존성 문제**가 발생할 수 있어 다른 방법을 찾아보기로 하였습니다.
#### 2. Delegate 패턴
- 적용
<details>
<summary> 코드 보기 </summary>
```swift
protocol DiaryDetailViewControllerDelegate: AnyObject {
func diaryDetailViewController(_ diaryDetailViewController: DiaryDetailViewController, upsert diary: Diary)
func diaryDetailViewController(_ diaryDetailViewController: DiaryDetailViewController, delete diary: Diary)
}
```
```swift
// MARK: DiaryDetailViewController Delegate
extension DiaryViewController: DiaryDetailViewControllerDelegate {
func diaryDetailViewController(_ diaryDetailViewController: DiaryDetailViewController, upsert diary: Diary) {
useCase?.upsert(diary)
loadData()
applySnapshot()
}
func diaryDetailViewController(_ diaryDetailViewController: DiaryDetailViewController, delete diary: Diary) {
useCase?.delete(diary)
loadData()
applySnapshot()
}
}
```
</details>
- 단점
- `Delegate` 패턴을 이용하여 `diaryDetailViewController`에서 `diaryViewController`로 변경된 데이터의 업데이트를 요청하고 `applySnapshot()`을 호출 하면 보이지 않는 뷰에 대해서 계속해서 `applySnapshot()`하는 단점이 있었습니다.
### 💡 **해결방법** <br>
#### 3. Delegate 패턴 + viewDidAppear 메서드
- 적용
- `coreData`를 업데이트 하는 작업만을 `delegate`를 통해 작업하도록 했습니다.
- `applySnapshot()`은 `delegate`를 통해서 호출하는 대신, `viewDidAppear` 메서드 내에서 호출해주었습니다.
<details>
<summary> 코드 보기 </summary>
```swift
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
applySnapshot()
}
```
</details>
- 장점
- 첫번째 화면(리스트 화면)으로 돌아갈 때만 `applySnapshot()`을 호출하므로 보이지 않는 뷰에 대해서 호출하던 단점을 보안할 수 있었습니다.
## 3️⃣ 길이가 10인 배열에 index 10으로 접근?
### 🚨 **문제점** <br>
`didSelectItemAt`에서 `IndexPath.item`를 통해 `diaryList`에 접근할때 `diaryList` 데이터가 미리 업데이트 되어 있지 않을 때 `Index out of range`가 발생했습니다.
### 💡 **해결방법** <br>
`IndexPath.item` 를 통해 `diaryList`를 접근하기 전에 데이터를 미리 업데이트 하지 않은 것은 휴먼에러이지만 이런 상황에서도 `Index out of range`에러를 통한 `Crash`를 막기위한 방법을 고민했습니다.
```swift
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
```
`Collection` 타입의 유효 범위를 가지고 있는 `indices`라는 프로퍼티에 대해 알게됐고, 접근한 `Index`가 유효할 때는 `Element` 타입을 반환하고 유효하지 않을 때는 `nil`을 반환 하도록 `subscript`메서드를 정의 했습니다.
<br>
## 4️⃣ 제목없을 때 리스트 높이가 줄어드는 현상
### 🚨 **문제점** <br>
일기장의 첫 줄이 없을때 `Diary` 타입의 `title` 속성으로 빈 문자열이 들어갈 경우 목록에서 `cell`의 높이가 줄어드는 문제가 발생했습니다.

### 💡 **해결방법** <br>
`title`속성이 비어있을 경우 `제목 없음` 텍스트를 넣어주도록 로직을 수정하여 해결하였습니다.

<br>
# 조언을 얻고 싶은 점 🙏🏻
## 1️⃣ 동시접근하고 있나요?
### 🚨 **문제점**
1. `DiaryDetailVeiwController`의 `textViewDidEndEditing`에서 `UseCase`의 메서드를 호출
2. `UseCase`의 메서드에서 `UseCaseDelegate`를 통해 `DiaryDetailVeiwControllerDelegate`의 `upsert`메서드를 호출
3. `DiaryDetailVeiwControllerDelegate` 메서드에서 다시 `UseCase`의 프로퍼티로 접근
**🔻다음과 같은 상황에서 `Simultaneous` 에러가 발생했습니다.**


**🔻동시접근을 하고 있지 않은 것 같아 다음과 같이 테스트코드를 작성해봤습니다.**
<details>
<summary> 코드 보기 </summary>
```swift
struct TestDiary {
let content: String
}
protocol TestUseCaseDelegate: AnyObject {
func delegateFunc()
}
struct TestUseCase {
var testDiary: TestDiary // 호출 순서: 4번
weak var delegate: TestUseCaseDelegate?
func doingTestUseCase() {
delegate?.delegateFunc() // 호출 순서: 2번
}
}
class TestVC {
var testUseCase: TestUseCase?
init(testUseCase: TestUseCase) {
self.testUseCase = testUseCase
}
func setupUseCaseDelegate() {
testUseCase?.delegate = self
}
func doing() {
testUseCase?.doingTestUseCase() // 호출 순서: 1번
}
}
extension TestVC: TestUseCaseDelegate {
func delegateFunc() {
print(testUseCase?.testDiary) // 호출 순서: 3번
}
}
let diary = TestDiary(content: "Dasan")
let useCase = TestUseCase(testDiary: diary)
let viewController = TestVC(testUseCase: useCase)
viewController.setupUseCaseDelegate()
viewController.doing()
```
**🔺이 테스트 코드에서는 에러가 발생하지 않았는데 뷰컨트롤러에서는 왜 동시접근 에러가 나는지 조언을 얻고 싶습니다.🤔**
</details>
### 1️⃣ 동시 접근하고 있었습니다.
`UseCase`가 `struct` 였기 때문에 `UseCase`내 `mutating` 메서드가 호출 되면 `UseCase`에 대한 메모리로 직접 접근하게 됩니다.
그런 와중에 `mutating` 메서드 내에서 델리게이트를 통해 다시 `UseCase`에 접근했기 때문에 동시 접근 오류가 난 것 같습니다. 원래 `Test`했던 코드도 `mutating`을 붙이니 동시 접근 에러가 발생 했습니다. `mutating`을 지우거나 `UseCase`를 `Class`로 변경했을때는 에러가 발생하지 않았습니다.
<details>
<summary>🔻 코드 펼쳐보기 </summary>
```swift
struct TestDiary {
let content: String
}
protocol TestUseCaseDelegate: AnyObject {
func delegateFunc()
}
struct TestUseCase {
var testDiary: TestDiary // 호출 순서: 4번
weak var delegate: TestUseCaseDelegate?
mutating func doingTestUseCase() {
testDiary = TestDiary(content: "경민")
delegate?.delegateFunc() // 호출 순서: 2번
}
}
class TestVC {
var testUseCase: TestUseCase?
init(testUseCase: TestUseCase) {
self.testUseCase = testUseCase
}
func setupUseCaseDelegate() {
testUseCase?.delegate = self
}
func doing() {
testUseCase?.doingTestUseCase() // 호출 순서: 1번
}
}
extension TestVC: TestUseCaseDelegate {
func delegateFunc() {
print(testUseCase?.testDiary) // 호출 순서: 3번
}
}
let diary = TestDiary(content: "Dasan")
let useCase = TestUseCase(testDiary: diary)
let viewController = TestVC(testUseCase: useCase)
viewController.setupUseCaseDelegate()
viewController.doing()
```
</details>
## 🔗 참고링크
- [🍎Apple Docs: NSFetchedResultsController](https://developer.apple.com/documentation/coredata/nsfetchedresultscontroller)
- [🍎Apple Docs: NSFetchedResultsControllerDelegate](https://developer.apple.com/documentation/coredata/nsfetchedresultscontrollerdelegate)
- [🍎Apple Docs: Core Data](https://developer.apple.com/documentation/coredata)
- [🍎Apple Docs: Setting up a Core Data stack](https://developer.apple.com/documentation/coredata/setting_up_a_core_data_stack)
- [🍎Apple Docs: UITextViewDelegate](https://developer.apple.com/documentation/uikit/uitextviewdelegate)
- [🍎Apple Docs: UISwipeActionsConfiguration](https://developer.apple.com/documentation/uikit/uiswipeactionsconfiguration)
- [🍎Apple Docs: collection](https://developer.apple.com/documentation/swift/collection)
----
viewContext와 backgroundContext의 차이점은 뭘까요~? 그리고 여기에서는 viewContext를 이용하신 이유도 남겨주시면 좋을것 같습니다~!!
viewContext와 backgroundContext는 managed object의 변경 사항을 조작하고 추적할 수 있는 NSManagedObjectContext를 상속받고 있다.
1. [viewContext](https://developer.apple.com/documentation/coredata/nspersistentcontainer/1640622-viewcontext)
- MainQueueConcurrencyType으로 main queue에서 관리되는 object context
- main queue context는 애플리케이션 인터페이스에 사용하기 위한 것이며 앱의 main queue에서만 사용할 수 있음
2. [newBackgroundContext](https://developer.apple.com/documentation/coredata/nspersistentcontainer/1640581-newbackgroundcontext)
- privateQueueConcurrencyType으로 private queue에서 실행되는 새 managed object context를 반환함
- 초기화 시 자체 큐를 생성하며 해당 큐에서만 사용할 수 있음.
- 애플리케이션에서 JSON에서 Core Data로 데이터를 가져오는 것과 같이 데이터를 처리할 경우 사용
- NSManagedObjectContext는 관리되는 object의 **변경 사항을 조작하고 추적할 수 있는** object space
- 컨텍스트는 하나 이사으이 영구 저장소에 대한 내부적으로 일관된 view를 나타내는 관련 모델 개체 그룹으로 구성됨.
- 관리되는 개체에 대한 변경 사항은 코어 데이터가 해당 컨텍스트를 하나 이상의 영구 저장소에 저장할 때까지 연결된 컨텍스트의 메모리에 유지됩니다.
- MainQueueConcurrencyType
#### [viewContext](https://developer.apple.com/documentation/coredata/nspersistentcontainer/1640622-viewcontext)
- main queue에서 관리되는 object context
- 이 컨텍스트는 NSPersistentStoreCoordinator와 직접 연결되며 기본적으로 non-generational임.
#### [newBackgroundContext](https://developer.apple.com/documentation/coredata/nspersistentcontainer/1640581-newbackgroundcontext)
- private queue에서 실행되는 새로운 관리되는 object context를 반환합니다.
- 이 메서드를 호출하면 영구 저장소가 동시성 유형이 NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType로 설정된 새 NSManagedObjectContext를 생성하고 반환합니다.
- 이 새 컨텍스트는 NSPersistentStoreCoordinator와 직접 연결되며 NSManagedObjectContextDidSave 브로드캐스트를 자동으로 소비하도록 설정됩니다.
메인큐 프라이빗큐 context 비교
https://developer.apple.com/documentation/coredata/using_core_data_in_the_background
- 메인큐 컨텍스트는 애플리케이션 인터페이스에 사용하기 위한 것이며 앱의 메인 큐에서만 사용할 수 있음
프라이빗 큐 컨텍스트는 초기화 시 자체 큐를 생성하며 해당 큐에서만 사용할 수 있음. 큐는 비공개이며 NSManagedObjectContext 인스턴스 내부에 있으므로 perform(_:) 및 performAndWait(_:) 메서드를 통해서만 액세스할 수 있습니다.
일반적으로 사용자와 관련이 없는 데이터처리는 main 큐에서 수행하지 마세요. 데이터 처리는 CPU를 많이 사용할 수 있으며, 메인 큐에서 수행하면 사용자 인터페이스가 응답하지 않을 수 있습니다.
애플리케이션에서 JSON에서 코어데이터로 데이터를 가져오는 것과 같이 데이터를 처리할 경우 프라이빗 큐 컨텍스트를 만들고 프라이빗 큐 컨텍스트에서 가져오기를 수행하세요.
뷰컨이 에러를 모르게 하려면 델리게이트를 써서 에러를 처리해라!
핸들러 내에서 do-catch 쌉가능