서론
MyPageViewModel의 getAllItems()는 끔찍함 그 자체입니다.
보겠습니다.
func getAllItems() {
var uid: Int64? = nil
switch userState {
case .loggedOut:
return
case .me:
uid = Authentication.shared.user?.id
case .other:
uid = self.userId
}
guard let uid else { return }
Task {
do {
async let interestsTask = interactor.fetchMyPageInterests(userId: uid)
async let followingsTask = interactor.fetchMyPageFollowings(userId: uid)
async let worksTask = interactor.fetchMyPageWorks(userId: uid)
async let exhibitionsTask = interactor.fetchMyPageExhibitions(userId: uid)
var interests = [ProductModel]()
var followings = [ArtistModel]()
var works = [MyPageWork]()
var exhibitions = [ExhibitionModel]()
(interests, followings, works, exhibitions) = try await (interestsTask, followingsTask, worksTask, exhibitionsTask)
if interests.isEmpty {
isInterestsEmpty = true
if userState == .me {
interests = try await interactor.fetchRecommendInterests()
}
} else {
isInterestsEmpty = false
}
if followings.isEmpty {
isFollowingsEmpty = true
if userState == .me {
followings = try await interactor.fetchRecommendFollowings()
}
} else {
isFollowingsEmpty = false
}
self.interests = interests
self.followings = followings
self.works = works
self.exhibitions = exhibitions
if userState == .me {
let notificationCheckStatus = try await fetchNotificationCheckStatusUseCase.execute()
notificationCheckStatusSubject.send(notificationCheckStatus)
}
if !isInitialLoadFinished {
isInitialLoadFinished = true
// setCategory(.Work)
}
} catch {
print(error.localizedDescription)
}
}
1. 함수명
items는 부적절한 단어입니다. 그냥 모든 정보를 가져온다는 의미로 썼던 네이밍인데, 직관적이지 않습니다.
2. SRP
딱봐도 역할이 많습니다. 유저정보, 유저의 카테고리 정보, 알림 상태 정보를 가져옵니다. 분리할 필요가 있습니다.
3. 주석
필요가 없는 주석입니다. 오히려 기존 코드를 헷갈리게 만들 수 있습니다. 없애주어야 합니다.
이러한 리팩토링 과정을 거쳐서 결과적으로 호출부에서 쪼개진 함수를 호출하는 것이 이번 포스팅의 과제입니다.
리팩토링
1. @Published 대신 Subject 사용

@Published를 남발해서 데이터 변경시 CollectionView가 reload되도록 했었습니다.
문제는 이렇게 하면 데이터를 받아올 때마다 reload가 생긴다는 것입니다. 빈 배열에 담았다가 옮기는 방식을 쓰더라도 최소한 4번은 reload해야합니다.
// viewModel
var interests = [ProductModel]()
var followings = [ArtistModel]()
var works = [MyPageWork]()
var exhibitions = [ExhibitionModel]()
var shouldReloadCollectionViewSubject = PassthroughSubject<Bool, Never>()
// viewController
viewModel.shouldReloadCollectionViewSubject
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] _ in
guard let self else { return }
self.collectionView.reloadData()
}).store(in: &cancellables)
이렇게 해서 데이터를 다 받아왔을 시 send()하도록 변경했습니다.
이제 빈 배열에 데이터를 받았다가 한번에 옮겨주는 코드는 없애도 됩니다.
2. 유저 ID를 가져오는 로직 분리

