## 완료한 명세 - [x] UI 구성 - [x] ID TextField Validation - [x] Network Layer 추상화 ## 배경 안녕하세요 광현님! 이번주부터 리뷰를 받게 될 김성훈-윤동주팀입니다. 2주간 좋은 리뷰 받기 위해 열심히 준비를 해보겠습니다! 😀😀 잘 부탁드립니다! 이번 미션을 진행하기에 앞서 다음과 같은 페어프로그래밍 룰을 정했습니다. [Rule] - Driver는 되도록 멍청해지자 - 구현을 위한 검색은 같이 진행 - Task 단위로 진행하되, 30분을 넘기지 않는 것으로 - 한시간에 10분 쉬기 → 차후 조정 또한, 기존 미션에서 Storyboard를 활용하라는 부분을 각자의 학습을 위해서 코드 베이스로 구현을 해보기로 하였습니다. 금일까지 구현한 과정은 다음과 같습니다. `Storyboard 부분 삭제` - `회원가입 화면 UI 구현` - `회원가입 섹션 별 로직 구현(진행 중)` - `네트워크 분리(진행 중)` 작업의 경우 주로 함께 페어로 작업을 진행해보기도 하고 각자 영역을 맡아서 진행한 부분도 있습니다. ## 수정 내역 기본적으로 코어타임에는 페어프로그래밍을 하고 있습니다. 코어타임 이후에는 작업을 나누고 이어서 작업을 진행합니다. 어제와 금일 작업은 다음과 같습니다. 페어프로그래밍 - UI 구성 및 설계 윤동주 - 회원 가입 Validation 작성 김성훈 - Network Layer 작성 ## 리뷰 노트 Q1. UIView와 UIStackView의 쓰임 - 현재 SignUpViewController에서는 SignUpView를 add하여 사용하고 있습니다. - 이때 SignUpView를 UIView로 만들지 UIStackView로 만들지 고민을 했습니다. - 해당 화면을 그릴 때 새로운 UIView를 가져오고 StackView없이 그리고 있지만 화면 크기에 대해 높이에 대해 반응형으로 만들어야 할 듯하여 고민이 듭니다. - 이때 어떤 기준으로 UIView를 쓸지, UIStackView를 쓸지 고민입니다. Q2. Network Layer 추상화 네트워크 추상화를 진행하면서 까다로웠던 것은 다음과 같습니다 - SRP : 해당 class가 특정 역할을 맡는 것이 맞는가? 고민이 더 필요합니다 - Error Handling : Error Handle에 대한 경험이 부족하여 어디서 에러를 던지고 어디서 받아서 처리할지 여전히 고민입니다 - Async / Await를 사용하여 코드를 고쳐보려고 합니다 ## 스크린샷 https://user-images.githubusercontent.com/54929503/275852033-5cef2173-0fae-4425-bf97-45b3428ab8f2.mp4 -------------------------------------------------------------------------------------------------- ## 완료한 명세 - [x] SignUp Logic 구현 이후 Unit Test - [x] 자동 로그인 구현 - [x] Network Service 추상화 이후 Unit Test - [ ] 중복 로그인 구현 중 ## 배경 안녕하세요 광현님! 지난 리뷰를 확인 후 최대한 반영하여 구현을 했습니다. 좋은 리뷰 많이 남겨주시고 관심있게 리뷰해주셔서 너무 감사했습니다. 이번에도 좋은 리뷰 부탁드립니다. 금일 목요일까지 구현한 과정은 다음과 같습니다. `회원가입 로직 구현 완료` - `VC로부터 delegate 분리` - `자동로그인을 위해 UserDefaults 설정` - `모델 구조 변경` - `회원가입 로직 유닛 테스트 진행` - `Singleton 개선` - `네트워크 분리 및 리뷰 반영 개선` - `네트워크 유닛테스트를 위한 Mock 구현` - `현존 아이디 Get API 구현` - `회원가입 UseCase 분리` - `중복 아이디 여부 확인(진행 중)` 최대한 학습에 초점을 맞추려고 해보았으나 다양한 경험도 중요하다고 생각하여 여러 개선과 구현을 해보는 경험을 해보았습니다. ## 수정 내역 - 명세에 ViewController에게 Delegate를 주지 않고 다른 클래스로 분리해보라 하셔서 그렇게 진행했습니다 - 저희 둘 모두 Unit Test 경험이 적어 같이하는김에 더 Test를 해보기로 하여 Unit Test 작성했습니다 - Test가 가능한 Network Layer를 위해 Mock을 만들어 Test를 진행했습니다 모든 기능 페어프로그래밍으로 진행하였습니다 ## 리뷰 노트 Q1. UseCase와 Manager - 기존에는 SignUpManager라는 Model을 SignUpViewController에서 갖고 있었습니다. - 여기에 UseCase를 적용해보고자 SignUpManager-SignUpUseCase-SignUpViewController 관계로 포함을 하도록 구현을 하였습니다. - 처음에는 최대한 checkId, checkName 등의 입력정보의 유효성 검사를 하는 함수들을 UseCase가 갖도록 구현했지만 점점 Model에서 데이터를 단순히 리턴하는 함수들(ex. fetchManagerId 등)을 UseCase로 넘겨오게 되어 뚱뚱한 UseCase와 홀쭉한 Model을 갖게 되었습니다. - 여기서 UseCase가 해야할 역할(가져야 할 함수)들과 Model이 해야할 역할(가져야 할 함수)들의 차이에 대한 고민을 하게 되었습니다. - 현재는 UseCase가 뚱뚱하도록 두었지만 혹시 이 부분을 개선할 사항이 있을지 질문드립니다. Q2. Network Layer 추상화와 Unit Test 네트워크 추상화를 리뷰를 받고 좀 더 좋은 구조로 변경하고 싶었습니다 Unit Test를 해야하겠다는 생각이 들어서 Mock에 대해서 학습하고 Protocol을 사용하여서 테스트가 가능하게 만들었습니다 테스트 가능한 코드가 좋다고는 하지만, 뭔가 새롭게 만든 `URLSessionProtocol` `URLSessionDataTaskProtocol` 같은 프로토콜은 정말 테스트를 위한 프로토콜이라는 생각이 듭니다 이러한 프로토콜은 정말 테스트를 위해서만 작성하게 되는 것인지 궁금했습니다. Q3. Network BaseURL(String) + Path(String) 오류 발생 ```swift= final class IdGetAPI: Router { typealias Response = [String] var baseURL = "https://api.codesquad.kr/signup" var path = "" } ``` ```swift= final class IdGetAPI: Router { typealias Response = [String] var baseURL = "https://api.codesquad.kr/" var path = "signup" } ``` 똑같은 API의 호출 상황에서 1번방식으로 하면 정상적으로 Response가 오고 2번 방식으로 진행하면 403 BadResponse가 옵니다 (URL문제겠죠?) debug을 해본결과 request를 볼낼때 정상적으로 baseURL과 path가 결합되어 정상적 url로 보내는 것 같은데 왜 이런 오류가 나는지 짐작가시는 부분이 있으면 알려주심 감사하겠습니다 (저희가 디버그 해봤을 때는 두 방식의 url이 모두 동일했습니다) Q4. UIButton에서 configuration 방식 사용 - 현재 버튼에 대한 구현은 하나밖에 없지만 앞으로의 Button 혹은 TableView Cell등을 구현하면서 Configuration을 많이 적용해보고자 합니다. - 원래는 항상 직접 UITableViewCell이나 UIButton에 직접 커스텀을 진행했습니다. - 하지만 inset 처리 시 deprecated된 부분들이 많다고 느껴 configuration 방식을 사용하기 시작했습니다. - 혹시 이런 방식의 구현이 앞으로 계속 필요해질지 혹은 현업에서는 어떤 식으로 타이핑하시는지 궁금합니다. ## 스크린샷 https://github.com/boostcampwm2023/swift-p4-onban/assets/54929503/b38b756f-74a9-490b-8206-d883267f44a4 <img src ="https://github.com/SeongHunTed/RxSwift/assets/42074365/01d0befb-fe2e-4b54-aef0-df2bcff683fe" alt="list" width="200"> ------------------------------------------------------------------------ ## 완료한 명세 - [x] 중복 ID 체크, SignUp POST 요청 - [x] CollectionView Layout 작업 - [x] UseCase, Network 요청 부분 구현 중 ## 배경 안녕하세요 광현님! 지난 리뷰도 반영하고, JK 리뷰도 있어서 해당 부분 반영하면서 구현해보았습니다. 금주 월요일부터 중간에 건강 이슈가 발생하여 지난 주에 비해 작업량이 감소하였습니다. CollectionView 를 처음 써보고 있어서 서로 뚝딱거리면서 천천히 만들어가는 중입니다. ## 리뷰 노트 Q1. UseCase와 Protocol ```swift= protocol OnBanListUsable { } struct OnBanListUseCase: OnBanListUsable { } class SomeViewController: UIViewController { private let usecase: OnBanListUsable } ``` 의존성 주입을 위해서 구체 타입이 아닌 프로토콜 타입으로 선언하라고 배웠습니다. protocol을 사용하면서, 구체 타입이 아닌 추상 타입으로 쓰며 느껴지는 장점도 분명히 느껴본 경험이 있지만, 이런 usecase도 추상타입으로 쓰면 이점이 있는가? 아직 잘 모르겠습니다. 조언을 주시면 이런한 usecase도 어떠한 관점에서 추상타입으로 사용하면 장점이 있는지 학습해보고 싶습니다. > 사실 오히려 추상타입으로 쓰면서 protocol에서 함수를 추가해줘야하는 번거로움이 있기도 했습니다 ㅎ.. Q2. 자료구조가 같지만 분리가 필요한 데이터 타입 ```swift= // 메뉴 단위 struct OnBanListModel { let detailHash: String let image: String let alt: String ... 생략 } typealias MainListModels = [OnBanListModel] typealias SideListModels = [OnBanListModel] typealias SoupListModels = [OnBanListModel] struct OnBanListModels { var mainModel: MainListModels var sideModel: SideListModels } ``` 현재 명세에 따르면 Main, Soup, Side API를 각각 호출하고 각 섹션별로 데이터를 업데이트 해야합니다. 근데 Server로부터 받아오는 DTO도 같고, 사용해야하는 Data 타입도 같습니다. 하지만 섹션 별로 구분해두기 위해 `OnBanListModels`에 변수로 typealias로 새로 명명한 타입들을 사용하였습니다. 이런 경우 이렇게 typealias를 사용하는 것이 좋은 방법일까요? (OnBanListViewController 파일에 선언한 enum OnBanListSection을 Key로하는 딕셔너리도 생각중에 있습니다) [Swift07A] 상품 목록 페이지 구현, Image Cacher 추가 ## 완료한 명세 - [x] 리뷰 항목 부분 적용 - [x] 상품리스트 fetch API 구현 - [x] CollectionView diffableDataSource 적용 - [x] SwiftLint 적용 - [x] 이미지 캐싱 - [ ] 상품 상세보기 UI 구현 진행 중 ## 배경 안녕하세요 광현님! 마지막 리뷰 요청을 드립니다. 이번 2주동안 구현보다 학습에 초점을 맞춘 점이 좋았습니다. 이틀동안 CollectionView, DiffableDataSource를 구현해보며 사용법을 익힐 수 있었습니다. 또한, Image Caching도 구현해보면서 메모리와 디스크 내에 저장하고 캐시 데이터를 사용할 수 있게 되었습니다. 마지막으로, 동기/비동기 로직을 구현해보면서 어색하지만 어느정도 그 필요성과 사용 시점 등을 학습할 수 있었습니다. 이번 리뷰는 구현된 View보다는 그 로직들(ex.async, await 구현 시점, 불필요하게 길어진 코드 등)이 적절한 시점에서의 구현이 이루어진 것인지, 조금 더 개선할 수 있는 부분이 있다면 사소한 부분이라도 가감없이 조언주시면 감사드리겠습니다! 2주동안 리뷰를 받으면서 이번 네부캠 코드리뷰 기간 중 가장 관심있게 코드를 봐주시고 리뷰 외적인 부분의 코드들도 확인하시면서 조언을 주신 리뷰어 분이셨습니다. 광현님께 리뷰를 받게 되어 감사했습니다! ## 리뷰 노트 Q1. Image Cacher 구현부 async/await를 학습해도 아직은 와닿지 않습니다. 현재 이미지 캐셔를 구현한 내용은 다음과 같습니다 - 디스크에 캐싱 : DispatchQueue.global 쓰레드에 위임 - 메모리에 캐싱 : 성능상 문제가 없을 것 같아 별도 쓰레드 위임X - 웹 이미지 다운로드 : Data(contentOf:) 는 동기 메서드이나 이를 포함하는 함수 자체를 비동기화 실제 Cell에서 호출하는 부분 ```swift= Task { imageView.image = await ImageCacher.shared.loadImage(url: data.image) } ``` 이렇게 만들어두고 셀에서 이미지를 가져올때 imageView.image에 image를 할당하는 부분은 Task { } 내부에서 수행했습니다. 사실 이렇게 사용하는게 맞는지 아직 확신은 없는데 우선 동작하는대로 구현해둔 상태입니다. 이제 좀 안다고 생각했는데 여전히 모르는게 많네요 Q2. SwiftLint의 사용성 이번 주중에는 SwiftLint도 적용을 해보았습니다. 크게 4가지 disabled_rules, included, excluded, opt_in_rules에 규칙들을 추가, 제거하였습니다. 이때, 다음과 같은 요소를 많이 쓴다는 글을 블로그에서 확인했습니다. ``` excluded: # 아래 3개는 자주 포함되는 경로임 - Pods - OnBanApp/AppDelegate.swift - OnBanApp/SceneDelegate.swift ``` 물론 항상 반영되는 것은 아니겠지만 어느정도 국룰...?처럼 작성해 넣는 Lint 요소들이 있다면 조언 부탁드리겠습니다.(마치 toptal에서 .gitignore를 생성하듯이 대부분의 프로젝트에서 공통적으로 이런건 꼭 적용하는 Lint다 싶은 것들을 의미합니다~! 이게 올바른 요청인지 조금 의문이 들긴 합니다.😅😅) Q3. 싱글톤을 잘 적용해보기 이번 프로젝트를 구현하면서 싱글톤으로 구현하기도 하고 오히려 구현했다가 빼기도 하면서 그 필요성에 항상 이유를 성훈님과 얘기나눠보면서 적용을 했습니다. 현재와 같은 경우 싱글톤을 사용한 부분 중 이런 부분은 싱글톤이 적용되지 않았으면 하시는 부분이나 이런 부분은 적용했으면 좋겠다 하시는 부분이 있으시면 조언 부탁드리겠습니다. <img src ="https://github.com/SeongHunTed/RxSwift/assets/42074365/9332a562-967c-43ee-a1bd-2746b6cf7e18" width="200"> <img width="764" alt="list" src="https://github.com/SeongHunTed/RxSwift/assets/42074365/9791d811-30a8-448f-a6e8-9ef5f29a885a">