Feat/#453 공통퀘스트 공감 API 연동#454
Hidden character warning
Conversation
워크스루공통 퀘스트 답변에 좋아요 기능을 추가합니다. 백엔드 API 호출부터 시작하여 도메인 use case를 거쳐 프레젠테이션 계층에서 사용자의 좋아요 탭 이벤트를 처리하고, 결과를 실시간으로 UI에 반영합니다. 변경 사항공통 퀘스트 좋아요 기능
코드 리뷰 예상 난이도🎯 3 (보통) | ⏱️ ~25 분 관련 리뷰어
시인성의 노래 🐰
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift (2)
30-30: ⚡ Quick win사용하지 않는 프로퍼티를 제거하세요.
likeCounts프로퍼티는 Line 115에서 할당되지만 이후 어디에도 사용되지 않습니다. 좋아요 카운트는 이제 서버 응답에서 직접updateUI(likeCount:)로 업데이트되므로, 로컬 상태 저장이 불필요합니다.♻️ 미사용 프로퍼티 제거
private var answerID: Int = 0 - private var likeCounts: Int = 0🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift` at line 30, Remove the unused property `likeCounts` from QuestContentView and delete any assignments to it (e.g., the assignment around where it's set from server response); instead rely solely on `updateUI(likeCount:)` which receives the server-provided value. Search for and remove all references to the `likeCounts` property (declaration and assignments) so the class no longer contains unused local state.
105-127: ⚡ Quick win불필요한 할당을 제거하세요.
Line 115의
self.likeCounts = likeCount할당은likeCounts프로퍼티가 이후 사용되지 않으므로 불필요합니다.♻️ 불필요한 할당 제거
) { self.answerID = answerID - self.likeCounts = likeCount answerContentTextView.do {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift` around lines 105 - 127, In the configure(...) method of QuestContentView remove the unnecessary assignment to self.likeCounts (the line setting self.likeCounts = likeCount) since likeCounts is not used later; simply delete that statement from the configure function so likeCount is only applied to likeCountLabel and the likeButton state remains driven by isLiked.ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift (1)
357-368: ⚡ Quick win파라미터 이름과 resolve된 use case 이름이 불일치합니다.
Line 358에서
fetchCommonQuestDetailUseCase를 resolve하고 있지만, Line 365에서 전달할 때 파라미터 이름이fetchCommonQuestCommentsUseCase입니다. "Detail"과 "Comments"의 불일치가 있어 코드 가독성과 유지보수성을 저하시킵니다. 파라미터 이름을 일관되게 맞춰주세요.♻️ 파라미터 이름 일관성 개선
return CommonQuestHistoryViewModel( - fetchCommonQuestCommentsUseCase: fetchCommonQuestDetailUseCase, + fetchCommonQuestDetailUseCase: fetchCommonQuestDetailUseCase, postCommonQuestLikeUseCase: postCommonQuestLikeUseCase🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift` around lines 357 - 368, The resolved variable name fetchCommonQuestDetailUseCase does not match the parameter name passed into CommonQuestHistoryViewModel (fetchCommonQuestCommentsUseCase); update the registration to use consistent names: either resolve and name the variable fetchCommonQuestCommentsUseCase or change the parameter passed to fetchCommonQuestDetailUseCase so the identifier matches the intended use case. Edit the DIContainer.shared.register block and adjust the resolve call and the constructor argument for CommonQuestHistoryViewModel accordingly (symbols: DIContainer.shared.register, CommonQuestHistoryViewModel, fetchCommonQuestDetailUseCase, fetchCommonQuestCommentsUseCase).ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift (1)
26-26: ⚡ Quick win
let으로 변경하여 불변성을 유지하세요.
questContentView를var로 선언할 필요가 없습니다. ViewController가 delegate를 설정하기 위해 접근만 하면 되므로private(set) let으로 충분합니다. 뷰 객체 자체를 재할당할 이유가 없습니다.♻️ 불변 프로퍼티로 변경
- private(set) var questContentView = QuestContentView() + private(set) let questContentView = QuestContentView()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift` at line 26, questContentView은 재할당이 필요 없으니 불변으로 선언하세요: 현재 선언된 private(set) var questContentView = QuestContentView()를 private(set) let questContentView = QuestContentView()로 변경하면 됩니다; QuestContentView 인스턴스의 프로퍼티(예: delegate) 변경은 여전히 가능하니 재할당만 막도록 선언을 바꾸세요.ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift (1)
295-295: 💤 Low value프로덕션 코드에서 디버그 로그를 제거하는 것을 고려하세요.
likeButtonDidTap메서드에 디버그 로그가 남아있습니다. 이 로그가 개발 중 디버깅 목적이었다면 제거하거나, 필요시 더 의미 있는 로그 레벨로 변경하는 것을 권장합니다.♻️ 제안하는 수정
func likeButtonDidTap(answerID: Int) { - ByeBooLogger.debug("answerID: \(answerID)") viewModel.action(.likeButtonDidTap(answerID: answerID)) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift` at line 295, likeButtonDidTap 메서드에 남아 있는 개발용 디버그 로그 ByeBooLogger.debug("answerID: \(answerID)")를 제거하거나 프로덕션에 적합한 로그 레벨로 변경하세요; 구체적으로 likeButtonDidTap 내부의 ByeBooLogger.debug 호출을 삭제하거나 info/trace 등 더 적절한 레벨로 바꾸고 메시지를 더 의미 있게(예: "Like button tapped for answerID: {id}") 수정해 배포 시 불필요한 디버그 출력이 남지 않도록 하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift`:
- Line 2: Header comment filename casing doesn't match the actual file/type
name; update the top-of-file header in PostCommonQuestLikeResponseDTO.swift so
the commented filename reads "PostCommonQuestLikeResponseDTO.swift" to match the
Swift file/type naming convention (e.g., the struct/class
PostCommonQuestLikeResponseDTO).
In `@ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift`:
- Around line 78-84: postCommonQuestLikes currently ignores
PostCommonQuestLikeResponseDTO.isLiked and returns only likeCount, causing UI to
diverge from server state; change CommonQuestRepository.postCommonQuestLikes to
return both likeCount and isLiked (e.g. a tuple or DTO) by decoding
PostCommonQuestLikeResponseDTO and propagating its isLiked, update the
PostCommonQuestLikeUseCase return type and the ViewModel subjects/bindings to
accept and publish the isLiked value, and modify
QuestContentView.likeButtonDidTap to avoid unconditionally toggling the button
(instead update the UI only after success or roll back to server isLiked on
failure) so the local state remains consistent with the server response.
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`:
- Line 13: The cancellables property is declared immutable but must be mutable
to use Combine’s `.store(in: &cancellables)`; change the declaration of
`cancellables` from `let cancellables = Set<AnyCancellable>()` to a mutable
`var` in both `CommonQuestViewModel` and `CommonQuestMyAnswerViewModel` so
Combine subscriptions can be stored (refer to the `cancellables` property and
usage of `.store(in: &cancellables)` in those classes).
---
Nitpick comments:
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift`:
- Line 30: Remove the unused property `likeCounts` from QuestContentView and
delete any assignments to it (e.g., the assignment around where it's set from
server response); instead rely solely on `updateUI(likeCount:)` which receives
the server-provided value. Search for and remove all references to the
`likeCounts` property (declaration and assignments) so the class no longer
contains unused local state.
- Around line 105-127: In the configure(...) method of QuestContentView remove
the unnecessary assignment to self.likeCounts (the line setting self.likeCounts
= likeCount) since likeCounts is not used later; simply delete that statement
from the configure function so likeCount is only applied to likeCountLabel and
the likeButton state remains driven by isLiked.
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift`:
- Line 26: questContentView은 재할당이 필요 없으니 불변으로 선언하세요: 현재 선언된 private(set) var
questContentView = QuestContentView()를 private(set) let questContentView =
QuestContentView()로 변경하면 됩니다; QuestContentView 인스턴스의 프로퍼티(예: delegate) 변경은 여전히
가능하니 재할당만 막도록 선언을 바꾸세요.
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift`:
- Line 295: likeButtonDidTap 메서드에 남아 있는 개발용 디버그 로그 ByeBooLogger.debug("answerID:
\(answerID)")를 제거하거나 프로덕션에 적합한 로그 레벨로 변경하세요; 구체적으로 likeButtonDidTap 내부의
ByeBooLogger.debug 호출을 삭제하거나 info/trace 등 더 적절한 레벨로 바꾸고 메시지를 더 의미 있게(예: "Like
button tapped for answerID: {id}") 수정해 배포 시 불필요한 디버그 출력이 남지 않도록 하세요.
In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift`:
- Around line 357-368: The resolved variable name fetchCommonQuestDetailUseCase
does not match the parameter name passed into CommonQuestHistoryViewModel
(fetchCommonQuestCommentsUseCase); update the registration to use consistent
names: either resolve and name the variable fetchCommonQuestCommentsUseCase or
change the parameter passed to fetchCommonQuestDetailUseCase so the identifier
matches the intended use case. Edit the DIContainer.shared.register block and
adjust the resolve call and the constructor argument for
CommonQuestHistoryViewModel accordingly (symbols: DIContainer.shared.register,
CommonQuestHistoryViewModel, fetchCommonQuestDetailUseCase,
fetchCommonQuestCommentsUseCase).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 26a2d730-550e-42c8-bdef-88a522c8c041
📒 Files selected for processing (17)
ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swiftByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swiftByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swiftByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swiftByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swiftByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift
| @@ -0,0 +1,13 @@ | |||
| // | |||
| // postCommonQuestLikeResponseDTO.swift | |||
There was a problem hiding this comment.
파일명 표기 불일치
헤더 주석의 파일명이 실제 파일명과 대소문자가 다릅니다 (postCommonQuestLikeResponseDTO.swift → PostCommonQuestLikeResponseDTO.swift). Swift 컨벤션에 따라 타입명과 일치하는 파일명을 사용하므로, 헤더 주석도 PostCommonQuestLikeResponseDTO.swift로 수정해야 합니다.
수정 제안
//
-// postCommonQuestLikeResponseDTO.swift
+// PostCommonQuestLikeResponseDTO.swift
// ByeBoo-iOS📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // postCommonQuestLikeResponseDTO.swift | |
| // | |
| // PostCommonQuestLikeResponseDTO.swift | |
| // ByeBoo-iOS |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift` at
line 2, Header comment filename casing doesn't match the actual file/type name;
update the top-of-file header in PostCommonQuestLikeResponseDTO.swift so the
commented filename reads "PostCommonQuestLikeResponseDTO.swift" to match the
Swift file/type naming convention (e.g., the struct/class
PostCommonQuestLikeResponseDTO).
| @@ -12,7 +12,10 @@ final class CommonQuestViewModel { | |||
|
|
|||
| private let cancellables = Set<AnyCancellable>() | |||
There was a problem hiding this comment.
cancellables 프로퍼티는 두 ViewModel 모두에서 mutable하게 선언되어야 합니다.
CommonQuestViewModel과 CommonQuestMyAnswerViewModel 모두 cancellables를 let으로 선언했지만, Combine 구독을 저장하려면 var로 선언해야 합니다. 이는 일반적인 Combine 사용 패턴에서 .store(in: &cancellables)를 사용하기 위해 필요합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`
at line 13, The cancellables property is declared immutable but must be mutable
to use Combine’s `.store(in: &cancellables)`; change the declaration of
`cancellables` from `let cancellables = Set<AnyCancellable>()` to a mutable
`var` in both `CommonQuestViewModel` and `CommonQuestMyAnswerViewModel` so
Combine subscriptions can be stored (refer to the `cancellables` property and
usage of `.store(in: &cancellables)` in those classes).
juri123123
left a comment
There was a problem hiding this comment.
isLike를 전체 조회에서 관리하는 것 보다는 각 post마다 관리하는게 더 유지보수하기 좋을 것 같아요 !
| private func postCommonQuestLike(answerID: Int) { | ||
| Task { | ||
| do { | ||
| let likeCount = try await postCommonQuestLikeUseCase.execute(answerID: answerID) | ||
| likeCountSubject.send(.success((answerID: answerID, likeCount: likeCount))) | ||
| } catch (let error as ByeBooError) { | ||
| likeCountSubject.send(.failure(error)) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
요기 task 취소할 수 있도록 변수로 관리해줘도 좋을 듯 싶습니다 !
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift (1)
29-30:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win미사용 프로퍼티
likeCounts제거 필요Line 30의
likeCounts프로퍼티는 Line 115에서 설정되지만 이후 어디에서도 읽히지 않습니다. 좋아요 수는likeCountLabel에 직접 설정되므로 이 프로퍼티는 불필요합니다.♻️ 제안하는 수정
weak var delegate: CommonQuestLikeCommentProtocol? private var answerID: Int = 0 -private var likeCounts: Int = 0그리고 Line 115도 제거:
) { self.answerID = answerID - self.likeCounts = likeCount answerContentTextView.do {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift` around lines 29 - 30, Remove the unused property `likeCounts` from the QuestContentView class. Delete the private variable declaration `likeCounts: Int = 0` at line 30, and also remove the line that assigns a value to `likeCounts` at line 115. Since the like count is set directly to the `likeCountLabel` and the `likeCounts` property is never read elsewhere in the code, maintaining this intermediate variable is unnecessary and creates code clutter.
🧹 Nitpick comments (1)
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift (1)
71-88: ⚖️ Poor tradeoff세 ViewModel에서 좋아요 처리 로직 중복.
postCommonQuestLike(answerID:)메서드가 세 ViewModel에서 거의 동일하게 구현되어 있습니다. 현재는 private 메서드이므로 허용 가능하지만, 향후 유지보수를 위해 공통 프로토콜 extension이나 base class로 리팩토링을 고려해볼 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift` around lines 71 - 88, The `postCommonQuestLike(answerID:)` method is duplicated across three ViewModels: CommonQuestViewModel.swift (71-88), CommonQuestMyAnswerViewModel.swift (72-89), and CommonQuestHistoryViewModel.swift (86-103). Extract this shared logic into a common location by creating a protocol with a default implementation in an extension (or using a base class if other shared functionality exists). Define the method in the protocol extension to handle the Task cancellation, error handling, and Subject updates using the shared pattern. Then remove the duplicated `postCommonQuestLike(answerID:)` implementations from all three ViewModel files and have them conform to the protocol instead, ensuring all three ViewModels call the shared implementation which will reduce code duplication and improve future maintainability.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`:
- Around line 79-82: The error handling in the catch blocks across three
ViewModel files is silently ignoring non-ByeBooError exceptions, causing the UI
to hang indefinitely. In CommonQuestViewModel.swift (lines 79-82),
CommonQuestMyAnswerViewModel.swift (lines 80-83), and
CommonQuestHistoryViewModel.swift (lines 94-97), replace the early return with
logic that logs the unknown error, wraps it as a ByeBooError, and sends the
failure to the subject. For any error that cannot be cast to ByeBooError,
explicitly handle it by creating a ByeBooError wrapper and calling the
appropriate subject.send method with the wrapped error to ensure the UI receives
proper error notification instead of hanging indefinitely.
- Around line 71-88: A race condition exists in the postCommonQuestLike method
across three ViewModel files where likeTasks[answerID] = nil is executed
unconditionally after the do-catch block, allowing cancelled Tasks to remove
entries from the dictionary before new Tasks can cancel them. In
CommonQuestViewModel.swift at lines 71-88, move the likeTasks[answerID] = nil
statement (currently at line 86) inside the guard !Task.isCancelled block so
that only non-cancelled Tasks clean up the dictionary. Apply the identical fix
to CommonQuestMyAnswerViewModel.swift at lines 72-89 (move line 87's
likeTasks[answerID] = nil into the cancellation guard), and to
CommonQuestHistoryViewModel.swift at lines 86-103 (move line 101's
likeTasks[answerID] = nil into the cancellation guard).
---
Outside diff comments:
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift`:
- Around line 29-30: Remove the unused property `likeCounts` from the
QuestContentView class. Delete the private variable declaration `likeCounts: Int
= 0` at line 30, and also remove the line that assigns a value to `likeCounts`
at line 115. Since the like count is set directly to the `likeCountLabel` and
the `likeCounts` property is never read elsewhere in the code, maintaining this
intermediate variable is unnecessary and creates code clutter.
---
Nitpick comments:
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`:
- Around line 71-88: The `postCommonQuestLike(answerID:)` method is duplicated
across three ViewModels: CommonQuestViewModel.swift (71-88),
CommonQuestMyAnswerViewModel.swift (72-89), and
CommonQuestHistoryViewModel.swift (86-103). Extract this shared logic into a
common location by creating a protocol with a default implementation in an
extension (or using a base class if other shared functionality exists). Define
the method in the protocol extension to handle the Task cancellation, error
handling, and Subject updates using the shared pattern. Then remove the
duplicated `postCommonQuestLike(answerID:)` implementations from all three
ViewModel files and have them conform to the protocol instead, ensuring all
three ViewModels call the shared implementation which will reduce code
duplication and improve future maintainability.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3b101713-18e0-48d8-b8ba-cdb96628a276
📒 Files selected for processing (12)
ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swiftByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swiftByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestLikeEntity.swiftByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swiftByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift
🚧 Files skipped from review as they are similar to previous changes (5)
- ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift
- ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift
- ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift
- ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift
- ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift
| private func postCommonQuestLike(answerID: Int) { | ||
| likeTasks[answerID]?.cancel() | ||
|
|
||
| likeTasks[answerID] = Task { | ||
| do { | ||
| let entity = try await postCommonQuestLikeUseCase.execute(answerID: answerID) | ||
| guard !Task.isCancelled else { return } | ||
| likeCountSubject.send(.success((answerID: answerID, entity))) | ||
| } catch { | ||
| guard let error = error as? ByeBooError else { | ||
| return | ||
| } | ||
| guard !Task.isCancelled else { return } | ||
| likeCountSubject.send(.failure(error)) | ||
| } | ||
| likeTasks[answerID] = nil | ||
| } | ||
| } |
There was a problem hiding this comment.
세 ViewModel 모두에서 Task 정리 경쟁 조건 발생.
세 파일 모두 postCommonQuestLike(answerID:) 메서드에서 likeTasks[answerID] = nil이 do-catch 블록 외부에 위치하여, Task가 취소된 후에도 무조건 실행됩니다. 사용자가 좋아요 버튼을 빠르게 연타하면 이전 Task가 새 Task를 딕셔너리에서 제거하여 취소가 불가능해집니다.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift#L71-L88: L86의likeTasks[answerID] = nil을if !Task.isCancelled블록 안으로 이동하여 취소된 Task가 정리하지 않도록 수정ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift#L72-L89: L87의likeTasks[answerID] = nil을 동일하게 취소 확인 블록 안으로 이동ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift#L86-L103: L101의likeTasks[answerID] = nil을 동일하게 취소 확인 블록 안으로 이동
📍 Affects 3 files
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift#L71-L88(this comment)ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift#L72-L89ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift#L86-L103
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`
around lines 71 - 88, A race condition exists in the postCommonQuestLike method
across three ViewModel files where likeTasks[answerID] = nil is executed
unconditionally after the do-catch block, allowing cancelled Tasks to remove
entries from the dictionary before new Tasks can cancel them. In
CommonQuestViewModel.swift at lines 71-88, move the likeTasks[answerID] = nil
statement (currently at line 86) inside the guard !Task.isCancelled block so
that only non-cancelled Tasks clean up the dictionary. Apply the identical fix
to CommonQuestMyAnswerViewModel.swift at lines 72-89 (move line 87's
likeTasks[answerID] = nil into the cancellation guard), and to
CommonQuestHistoryViewModel.swift at lines 86-103 (move line 101's
likeTasks[answerID] = nil into the cancellation guard).
| } catch { | ||
| guard let error = error as? ByeBooError else { | ||
| return | ||
| } |
There was a problem hiding this comment.
세 ViewModel 모두에서 ByeBooError가 아닌 에러 무시.
세 파일 모두 ByeBooError로 캐스팅할 수 없는 에러를 조용히 무시하여 subject에 실패를 전송하지 않습니다. 이로 인해 UI가 무한정 응답을 기다리게 됩니다.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift#L79-L82: 알 수 없는 에러를 로깅하고 ByeBooError로 래핑하여 실패 전송ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift#L80-L83: 동일하게 에러 로깅 및 래핑 추가ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift#L94-L97: 동일하게 에러 로깅 및 래핑 추가
📍 Affects 3 files
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift#L79-L82(this comment)ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift#L80-L83ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift#L94-L97
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`
around lines 79 - 82, The error handling in the catch blocks across three
ViewModel files is silently ignoring non-ByeBooError exceptions, causing the UI
to hang indefinitely. In CommonQuestViewModel.swift (lines 79-82),
CommonQuestMyAnswerViewModel.swift (lines 80-83), and
CommonQuestHistoryViewModel.swift (lines 94-97), replace the early return with
logic that logs the unknown error, wraps it as a ByeBooError, and sends the
failure to the subject. For any error that cannot be cast to ByeBooError,
explicitly handle it by creating a ByeBooError wrapper and calling the
appropriate subject.send method with the wrapped error to ensure the UI receives
proper error notification instead of hanging indefinitely.
dev-domo
left a comment
There was a problem hiding this comment.
리뷰가 늦어져 죄송합니다! 코멘트 확인 부탁드릴게요~
| } | ||
|
|
||
| private func postCommonQuestLike(answerID: Int) { | ||
| likeTasks[answerID]?.cancel() |
There was a problem hiding this comment.
저도 헷갈리는 부분이라 체크 한 번 부탁드려요! 현재 코드에서 cancel 메서드는 호출하고 있지만 실제로 취소 에러를 던지는 부분은 없는 것 같은데, 그럼 취소를 해도 API 호출 작업이 끝까지 실행되지 않을까요?
| @@ -12,7 +12,10 @@ final class CommonQuestViewModel { | |||
|
|
|||
| private let cancellables = Set<AnyCancellable>() | |||
There was a problem hiding this comment.
이 부분은 코드래빗 리뷰 반영해야 할 것 같아요!
private var cancellables
🔗 연결된 이슈
📄 작업 내용
Summary by CodeRabbit
릴리스 노트