Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

일기장 [STEP 2] Mary, Whales #134

Open
wants to merge 26 commits into
base: ic_9_whale
Choose a base branch
from

Conversation

MaryJo-github
Copy link

STEP 2

📔 일기장 _ 🐿️🐋

안녕하세요 havi🐠(@havilog)!!!
3일 전에 인사드린 (๑❛ᴗ❛๑ ) Mary🐿️, Whales🐋 입니다!!
STEP2 PR이 너무 늦었습니다😥
더 열심히 달려보겠습니다!!!!

잘 부탁드립니다 하비!! 🙇🏻‍♀️ 🙇🏻‍♀️

🤹🏻 고민했던 점

🔹 자동 저장

🚨 고민했던 점

  • 다음과 같은 상황에 자동 저장 기능이 추가되어야해서 어떻게 구현할 수 있을지 고민하였습니다.
    • 이전 화면(리스트 화면)으로 이동하는 경우
    • 앱이 백그라운드로 진입하는 경우
    • 사용자가 입력을 멈추는 경우(키보드가 사라지는 경우)

💡 해결방법

  1. 이전 화면(리스트 화면)으로 이동하는 경우
    • 화면이 pop될 때 자동 저장 하기 위해 viewWillDisappear 메서드 내에서 save() 메서드를 호출하였습니다.
  2. 앱이 백그라운드로 진입하는 경우
    • application 또는 scene에서는 앱이 백그라운드로 진입할 때 notification을 post합니다. 이를 observer로 받아 selector 메서드에서 save()를 호출하였습니다.
    private func setObserver() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(enterBackground),
                                               name: UIWindowScene.didEnterBackgroundNotification,
                                               object: nil)
    }
  3. 사용자가 입력을 멈추는 경우(키보드가 사라지는 경우)
    • UITextViewDelegate에서 제공해주는 textViewDidEndEditing 메서드 내부에서 save() 메서드를 호출하였습니다.
    extension EditingDiaryViewController: UITextViewDelegate {
        func textViewDidEndEditing(_ textView: UITextView) {
            save()
        }
    }

🔹 빈 일기장 처리 고민

현재 로직
+버튼 클릭 시 -> DiaryContent(id = UUID(), title = "", body = "", date = 오늘)가 코어데이터에 insert 메서드로 저장.

🚨 문제점

  • 일기를 생성 버튼을 누르고 textView에 아무것도 입력하지 않았더니 title과 body는 비어있고 날짜 레이블만 채워진 셀이 생성되었습니다. 빈 일기는 코어데이터에 저장하지 않는 것이 데이터 메모리나 기능면에서 더 좋을 것 같아 고민하였습니다.

💡 해결방법

  • textView가 비어있다면 생성된 DiaryContent를 코어데이터에서 삭제합니다.
    • 1️⃣ save() 메서드에 분기 처리

      private func save() {
          guard let text = diaryTextView.text else { return }
      
          if text.isEmpty {
              ContainerManager.shared.delete(id: diaryContent.id)
              return
          }
          ...
      }

      -> 빈 textView를 클릭하면 해당 id에 대한 DiaryContent가 삭제되어 그 후에 text를 입력하더라도 코어데이터에 저장되지 않았습니다.

    • 2️⃣ viewWillDisappear에 있는 save 메서드 호출 전에 분기 처리
      코어데이터에서 삭제하는 로직을 viewWillDisappear에 추가하여 키보드가 사라지거나 백그라운드에 들어갔을 때는 여전히 update를 하고 일기 목록화면으로 돌아갔을 때만 확인하도록 수정하였습니다.

      override func viewWillDisappear(_ animated: Bool) {
          super.viewWillDisappear(animated)
      
          if diaryTextView.text.isEmpty {
              ContainerManager.shared.delete(id: diaryContent.id)
              return
          }
      
          save()
      }

🎙️ 조언을 얻고 싶은 점

🔸 ActivityView

공유하기 위해 ActivityView를 띄웠을 때 콘솔창에 자꾸 이런 메세지가 뜹니다.
Layout에 대한 얘기인 것 같은데 ActivityView 는 따로 잡지않고 내부 구현된 부분으로 보이는데 왜 이런 메세지가 뜨는지 찾지 못하였습니다ㅠㅠ
이유를 아실까요??

MaryJo-github and others added 16 commits September 5, 2023 18:52
사용자가 입력을 멈추는 경우(키보드가 사라지는 경우)
- 사용자가 입력을 멈추는 경우(키보드가 사라지는 경우) - 앱이 백그라운드로 진입하는 경우 - 이전화면(리스트 화면)으로 이동하는 경우
- DiaryViewController 오류 print -> os_log 변경
- presentCheckDeleteAlert 및 presentActivityView 메서드를 UIViewController extension에 구현
Copy link

@havilog havilog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔸 ActivityView

어딘가에서 translate ~~ 뭐시기 이거를 제대로 안잡아주고 오토레이아웃을 사용한 건 아닐까요?
요런 특정상황의 버그는 저도 스택오버플로우를 봐야 ..

수고하셨어요 메리웨일~

Diary/Controller/DiaryViewController.swift Outdated Show resolved Hide resolved
Diary/Controller/DiaryViewController.swift Outdated Show resolved Hide resolved
Diary/Controller/DiaryViewController.swift Outdated Show resolved Hide resolved
Diary/Controller/DiaryViewController.swift Outdated Show resolved Hide resolved
Diary/Utility/PresentableActivityView.swift Outdated Show resolved Hide resolved
Diary/View/DiaryTableViewCell.swift Outdated Show resolved Hide resolved
import OSLog

