Skip to content

Commit e559299

Browse files
committed
[#234] UserDepartmentService 코드를 전부 반응형으로 재설계
- RxMoya 도입 - 가독성 높은 JSON 출력 함수를 설계 후 테스트 코드에서 사용
1 parent 0705dcc commit e559299

File tree

7 files changed

+219
-207
lines changed

7 files changed

+219
-207
lines changed

EATSSU/App/Sources/Data/Network/Router/UserDepartmentRouter.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ enum UserDepartmentRouter {
2121

2222
/// 단과대/학부 검증 API 요청
2323
case validateDepartment
24-
24+
2525
/// 사용자 단과대의 제휴업체 요청
2626
case getUserPartnership
2727
}

EATSSU/App/Sources/Data/Network/ServiceLayer/UserDepartmentService.swift

+52-78
Original file line numberDiff line numberDiff line change
@@ -6,111 +6,85 @@
66
//
77

88
import Foundation
9+
910
import Moya
11+
import RxMoya
12+
import RxSwift
1013

1114
/// 부서 관련 API 요청을 처리하는 서비스 클래스입니다.
1215
///
1316
/// 이 클래스는 사용자 부서 관련 API 요청을 수행하며, 부서 추가 기능을 제공합니다.
14-
/// MoyaProvider를 사용하여 API 요청을 처리하며, 인증 토큰은 RealmService를 통해 가져옵니다.
17+
/// RxMoyaProvider를 사용하여 API 요청을 처리하며, 인증 토큰은 RealmService를 통해 가져옵니다.
1518
final class UserDepartmentService {
1619
/// MoyaProvider 인스턴스
1720
private let provider: MoyaProvider<UserDepartmentRouter>
21+
private let decoder: JSONDecoder
1822

1923
/// 초기화 메서드
2024
///
2125
/// - Parameter provider: 기본값은 `MoyaProvider<UserDepartmentRouter>()`입니다.
22-
init(provider: MoyaProvider<UserDepartmentRouter> = MoyaProvider<UserDepartmentRouter>()) {
26+
/// - Parameter decoder: 기본값은 `JSONDecoder()`입니다.
27+
init(
28+
provider: MoyaProvider<UserDepartmentRouter> = MoyaProvider<UserDepartmentRouter>(),
29+
decoder: JSONDecoder = JSONDecoder()
30+
) {
2331
self.provider = provider
32+
self.decoder = decoder
33+
}
34+
35+
/// 공통 디코딩 로직을 처리하는 private 메서드
36+
private func decode<T: Decodable>(_ type: T.Type, from response: Response) -> Single<T> {
37+
Single.create { single in
38+
do {
39+
let decodedData = try self.decoder.decode(type, from: response.data)
40+
single(.success(decodedData))
41+
} catch {
42+
single(.failure(error))
43+
}
44+
return Disposables.create()
45+
}
2446
}
2547

2648
/// 부서 추가 API 요청을 수행합니다.
2749
///
28-
/// - Parameters:
29-
/// - departmentName: 추가할 부서의 이름
30-
/// - completion: 요청 완료 후 호출되는 클로저.
31-
/// 성공 시 서버의 응답 문자열을 반환하고, 실패 시 에러를 반환합니다.
32-
func addDepartment(departmentName: String, completion: @escaping (Result<String, Error>) -> Void) {
33-
provider.request(.addDepartment(departmentName: departmentName)) { result in
34-
switch result {
35-
case let .success(response):
36-
do {
37-
// 서버 응답을 문자열 형태로 매핑합니다.
38-
let decodedData = try JSONDecoder().decode(BaseResponseWithoutResult.self, from: response.data)
39-
if decodedData.isSuccess {
40-
completion(.success(decodedData.message))
41-
} else {
42-
// 서버에서 전달한 메시지를 포함한 에러 반환
43-
let error = NSError(
44-
domain: "UserDepartmentService",
45-
code: -1,
46-
userInfo: [NSLocalizedDescriptionKey: decodedData.message]
47-
)
48-
completion(.failure(error))
49-
}
50-
} catch {
51-
completion(.failure(error))
52-
}
53-
case let .failure(error):
54-
completion(.failure(error))
50+
/// - Parameter departmentName: 추가할 부서의 이름
51+
/// - Returns: 성공 시 서버의 응답 메시지를 포함한 BaseResponseWithoutResult를 반환하는 Single
52+
/// - Note: 부서 추가에 실패할 경우 에러를 반환합니다.
53+
func addDepartment(departmentName: String) -> Single<BaseResponseWithoutResult> {
54+
provider.rx.request(.addDepartment(departmentName: departmentName))
55+
.flatMap { [weak self] response in
56+
guard let self else { return .error(ServiceError.instanceDeallocated) }
57+
return decode(BaseResponseWithoutResult.self, from: response)
5558
}
56-
}
5759
}
5860

5961
/// 부서 이름 검증 API 요청을 수행합니다.
6062
///
61-
/// - Parameter completion: 요청 완료 후 호출되는 클로저.
62-
/// 성공 시 서버의 응답 문자열을 반환하고, 실패 시 에러를 반환합니다.
63-
func validateDepartment(completion: @escaping (Result<Bool, Error>) -> Void) {
64-
provider.request(.validateDepartment) { result in
65-
switch result {
66-
case let .success(response):
67-
do {
68-
let decodedData = try JSONDecoder().decode(BaseResponse<Bool>.self, from: response.data)
69-
if decodedData.isSuccess {
70-
completion(.success(decodedData.result))
71-
} else {
72-
let error = NSError(
73-
domain: "UserDepartmentService",
74-
code: -1,
75-
userInfo: [NSLocalizedDescriptionKey: decodedData.message]
76-
)
77-
completion(.failure(error))
78-
}
79-
} catch {
80-
completion(.failure(error))
81-
}
82-
case let .failure(error):
83-
completion(.failure(error))
63+
/// - Returns: 성공 시 검증 결과를 포함한 BaseResponse<Bool>을 반환하는 Single
64+
/// - Note: 검증에 실패할 경우 에러를 반환합니다.
65+
func validateDepartment() -> Single<BaseResponse<Bool>> {
66+
provider.rx.request(.validateDepartment)
67+
.flatMap { [weak self] response in
68+
guard let self else { return .error(ServiceError.instanceDeallocated) }
69+
return decode(BaseResponse<Bool>.self, from: response)
8470
}
85-
}
8671
}
8772

8873
/// 사용자 단과대의 제휴업체 목록을 요청합니다.
8974
///
90-
/// - Parameter completion: 요청 완료 후 호출되는 클로저.
91-
/// 성공 시 제휴업체 목록을 반환하고, 실패 시 에러를 반환합니다.
92-
func getUserPartnership(completion: @escaping (Result<[PartnershipResponse], Error>) -> Void) {
93-
provider.request(.getUserPartnership) { result in
94-
switch result {
95-
case let .success(response):
96-
do {
97-
let decodedData = try JSONDecoder().decode(BaseResponse<[PartnershipResponse]>.self, from: response.data)
98-
if decodedData.isSuccess {
99-
completion(.success(decodedData.result))
100-
} else {
101-
let error = NSError(
102-
domain: "UserDepartmentService",
103-
code: -1,
104-
userInfo: [NSLocalizedDescriptionKey: decodedData.message]
105-
)
106-
completion(.failure(error))
107-
}
108-
} catch {
109-
completion(.failure(error))
110-
}
111-
case let .failure(error):
112-
completion(.failure(error))
75+
/// - Returns: 성공 시 제휴업체 목록을 포함한 BaseResponse<[PartnershipResponse]>를 반환하는 Single
76+
/// - Note: 목록 조회에 실패할 경우 에러를 반환합니다.
77+
func getUserPartnership() -> Single<BaseResponse<[PartnershipResponse]>> {
78+
provider.rx.request(.getUserPartnership)
79+
.flatMap { [weak self] response in
80+
guard let self else { return .error(ServiceError.instanceDeallocated) }
81+
return decode(BaseResponse<[PartnershipResponse]>.self, from: response)
11382
}
114-
}
11583
}
11684
}
85+
86+
/// 서비스 계층에서 발생할 수 있는 에러 정의
87+
enum ServiceError: Error {
88+
case instanceDeallocated
89+
// 필요한 다른 에러 케이스들을 추가할 수 있습니다.
90+
}

EATSSU/App/Sources/Presentation/Maps/ViewController/MapViewController.swift

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ final class MapViewController: BaseViewController {
4141
/// `PartnershipService` 인스턴스. 제휴 업체 정보를 가져오는 데 사용됩니다.
4242
private let partnershipService = PartnershipService()
4343

44+
/// `UserDepartmentService` 인스턴스. 사용자의 학부와 관련된 정보를 서버에 요청하는 객체입니다.
45+
private let userDepartmentService = UserDepartmentService()
46+
4447
/// Rx에서 사용되는 DisposeBag입니다.
4548
private let disposeBag = DisposeBag()
4649

@@ -344,6 +347,8 @@ final class MapViewController: BaseViewController {
344347
)
345348
.disposed(by: disposeBag)
346349
}
350+
351+
private func fetchUserPartnership() {}
347352
}
348353

349354
// MARK: - NMFMapViewTouchDelegate

EATSSU/App/Sources/Presentation/MyPage/ViewController/EditProfileViewController.swift

+12-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created by Jiwoong CHOI on 01/31/25.
66
//
77

8+
import RxSwift
89
import UIKit
910

1011
import EATSSUKit
@@ -52,6 +53,7 @@ final class EditProfileViewController: BaseViewController {
5253
let nicknameProvider = MoyaProvider<UserNicknameRouter>(plugins: [ESMoyaLoggingPlugin()])
5354

5455
let userDepartmentService = UserDepartmentService()
56+
private let disposeBag = DisposeBag()
5557

5658
// MARK: - UI Components
5759

@@ -207,14 +209,15 @@ extension EditProfileViewController {
207209
}
208210

209211
func setUserDepartment(department: String) {
210-
userDepartmentService.addDepartment(departmentName: department) { result in
211-
switch result {
212-
case let .success(data):
212+
userDepartmentService.addDepartment(departmentName: department)
213+
.subscribe(onSuccess: { [weak self] response in
214+
guard let self else { return }
213215
#if DEBUG
214216
print("학과 변경 성공")
215-
print(data)
217+
print(response)
216218
#endif
217-
self.showAlertController(title: "완료", message: "학과 설정이 완료되었습니다", style: .cancel) {
219+
220+
showAlertController(title: "완료", message: "학과 설정이 완료되었습니다", style: .cancel) {
218221
if let myPageViewController = self.navigationController?.viewControllers.first(where: { $0 is MyPageViewController }) {
219222
self.navigationController?.popToViewController(myPageViewController, animated: true)
220223
} else {
@@ -226,13 +229,14 @@ extension EditProfileViewController {
226229
}
227230
}
228231
}
229-
case let .failure(error):
232+
}, onFailure: { [weak self] error in
233+
guard let self else { return }
230234
#if DEBUG
231235
print("학과 변경 실패")
232236
print(error.localizedDescription)
233237
#endif
234238
AlertControllerHelper.showConfirmOnlyAlert(title: "문제가 발생했습니다", message: "다시 시도하세요", in: self)
235-
}
236-
}
239+
})
240+
.disposed(by: disposeBag)
237241
}
238242
}

EATSSU/App/Sources/Presentation/TabBar/RootTabBarViewController.swift

+8-7
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
// Created by JIWOONG CHOI on 1/27/25.
66
//
77

8+
import RxSwift
89
import UIKit
910

1011
import EATSSUDesign
1112
import EATSSUKit
1213

1314
class RootTabBarViewController: UITabBarController {
1415
private let userDepartmentService = UserDepartmentService()
16+
private let disposeBag = DisposeBag()
1517

1618
override func viewDidLoad() {
1719
super.viewDidLoad()
@@ -90,14 +92,13 @@ extension RootTabBarViewController: UITabBarControllerDelegate {
9092

9193
/// 학과 정보 입력 여부를 비동기적으로 확인하는 메서드
9294
private func userEnteredDepartmentInfo(completion: @escaping (Bool) -> Void) {
93-
userDepartmentService.validateDepartment { result in
94-
switch result {
95-
case let .success(isEntered):
96-
completion(isEntered)
97-
case .failure:
95+
userDepartmentService.validateDepartment()
96+
.subscribe(onSuccess: { response in
97+
completion(response.result)
98+
}, onFailure: { _ in
9899
completion(false)
99-
}
100-
}
100+
})
101+
.disposed(by: disposeBag)
101102
}
102103

103104
/// 학과 정보 입력이 되어있지 않은 경우 모달 시트를 표시하는 메서드

0 commit comments

Comments
 (0)