# Solbony 팀프로젝트 회의록 모음집
# 220509 회의록
## 1. 전체적인 구조 설계
[[구조 설계 ExcaliDraw 페이지]](https://excalidraw.com/#room=275b0729e0b525d0b583,daykXYHLaj_2xov8UkLM3A)
## 2. 어떤걸 먼저 만들고 싶은지, 어떤걸 해보고 싶은지 회의
### 1. 아키텍처 결정 (MVC vs MVVM)
- MVVM은 뷰를 테스트하기 좋고 사용자의 입력을 추상화하여 ViewModel을 테스트하기 좋다. 하지만 설계가 어렵다. (비용이 많이 듦)
- MVC는 설계가 쉽지만 뷰를 테스트하기 어렵다. View테스트는 다음 기회에..
**Model에 집중하고 싶어서 MVC로 선택하였다!**
### 2. 의존성 주입 관련 결정
- SceneDelegate에서 화면이동 때 마다 의존성을 타고 타며 주입하는 방식 vs singletone을 사용하여 나에게 맞는 의존성을 당겨오는 방식.
**페이지가 많기에 의존성을 당겨오는 방식 채택**
### 3. 로그인 처리, 이벤트 다시 보지 않기 설정을 UserDefault에 저장
- UserDefault의 행동들을 protocol로 추상화하여 필요한 곳에서는 그에 맞는 추상타입으로 주입받음. (OCP원칙)
### 4. Cache 처리와 관련된 내용 결정(예정)
- 메모리 캐시, 디스크 캐시 등등.. 참고 : https://beenii.tistory.com/187
### 5. 테스트 코드
- UseCase의 추상화? - Mock ViewController를 만들어 버튼이 눌리는 상황까지 추상화할 수 있다.
- 어떤걸 테스트 할까?
> - 로그인이 되었다면 사용자의 토큰 정보가 저장되었는지.
> - 네트워크 작업. 잘 가져왔는지 등등
> - 캐시처리 작업. 잘 저장 되였는지..
### 6. GCD
- 네트워크 처리, 캐시 읽기 쓰기 등 무거운 작업은 서브쓰레드에서 실행시키기!
> - MainThread는 항상 할 일이 많으니 부담을 덜어줍시다!
### 7. 외부 라이브러리
- View코드의 가독성을 위해 SnapKit을 사용하였습니다.
## 3. Xcode 프로젝트 생성 후 각자 잘 실행되는지 확인
- iOS 버전 15.2로 설정
## 4. Issue관리 등 GroundRule 정하기
- 제일 최상위 [Issue](https://github.com/rising-jun/swift-starbucks/issues/1)에 작업하고자하는 내용을 지속적으로 추가하고, 추가된 내용마다 Issue를 생성해 작업을 진행.
- 깃 브랜치는 아래와 같은 형태로 관리.
```
CodeSquad Repo(Upstream)
└── solbony (Origin, Remote branch)
└── dev (Branch)
├── [Init] // 예시
└── [Feature]-UI(Branch)
```
## 5. 하고 싶은 말.
|Sol|Ebony|
|---|---|
|안해봤던 새로운 기술 많이 배울 수 있는 프로젝트가 될 것 같아요 화이팅합시다 에보니!|이유없는 코드X 이유 있는 코드O 화이팅 해봐용 솔!|
## 내일 대략적인 목표
1. 로그인 화면(LoginView) 구현
2. UserDefault 건드려보기
3. **EventView 손 대보기!**
# 220510 회의록
## 1. AppleID 로그인에서 kakaoID 로그인으로 변경
- AppleID 로그인의 경우, Xcode Simulator로 되지 않는 문제가 발생하여, SNS ID 로그인으로 변경.
- SNS ID 로그인 방법들 중, Kakao ID 로그인이 가장 간단하고 적은 비용으로 구현할 수 있다고 판단하여 Kakao ID 로그인 방법을 사용하도록 결정.
## 2. 테스트할 수 있는 내용에 대해 의견 나눔
- 로그인했을 때, 로그인 정보가 담긴 토큰을 LoginUseCase와 KakaoLogin을 통해 UserDefault로 저장할 예정
- 해당 내용에 대해 테스트할 내용은 뭐가 있을까?
- 토큰이 잘 왔는지, 토큰에 nickName이 잘 담겨 왔는지, UserDefault에 토큰이 잘 저장됐는지 확인할 수 있을 것 같다.
## 3. 오늘 배운 것
- KakaoSDK API 중에 UIKit이 포함되어 있어 따로 import 해주지 않아도 UIKit에 있는 클래스들을 사용할 수 있었다.
- `UserApi.shared.loginWithKakaoAccount` 메소드 내에서 로그인할 것인지에 대한 UIAlert가 뜨고, 동의하면 카카오에서 제공해주는 로그인VC가 모달로 화면에 등장하는데, UI와 관련된 작업이 포함되어있기 때문에, `UserApi.shared.loginWithKakaoAccount` 메소드를 Background Thread에서 실행시킬 수 없었다.
## 4. 숙제
- 내일 사용할 UserDefault, PropertyWrapper 공부해보기
## 5. 내일 대략적인 목표
1. **UserDefault**와 관련된 클래스 생성
2. 오늘 만들었던 Kakao ID Login과 비슷한 흐름으로 kakaoID token을 UserDefault에 저장하는 흐름 만들기(짝 프로그래밍)
3. 시간이 되면 EventView까지 건드려보기
# 220512 회의록
## 1. SceneUseCase에서 사용하던 EventRepository와 LoginUseCase에서 사용하는 EventRepository가 같은 객체로 사용하는 방법 논의
- 기존에 선택한 의존성을 부여하는 방식은 "의존성 당겨오기" 방법이라, 상위의 모듈에서 하위의 모듈의 의존성을 직접 주입해주는 방법이 아님 -> 따라서 SceneUseCase에서 사용하던 EventRepository를 직접 LoginUseCase로 전달해주는 방법이 없는 것 같음.
- 그럼 의존성 부여하는 방식을 상위 모듈에서 하위 모듈로 의존성을 주입해주는 방식으로 변경하는 방법밖에 없을까?
## 2. 내일 대략적인 목표
1. LoginVC에서 EventVC로 넘어가는 로직 만들기
2. UserDefault에 있는 값들을 토대로 앱 시작시 등장하는 VC 결정하는 로직 만들기
3. HomeVC 맛보기 (Repository까지 이어질 거 같은데 Service가 양이 방대해질 것 같다.)
or 3. Event화면 리펙토링하기.
# 220513 회의록
## 1. LoginVC -> EventVC 이동시 "스타벅스트" title이 안뜨는 문제 해결
- `LoginVC.view` 설정과 `eventView.titleLabel.text`를 설정하는 로직의 호출 시점? 실행 시점의 문제가 있었음
- `LoginVC` -> `EventVC` 일 경우에 `eventView.titleLabel.text`가 설정되는 메소드가 `LoginVC.view` 설정되는 메소드보다 일찍 호출되었기 때문.
- 비동기로 받은 데이터를 View에 설정 할 때, View의 생성시점 또한 고려해야 한다는걸 깨달았음.
# 220516 회의록
## 1. LoginVc -> EventVC 이동시 EventView의 버튼에 액션이 추가되지 않는 문제 해결
- LoginVC -> EventVC 이동시 EventView의 button에 addAction하는 타이밍과 `EventViewController.action = self` 가 호출되는 시점에 차이가 발생해 button에 액션이 추가되지 않는 문제가 발생
- 해당 문제의 원인은 addAction, `EventViewController.action = self` 두 코드의 호출 시점과 관련되어 있고, EventViewController를 present해주는 시점을 수정한다면 `viewDidLoad()` 의 호출 시점이 수정될 것이고, 그에 따라 `viewDidLoad()`내에 있는 `EventViewController.action = self` 의 호출 시점도 수정될 것이라 판단.
- 이를 해결하기 위해 `init()` 초기화 생성자를 구현해 해당 생성자 내에 `EventViewController.action = self`를 호출하려 했으나, 이렇게 하면 기존에 `viewDidLoad()` 내에서 뷰와 관련된 모든 내용을 설정해주려고 했던 계획과 맞지 않게 됨.
- 기존에는 EventViewController 객체를 생성하고 SceneUseCase/ LoginUseCase 에서 EventData를 받아와 EventViewController 객체에 넣어주도록 진행됐던 흐름에서, SceneUseCase/ LoginUseCase 에서 EventData를 받아와서 그 후 동작을 정해주는 **completionHandler** 내에서 받아온 데이터를 곧바로 새로운 EventViewController 객체에 넣어 present 해주는 흐름으로 수정하여 문제를 해결.
- SceneUseCase/ LoginUseCase 에서 EventData를 받아오는 completionHandler에서 해당 과정이 진행됨으로 인해 `StarbuckstDTO.title`가 무조건 존재한다는 보장이 생겨, 기존에 호출했던 `setEventDTO()` 메소드를 더이상 호출할 필요가 없어짐.
## 2. HomeViewController를 생성하는 static function 구현
- HomeViewController를 생성하는 위치는 SceneDelegate, LoginVC, EventVC의 세 곳이 되고, 각각의 위치에서 (NavigationController, TapBarController를 가진 복잡한) HomeVC를 생성해주는 코드가 있다면 중복되는 코드가 많이 발생할 것이라 판단함.
- 또한 세 위치에서 모두 HomeViewController에 대한 의존성? 결합?을 필연적으로 가지게 되므로, HomeVC 내의 static function을 이용한다면 코드의 가독성도 좋고, 중복되는 코드를 줄일 수 있으므로 해당 방법을 사용하도록 함.
## 3. 코드 리팩토링
- 테스트가 가능할 듯한 동작들 추상화 하기.
- 의존성 주입방법 고민하기. ( DependencyInjector 이해하기. )
- 추상화 한 동작들을 어떻게 테스트할까 고민하기.
- 로그인 기능
- UserDefault기능
- EventRepository
## 4. 내일 만나서 할 일
- 테스트 코드 작성하기
- 시간 되면 HomeViewController 구현 마저 해보기 (View 짜기, Repository에서 데이터 가져오기)
# 220517 회의록
## 1. UserDefault에 대한 테스트 필요 여부에 대한 고민
- UserDefaultManager 객체를 여러 프로토콜로 나누고 각각 테스트를 진행해보려고 했음
- 그런데, UserDefault로부터 어떤 데이터를 갖고왔냐 안갖고왔냐만을 위한 테스트는 의미가 없다고 판단됨
- 그럼 UserDefaultManager를 추상화할 필요가 없었을까? -> 그건 아니었다고 생각됨.
- **OCP 원칙을 지키기 위한 추상화**라는 의의를 가질 수 있는 추상화였다고 판단되어, 해당 부분의 추상화 부분은 수정하지 않고 UserDefaultManager에 대한 의존성을 당겨오는 부분은 구체 타입으로 미리 지정해놓도록 코드를 변경.
## 2. CollectionView 사용 시 유의사항!!!
- DataSource를 따로 나눈다면 꼭 꼭 전역변수로 지정한 후 사용하자! 꼬꼮꼭!!
## 3. Project Conflict 시
- xcoproj파일에서 `패키지열기`, `project.pbxproj` 수정.
# 220518 회의록
## 1. HomeVC에 있는 View들의 속성을 변경해주기 위해 서버로부터 받아와야하는 데이터들을 각각 다른 API를 사용해 비동기적으로 받아와야 하기 때문에 발생하는 문제해결
- DispatchGroup을 이용해 여러 Session에서 이루어지는 작업들을 SerialQueue에 넣은 후 해당 작업들이 모두 끝나면 DispatchGroup을 종료하면서 만든 DTO를 Delegate를 이용해 전달함.
- Repository는 Api요청 순서(service들의 순서)를 관리함. Service에서는 API요청작업만 실행.
- ( HomeView에 보여질 데이터들 요청 흐름 설계 )
<img width="672" alt="스크린샷 2022-05-18 오후 5 15 24" src="https://user-images.githubusercontent.com/62687919/168991409-331085a0-8dd1-4ece-98fc-ceea14bb9fbc.png">
- 데이터를 받아오는 작업들은 대부분 서브쓰레드에서 실행되기에 View를 설정하는 로직에선 DispatchQueue.main.async를 사용해줘야 함.
# 220519 회의록
## 1. 오늘의 과정
- ScrollViewDelegate
- bottomOffset (scrollView.contentSize.height - scrollView.bounds.height + scrollView.contentInset.bottom) : 스크롤뷰 최하단 컨텐츠의 bottom inset을 구할 수 있다.
- 이를 활용하여 밑의 스크롤에 바인딩 될 때, 이벤트 무시 조건 추가.
- UserDefault Manager 테스트 불가?
- 그렇다면 해당 클래스를 싱글톤으로 만들고, 사용하는 곳에서 의존성이 강하게 만들어도 괜찮지 않을까?
- 이벤트 상세화면에 필요한 데이터들을 모두 해당 API에서 받아올 수 없었음
- 그래서 처음에는 홈 화면의 이벤트 데이터를 받아오는 과정에서 받아온 데이터들을 저장해놓고 이벤트 상세화면에서 사용하려 했지만, 흐름이 복잡해지고 적절한 흐름도 아닌 것 같았음
- Alex의 도움으로 이벤트 상세화면에 필요한 데이터들을 보내주는 API를 따로 만들어 이벤트 상세화면이 present될 때 해당 데이터들을 받아오도록 수정.
## 2. 내일 할 일
- 앱 아이콘 설정해서 배포?
- Cache 관련 진행해보기?
# 220520 회의록
## 1. WhatsNew Cell을 등록 일자 순으로 Sorting하기 위해 흐름 변경
- 처음에는 WhatsNewCell에 들어갈 DTO에서 이미지를 받아와 Entity로 변환해준후, 이미지를 받아온 순서부터 CollectionView에 insertItem 해주도록 했음.
- 이렇게 되면 등록 일자 순으로 정렬이 되지않아 이벤트를 정렬해줄 필요가 있었음.
- 처음에는 WhatsNewDataSource의 DataSource에 해당하는 객체에 [WhatsNewDescription?] 타입의 배열을 만들어 Entity의 개수에 맞게 nil로 채워주고,
ViewController에서 해당 배열의 Entity의 인덱스에 맞는(등록 일자 순) 위치에 넣어주도록 하려했지만, 이럴 경우 Cell을 업데이트해주는 타이밍과 Entity가 새로 올라와 배열에 저장되는 타이밍이 일치하지 않아 원하는 위치에 원하는 Cell이 위치하지 않는 문제가 발생했음.
- 이를 해결하기 위해 Entity에 index 프로퍼티를 만들었고,
DTO를 받아오면서 이벤트 등록 일자 순에 맞게 정렬을 해서 Repository로 넘겨주고,
Repository에서는 등록 일자 순에 맞게 넘어온 DTO를 가공해 (이미지는 아직 받아오지 않은 상태에서) Entity를 만들어 ViewController를 통해 CollectionView에 해당 Cell을 만들어주도록 했음.
그리고 이미지를 Service를 통해 받아와서 해당하는 이벤트 Entity의 index를 찾아서 받아온 이미지를 넣어주도록 흐름을 변경했음.
그리고 ViewController로 이미지가 넘어올 때 CollectionView의 `reloadItems(at:section:)` 메소드를 이용해 index에 맞는 Cell을 업데이트 해주도록 했음.
## 2. DependencyInjector 객체의 `injecting(to:)` 메소드에 분기문이 너무 많은 문제
- DependencyInjector는 상위 모듈로 사용될 SceneDelegate와 ViewController에 필요한 속성들의 의존성을 당겨올 수 있도록 해주는 싱글톤 객체임.
- DependencyInjector에서 의존성을 주입해줄 때 사용하는 `injecting(to:)` 메소드는 매개변수로 받아온 SceneDelegate, ViewController들에 의존성을 주입해주는데, 기존에는 아래와 같이 매개변수마다 분기하여 의존성을 주입해주고 있었음.
```swift
final class DependencyInjector {
static func injecting<T: DependencySetable>(to compose: T) {
if compose is SceneDelegate {
guard let sceneDelegate = compose as? SceneDelegate else { return }
sceneDelegate.setDependency(dependency: SceneDependency(manager: SceneUseCase(userDefaultManagable: UserDefaultManager(), eventDataGettable: EventRepository(eventService: EventService()))))
} else if compose is LoginViewController {
...
} else if ...
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
...
override init() {
super.init()
DependencyInjector.injecting(to: self)
}
}
```
- 해당 메소드 내의 복잡한 분기문들을 제거하기 위해 DependencyInjector 객체 내에 Key가 ObjectIdentifier 이고 Value가 Any타입인 Dictionary 속성을 추가함.
이 Dictionary의 Value에는 어떤 클래스 타입이 들어오는지에 따라 주입될 Dependency 타입이 저장되어 있고, `Injecting(to:)` 메소드의 매개변수로 들어온 값의 클래스 타입에 해당하는 Value값(Dependency)을 불러와 매개변수로 주입해줌.
```swift
// final class DependencyInjector
private static let dependencyDictionary: [ObjectIdentifier: Any] = [
ObjectIdentifier(SceneDelegate.self): SceneDependency(manager: SceneUseCase(userDefaultManagable: UserDefaultManager(), eventDataGettable: EventRepository(eventService: EventService()))),
ObjectIdentifier(LoginViewController.self): LoginDependency(manager: LoginUseCase(kakaoLoginable: KakaoLogin(), userDefaultManagable: UserDefaultManager(), eventDataGettable: EventRepository(eventService: EventService()))), ...
]
static func injecting<T: DependencySetable>(to compose: T) {
guard let dependency = dependencyDictionary[ObjectIdentifier(type(of: compose.self))],
let detailedDependency = dependency as? T.DependencyType else {
return
}
compose.setDependency(dependency: detailedDependency)
}
```