로그아웃 - nil
내 프로필 - 내 id
다른사람 프로필 -다른사람 id
이렇게 리턴이 되어야 합니다.
private func getUserId() -> Int64? {
switch userState {
case .me:
return Authentication.shared.user?.id
case .other:
return userId
case .loggedOut:
return nil
}
}
3. 카테고리 정보 불러오는 로직 분리
private func fetchAllCategoryContents(userId: Int64?) async throws {
guard let userId else { return }
async let interestsTask = interactor.fetchMyPageInterests(userId: userId)
async let followingsTask = interactor.fetchMyPageFollowings(userId: userId)
async let worksTask = interactor.fetchMyPageWorks(userId: userId)
async let exhibitionsTask = interactor.fetchMyPageExhibitions(userId: userId)
(interests, followings, works, exhibitions) = try await (interestsTask, followingsTask, worksTask, exhibitionsTask)
}
4. 추천 카테고리 데이터 불러오는 로직 분리
관심, 팔로잉 컨텐츠가 없으면 추천 카테고리 컨텐츠를 불러와야 합니다.
private func fetchRecommendInterestContentsIfNeeded() async throws {
isInterestsEmpty = interests.isEmpty
if isInterestsEmpty && userState == .me {
interests = try await interactor.fetchRecommendInterests()
}
}
private func fetchRecommendFollowingContentsIfNeeded() async throws {
isFollowingsEmpty = followings.isEmpty
if isFollowingsEmpty && userState == .me {
followings = try await interactor.fetchRecommendFollowings()
}
}
가독성도 고려해서 최대한 들여쓰기가 없도록 개선했습니다.
5. 알림 확인 여부 로직 분리
private func fetchNotificationCheckStatusIfNeeded() async throws {
if userState == .me {
let notificationCheckStatus = try await fetchNotificationCheckStatusUseCase.execute()
notificationCheckStatusSubject.send(notificationCheckStatus)
}
}
6. initialLoad 확인 로직 분리
private func changeInitialLoadFlagIfNeeded() {
if !isInitialLoadFinished {
isInitialLoadFinished = true
}
}
7. getAllItems 함수명 변경
getAllItems를 setupData로 변경했습니다.
변경후 코드는 다음과 같습니다.
private func getUserId() -> Int64? {
switch userState {
case .me:
return Authentication.shared.user?.id
case .other:
return userId
case .loggedOut:
return nil
}
}
private func fetchAllCategoryContents(userId: Int64?) async throws {
guard let userId else { return }
async let interestsTask = interactor.fetchMyPageInterests(userId: userId)
async let followingsTask = interactor.fetchMyPageFollowings(userId: userId)
async let worksTask = interactor.fetchMyPageWorks(userId: userId)
async let exhibitionsTask = interactor.fetchMyPageExhibitions(userId: userId)
(interests, followings, works, exhibitions) = try await (interestsTask, followingsTask, worksTask, exhibitionsTask)
}
private func fetchRecommendInterestContentsIfNeeded() async throws {
isInterestsEmpty = interests.isEmpty
if isInterestsEmpty && userState == .me {
interests = try await interactor.fetchRecommendInterests()
}
}
private func fetchRecommendFollowingContentsIfNeeded() async throws {
isFollowingsEmpty = followings.isEmpty
if isFollowingsEmpty && userState == .me {
followings = try await interactor.fetchRecommendFollowings()
}
}
private func fetchNotificationCheckStatusIfNeeded() async throws {
if userState == .me {
let notificationCheckStatus = try await fetchNotificationCheckStatusUseCase.execute()
notificationCheckStatusSubject.send(notificationCheckStatus)
}
}
private func changeInitialLoadFlagIfNeeded() {
if !isInitialLoadFinished {
isInitialLoadFinished = true
}
}
func setupData() {
let userId = getUserId()
guard let userId else { return }
Task {
do {
try await fetchAllCategoryContents(userId: userId)
try await fetchRecommendInterestContentsIfNeeded()
try await fetchRecommendFollowingContentsIfNeeded()
try await fetchNotificationCheckStatusIfNeeded()
changeInitialLoadFlagIfNeeded()
shouldReloadCollectionViewSubject.send(true)
} catch {
print(error.localizedDescription)
}
}
}
마무리
사실, setupData라는 함수명이 마음에 들지는 않습니다. 여러 역할을 한다는 의미가 내포된 것 같습니다.
그리고, 중간중간 subject에게 send하라고 하는 것도 함수의 역할에서 어긋난 것 같습니다. 그렇다고 send만을 위한 함수를 따로 만드는 것도 이상해 보입니다 .. ㅠ 그래도 기존 코드보다는 훨씬 보기 좋게 잘 나누었다는 생각이 듭니다.
끝~!
'iOS > Refactoring' 카테고리의 다른 글
| [UIKit/Refactoring] ExhibitionMain의 processExhibitions() 함수 리팩토링 (1) | 2024.09.24 |
|---|---|
| [UIKit/Refactoring] 전시 화면 데이터 처리 관련 로직 개선하기 (0) | 2024.08.17 |
| [UIKit/Refactoring] Domain + Presentation + MVVM 아키텍처 개선 (0) | 2024.08.09 |
| [UIKit/Refactoring] 홈 화면 UX 개선하기 (리사이징, 메모리 캐싱) (0) | 2024.07.27 |
| [UIKit/Refactoring] MyPageProfileEditViewModel 코드 개선하기 (2) (0) | 2024.07.17 |