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

[Refactor] #241 - 성취뷰 MVVM + Combine #242

Merged
merged 25 commits into from
Mar 23, 2024
Merged

[Refactor] #241 - 성취뷰 MVVM + Combine #242

merged 25 commits into from
Mar 23, 2024

Conversation

jeongdung-eo
Copy link
Member

@jeongdung-eo jeongdung-eo commented Mar 13, 2024

🫧 작업한 내용

[화면 간 API 호출 중복 문제 해결을 위한 Service Layer 재구성과 Manager도입]

#239
중복된 API 호출
초기에는 각 화면에서 필요한 API에 따라 별도의 API Service를 사용하여 개발.
그러나 이로 인해 같은 API가 여러 화면에서 중복 호출되는 문제가 발생.

  • 홈VC: 다른 날짜 선택 API 호출
  • 생성 VC: 다른 날짜 선택 API 호출

역할(endpoint)에 따라 분리된 API Service
API 호출을 담당하는 Service를 역할(endpoint)에 따라 분류하여 중복을 최소화함.

#241
네트워크 레이어
싱글톤 객체에서 의존성 주입을 통한 레이어 분리
싱글톤 객체로 구현되어있던 Service객체는 의존성이 강하게 결합되고 테스트가 어려워지며 유지 보수가 어려워 DI주입을 통한 레이어 분리를 도입

Manager 도입
역할에 따라 서비스를 분리하고나니 한 VC에서 여러 개의 Service를 필요로 하는 경우가 생겨 각 Service를 관리하기 위해 Manager 클래스를 도입. Manager 클래스는 해당 화면이 필요로 하는 Service들을 생성하고 관리하는 ServiceWrapper 역할을 함. Manager 도입을 통해 각 Service의 역할을 명확히 구분하고 중복을 최소화하여 유지보수성을 향상시킬 수 있음.

[Manager 객체와 Service 객체 역할]

  • Manager 객체에서는 네트워크 결과로 얻게 될 DTO를 앱에서 사용될 Model로 바꿔주는 역할 수행
  • Manager객체에 DefaultService객체를 protocol(ServiceProtocol)을 통해 DI를 해줌
func getDailyMission(date: String) -> AnyPublisher<[AchieveDetailData], Error> {
        missionAPI.getDailyMission(date: date)
            .map { $0.data?.map { $0.toData(selectedDate: date) } }
            .compactMap { $0 }
            .eraseToAnyPublisher()
    }
  • Service객체는 Request요청을 만들고 실제로 네트워크 수행이 이뤄어지는 BaseService객체와 협력해 네트워크 통신을 수행
typealias DefaultMissionService = BaseService<MissionAPI>

extension DefaultMissionService: MissionServiceProtocol {
            
    func getDailyMission(date: String) -> AnyPublisher<DailyMissionData, Error> {
        return requestWithCombine(MissionAPI.dailyMission(date: date))
    }
    
    func getAchieveCalendar(month: String) -> AnyPublisher<CalendarData, Error> {
        return requestWithCombine(MissionAPI.achieveCalendar(month: month))
    }
}

🔫 PR Point

  • 네트워크 요청을 위한 moya + combine는 sopt makers 참고
  • DI를 위한 Viewmodel, manager는 lionheart 참고

📸 스크린샷

📮 관련 이슈

Copy link
Member

@yungu0010 yungu0010 left a comment

Choose a reason for hiding this comment

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

네트워크 통신 코드에도 컴바인 적용해주셔서 감사합니다!! 코드 보면서 헷갈리는 부분 질문 남겼어용,,💗

return ["Content-Type": "application/json",
"Authorization": "\(KeychainUtil.getAccessToken())"]
}
// status codea만 사용하는 경우
Copy link
Member

Choose a reason for hiding this comment

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

code로 바꿔주세욤><

Copy link
Member Author

Choose a reason for hiding this comment

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

넹><

import Foundation
import Combine

protocol DetailAchievementViewModelPresentable {}
Copy link
Member

Choose a reason for hiding this comment

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

저는 Presentable 프로토콜이 하는 역할이 무엇인지 잘 모르겠어서 사용하지 않았는데 참고 레포에서 혹시 다른 쓰임이 있었을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

카톡에서 이야기했던 것 처럼 ViewController Factory 내에서 VC가 구체적인 타입이 아닌 추상화된 인터페이스인 ViewModel & Presentable에 의존하게 됩니다! Presentable은 데이터 프로퍼티를 전달하기 위함임돵!

@@ -61,7 +73,7 @@ final class DetailAchievementViewController: UIViewController {

if !collectionView.frame.contains(location) {
AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.Achieve.closeDailyMissionModal)
Copy link
Member

Choose a reason for hiding this comment

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

View는 UI 관련 처리만 남겨야한다고 생각해서 Amplitude 관련 코드는 ViewModel이 처리하는게 좋지 않을까 판단했는데 정은님 생각은 어떠신가요!

Copy link
Member Author

Choose a reason for hiding this comment

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

오호! 동의합니당! 이 로직도 뷰모델에서 처리하도록 하겠습니당 !

var config = config
guard let itemCount = self.dataSource?.snapshot().itemIdentifiers(inSection: .main).count else { return config }
guard let itemCount = self?.dataSource?.snapshot().itemIdentifiers(inSection: .main).count else { return config }
Copy link
Member

Choose a reason for hiding this comment

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

self도 guard로 한 번 확인해주는게 좋을 듯 합니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

넹 ><!

Comment on lines 28 to 34
func getAchieveCalendar(month: String) -> AnyPublisher<CalendarEventData, Error> {
return missionAPI.getAchieveCalendar(month: month)
.map { response in
self.convertResponseToCalendarEventData(response)
}
.compactMap { $0 }
.eraseToAnyPublisher()
Copy link
Member

Choose a reason for hiding this comment

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

실제 API 호출은 API에서 해주는 것이고 Manager에서는 디코딩한 값을 DTO에 맞게 변환해준다고 이해했는데 맞나요.. ?🥹

Copy link
Member Author

Choose a reason for hiding this comment

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

실제 API호출은 Service에서 이루어지고 Manager에서는 필요한 API를 호출하고, 결과를 DTO로 변환하여 반환해줍니당!


private lazy var safeArea = self.view.safeAreaLayoutGuide

private let dataSource = CurrentValueSubject<[String: Float], Never>([:])
Copy link
Member

Choose a reason for hiding this comment

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

요 부분이 성취뷰에서 날짜를 클릭했을 때 부분인가요?-? CurrentValueSubject로 사용하신 이유가 궁금합니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

calendar UI 속성을 변경하는 부분에서 dataSource의 value 값에 접근하기 위해서 CurrentValueSubject으로 선언했습니다!

Comment on lines +11 to +13
protocol DetailAchievementViewModelPresentable {
func selectedDate(_ date: String)
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

  • 공통적으로 적용되는 기본적인 기능을 구현한 베이스 클래스
  • 네트워크 요청을 처리하고 응답을 받아오는 등의 작업을 수행

@jeongdung-eo jeongdung-eo merged commit fc91f4b into develop Mar 23, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Refactor] 성취뷰 MVVM + Combine
2 participants