final class ContainerManager {
static let shared = ContainerManager()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 이 친구는 반드시 싱글톤이어야 하는지?
  2. 싱글톤일 때 장점과 단점
  3. 에러 핸들링을 OSLog를 찍으면 사용자는 일기의 작성이 실패했는지를 어떻게 알 수 있을지
  4. 뷰가 이 의존성을 가지고 있을 때 테스터빌리티에 미치는 영향은?
  5. 외부에서 접근하는 함수를 protocol화 하면 어떤 장단점이 있을지?
    등등 생각해보시면 좋을 거 같아요

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. c9e4490
    ContainerManager를 DiaryManager에 병합시키면서 코드를 전체적으로 수정하였습니다.

  2. 싱글톤일 때 장점과 단점

  • 싱글톤 장점

    • 인스턴스가 절대적으로 한 개만 존재하는 것을 보증할 수 있습니다.
    • 어디에서든지 싱글톤 인스턴스에 접근할 수 있습니다. 따라서 다른 클래스의 인스턴스들이 데이터를 공유하는 것이 가능합니다.
    • 고정된 메모리 영역을 얻으면서 동시에 단 한번만 생성하여 메모리 낭비를 방지할 수 있습니다.
  • 싱글톤 단점

    • 어디에서든지 접근할 수 있기 때문에 싱글톤 객체에 접근하거나 싱글톤 객체의 프로퍼티를 수정하는 객체를 추적하기 어렵습니다. 이로 인해 오류 발생 시 디버깅이 어려울 수 있습니다.
    • 싱글톤 인스턴스가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유시키면 다른 클래스들 간의 결합도가 높아짐 -> 개방-폐쇠 원칙 위배
    • 싱글톤 객체에 의존하는 객체는 강한 결합으로 인해 단위 테스트가 어려워집니다.
    • 앱 수명동안 인스턴스가 유지되므로 메모리 관리에도 신경써야 합니다.
    • 멀티 스레드 환경에서 동기화 처리를 하지 않으면 인스턴스가 2개 생성되는 문제가 발생할 수 있습니다.
  1. dfc8a85
    에러시 사용자도 알 수 있게 alert을 띄우는 코드를 추가하였습니다.

  2. 강한 결합으로 인해 단위 테스트를 할 수 없습니다.

  3. a3fae63
    외부에서 접근하는 함수를 protocol로 만들어 보았습니다. 또한 뷰에서는 manager가 아닌 이 프로토콜에 의존하게 수정하였습니다. 이렇게 함으로써 단위 테스트가 가능해졌습니다.

setObserver()
}

override func viewWillDisappear(_ animated: Bool) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

뷰의 상태값을 가지고 어떤 행동을 하는 것은 위험할 것 같아요.
일기장 편집에서 나가는 행위는, 뒤로가기를 누르거나 스와이프 백 액션이 있을 수 있고,
그 액션들은 코드로 받을 수 있을거 같아요
또한 이렇게 어떤 조건이 아닐 때만 어떤 행동을 해야한다. 라는 context라면 guard를 쓰는게 더 자연스러워보여요

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


알려주신 것처럼 guard가 문맥상 더 자연스러워 보여서 수정하였습니다!!
cfecbc7

근데
뷰의 상태값을 가지고 어떤 행동을 하는 것은 위험할 것 같아요.
라는 부분이 잘 이해가 되지 않습니다.😓

코드 자체를 수정하는 방법으로는

  1. Back 버튼을 새로 구현하는 방법
@objc func tappedBackButton() {
    guard diaryTextView.text.isEmpty else {
        save()
        navigationController?.popViewController(animated: true)
        return
    }

    ContainerManager.shared.delete(id: diaryContent.id)
    navigationController?.popViewController(animated: true)
}
  1. willMove 메서드를 이용하는 방법
override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent)
    
    guard parent == nil else { return }
    guard diaryTextView.text.isEmpty else {
        save()
        return
    }

    ContainerManager.shared.delete(id: diaryContent.id)
}

이렇게 두 가지를 생각해보았지만 왜 viewWillDisappear를 쓰는 것이 위험한지, 셋 중 어떤 방법이 어떤 면에서 더 좋을지를 공부해보아도 확신이 들지 않아서 한 번 더 질문드립니다.
추가 설명을 부탁드려도 될까요?

Comment on lines +138 to +143
if let index = text.firstIndex(of: "\n") {
diaryContent.title = String(text[text.startIndex ..< index])
diaryContent.body = String(text[index ..< text.endIndex])
} else {
diaryContent.title = text
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적으로 요런 식으로 분리되면 좋을거 같은데 요것도 오답이다 는 아니구 고민해보면 좋을거 같은 포인트인거 같아요~
// 처음 뷰를 그리기 위한 일기 객체
// 텍스트뷰에 써진 텍스트
// 저장하기 위한 새로운 일기장 객체 생성
// 새로 만든 객체로 저장
// 성공하면 self의 일기장을 바꿔주거나

@MaryJo-github MaryJo-github marked this pull request as draft September 16, 2023 15:05
@MaryJo-github MaryJo-github marked this pull request as ready for review September 16, 2023 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants