-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 프로필 중간 구현 #190
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
[Feat] 프로필 중간 구현 #190
Conversation
# Conflicts: # Wable-iOS.xcodeproj/project.pbxproj
…o feat/#182-profile
- 기존의 ViewModelType을 따르도록 함.
WalkthroughThis update restructures and expands the profile, withdrawal, account info, and alarm setting features in the Wable-iOS project. It introduces new view controllers, view models, and UI components for profile management, withdrawal flows, and account settings. Several files are renamed, removed, or refactored, with new use cases and repository interfaces adopting Swift concurrency and Combine for asynchronous operations. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant TabBarController
participant MyProfileViewController
participant MyProfileViewModel
participant ProfileRepository
participant CommentRepository
participant ContentRepository
User->>TabBarController: Tap Profile Tab
TabBarController->>MyProfileViewController: Show Profile Screen
MyProfileViewController->>MyProfileViewModel: Load Profile Data
MyProfileViewModel->>ProfileRepository: Fetch UserProfile
MyProfileViewModel->>ContentRepository: Fetch UserContentList
MyProfileViewModel->>CommentRepository: Fetch UserCommentList
ProfileRepository-->>MyProfileViewModel: UserProfile
ContentRepository-->>MyProfileViewModel: UserContent[]
CommentRepository-->>MyProfileViewModel: UserComment[]
MyProfileViewModel-->>MyProfileViewController: ProfileViewItem (profile, content, comment)
MyProfileViewController-->>User: Display Profile, Posts, Comments
sequenceDiagram
participant User
participant MyProfileViewController
participant AccountInfoViewController
participant AccountInfoViewModel
participant FetchAccountInfoUseCase
participant ProfileRepository
User->>MyProfileViewController: Tap Account Info
MyProfileViewController->>AccountInfoViewController: Show Account Info Screen
AccountInfoViewController->>AccountInfoViewModel: viewDidLoad()
AccountInfoViewModel->>FetchAccountInfoUseCase: execute()
FetchAccountInfoUseCase->>ProfileRepository: fetchAccountInfo()
ProfileRepository-->>FetchAccountInfoUseCase: AccountInfo
FetchAccountInfoUseCase-->>AccountInfoViewModel: AccountInfo
AccountInfoViewModel-->>AccountInfoViewController: items
AccountInfoViewController-->>User: Display Account Info
sequenceDiagram
participant User
participant AccountInfoViewController
participant WithdrawalReasonViewController
participant WithdrawalReasonViewModel
participant WithdrawalGuideViewController
participant WithdrawalGuideViewModel
participant WithdrawUseCase
participant RemoveUserSessionUseCase
participant AccountRepository
participant UserSessionRepository
User->>AccountInfoViewController: Tap "Delete Account"
AccountInfoViewController->>WithdrawalReasonViewController: Show Withdrawal Reasons
User->>WithdrawalReasonViewController: Select Reasons, Tap Next
WithdrawalReasonViewController->>WithdrawalGuideViewController: Show Withdrawal Guide
User->>WithdrawalGuideViewController: Agree, Tap Next, Confirm
WithdrawalGuideViewController->>WithdrawalGuideViewModel: Withdraw
WithdrawalGuideViewModel->>WithdrawUseCase: execute(reasons)
WithdrawUseCase->>AccountRepository: deleteAccount(reasons)
AccountRepository-->>WithdrawUseCase: Success
WithdrawUseCase-->>WithdrawalGuideViewModel: Success
WithdrawalGuideViewModel->>RemoveUserSessionUseCase: removeUserSession()
RemoveUserSessionUseCase->>UserSessionRepository: Remove Session
UserSessionRepository-->>RemoveUserSessionUseCase: Success
WithdrawalGuideViewModel-->>WithdrawalGuideViewController: isWithdrawSuccess
WithdrawalGuideViewController-->>User: Show Login Screen
Assessment against linked issues
Poem
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback. Note ⚡️ Faster reviews with cachingCodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure ✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 22
🔭 Outside diff range comments (1)
Wable-iOS/Presentation/Onboarding/ViewController/AgreementViewController.swift (1)
140-217: 🛠️ Refactor suggestionConsider migrating this Combine implementation to async/await
Since the PR objectives mention introducing async/await syntax to improve code readability and maintainability, consider refactoring this nested Combine chain to use the new async/await pattern. This would make the code more linear and easier to follow.
Here's how you might refactor the
nextButtonDidTapmethod:@objc func nextButtonDidTap() { Task { do { guard let userSession = try await userInformationUseCase.fetchActiveUserInfoAsync() else { return } try await profileUseCase.executeAsync( profile: UserProfile( user: User( id: userSession.id, nickname: nickname, profileURL: userSession.profileURL, fanTeam: LCKTeam(rawValue: lckTeam) ), introduction: "", ghostCount: 0, lckYears: lckYear, userLevel: 1 ), isPushAlarmAllowed: rootView.marketingAgreementItemView.checkButton.isSelected, isAlarmAllowed: rootView.marketingAgreementItemView.checkButton.isSelected, image: profileImage, defaultProfileType: defaultImage ) try await updateFCMTokenUseCase.executeAsync(nickname: nickname) try await userInformationUseCase.updateUserSessionAsync( userID: userSession.id, nickname: userSession.nickname, profileURL: userSession.profileURL, isPushAlarmAllowed: userSession.isPushAlarmAllowed, isAdmin: userSession.isAdmin, isAutoLoginEnabled: true, notificationBadgeCount: userSession.notificationBadgeCount ) WableLogger.log("세션 저장 완료", for: .debug) await MainActor.run { guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let loginViewController = windowScene.windows.first?.rootViewController else { return } let tabBarController = TabBarController() dismiss(animated: false) { loginViewController.present(tabBarController, animated: true) { let noticeViewController = WableSheetViewController( title: "와블과 함께해 주셔서 감사합니다!", message: "\(self.nickname)님\n와블의 일원이 되신 것을 환영해요.\nLCK 함께 보며 같이 즐겨요 :)" ) noticeViewController.addAction(.init(title: "와블 즐기러 가기", style: .primary)) tabBarController.present(noticeViewController, animated: true) } } } } catch { // Handle errors appropriately WableLogger.log("Profile creation error: \(error)", for: .error) } } }Note: You'll need to ensure that the use cases have async variants of their methods.
🧹 Nitpick comments (48)
Wable-iOS/Presentation/WableComponent/Navigation/NavigationView.swift (1)
275-275: Navigation title now consistently updates both title labels.The
setNavigationTitle(text:)method now updates bothhubTitleLabelandpageTitleLabel, ensuring consistent navigation title styling across different view types.Consider adding conditional updates based on the current navigation type to avoid setting titles for hidden labels:
func setNavigationTitle(text: String) { hubTitleLabel.attributedText = text.pretendardString(with: .head2) pageTitleLabel.attributedText = text.pretendardString(with: .head2) + + // Alternatively, consider conditionally updating labels based on navigation type + // if case .hub = type { + // hubTitleLabel.attributedText = text.pretendardString(with: .head2) + // } else { + // pageTitleLabel.attributedText = text.pretendardString(with: .head2) + // } }Wable-iOS/Domain/RepositoryInterface/ProfileRepository.swift (1)
13-13: Good adoption of async/await for improved readability.The change from Combine's
AnyPublisher<AccountInfo, WableError>to Swift's nativeasync throws -> AccountInfopattern is an excellent improvement that aligns with modern Swift concurrency practices. The method name change fromfetchUserInfo()tofetchAccountInfo()also provides better clarity about what's being fetched.Consider creating a migration plan to eventually convert other repository methods (lines 14-25) from Combine to async/await for API consistency. This would further simplify your codebase by standardizing on a single concurrency model.
Wable-iOS/Data/Mapper/ProfileMapper.swift (1)
19-20: Improved social platform extraction logic.The updated code correctly handles situations where the social platform string contains additional information by extracting just the first word. This is a good fix that adds robustness to the mapping logic.
Consider a slightly more concise implementation:
-let splitKeyword = response.socialPlatform.split(separator: " ").map { "\($0)" }.first -let socialPlatform = SocialPlatform(rawValue: splitKeyword ?? response.socialPlatform) +let platformName = response.socialPlatform.components(separatedBy: " ").first ?? response.socialPlatform +let socialPlatform = SocialPlatform(rawValue: platformName)Wable-iOS/Presentation/Profile/ProfileViewItem.swift (1)
10-14: Consider renaming thecommentproperty to plural formThe property
commentcontains an array ofUserCommentobjects, so using the plural formcommentswould be more consistent with Swift naming conventions and make the code more readable.struct ProfileViewItem { var profileInfo: UserProfile? var content: [UserContent] - var comment: [UserComment] + var comments: [UserComment] }Wable-iOS/Presentation/Profile/AccountInfo/Model/AccountInfoCellItem.swift (1)
10-20: Clean model design with appropriate Hashable conformance.The
AccountInfoCellItemstruct has a clean design with clear properties and a good initializer pattern that provides a default value forisUserInteractive. The Hashable conformance is appropriate for use with diffable data sources.Consider using localization for user-facing strings when you populate this model to support multiple languages in the future.
Wable-iOS/Data/Mapper/ErrorMapper.swift (1)
28-44: Good addition to support async/await error handling.The new
ErrorMapperenum effectively mirrors the existing Publisher extension's error mapping logic but makes it available for use in async/await contexts. This supports the PR's goal of transitioning from Combine to async/await for improved code readability.Consider extending the mapper to handle different types of non-NetworkError errors more specifically, rather than mapping all of them to
.unknownError. This would provide more granular error information to the user.enum ErrorMapper { static func map(_ error: Error) -> WableError { WableLogger.log(error.localizedDescription, for: .network) WableLogger.log("\(error)", for: .network) if let networkError = error as? NetworkError { switch networkError { case .statusError(_, let message): return WableError(rawValue: message) ?? .networkError default: return .unknownError } } + + // Handle other specific error types if needed + if let decodingError = error as? DecodingError { + return .decodingError + } return .unknownError } }Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/Subview/WithdrawalGuideDescriptionView.swift (1)
13-36: Well-structured UI component with clean configuration API.The
WithdrawalGuideDescriptionViewis a well-designed UI component with proper initialization and a clean configuration API. Theconfigure(description:)method provides a good way to update the label's content.Consider extracting the hardcoded string "설명" from line 17 to a constant or use localization to support multiple languages:
private let descriptionLabel = UILabel().then { - $0.attributedText = "설명".pretendardString(with: .body2) + $0.attributedText = NSLocalizedString("description_placeholder", comment: "Default text for description label").pretendardString(with: .body2) $0.textColor = .gray800 $0.numberOfLines = 0 }Wable-iOS/Presentation/Profile/AlarmSetting/ViewModel/AlarmSettingViewModel.swift (1)
15-20: Well-implemented async notification authorization check.The
checkAlarmAuthorizationmethod appropriately uses Swift concurrency withTaskto perform the async operation of checking notification settings. The code is clean and focused on a single responsibility.One suggestion to consider:
Consider adding error handling if there's any possibility the notification settings check could fail:
func checkAlarmAuthorization() { Task { - let settings = await UNUserNotificationCenter.current().notificationSettings() - isAuthorized = settings.authorizationStatus == .authorized + do { + let settings = await UNUserNotificationCenter.current().notificationSettings() + isAuthorized = settings.authorizationStatus == .authorized + } catch { + // Handle any potential errors + isAuthorized = false + // Optionally log the error + } } }However, since the
notificationSettings()method doesn't throw, the current implementation is acceptable.Wable-iOS/Domain/UseCase/Profile/FetchAccountInfoUseCase.swift (1)
8-9: Remove unnecessary Combine import.The
Combineframework is imported but not used anywhere in this file since the implementation uses Swift's native async/await pattern instead of Combine publishers.-import Combine import FoundationWable-iOS/Presentation/Onboarding/View/ProfileRegisterView.swift (3)
191-200: Potential layout issue with constraint updates.The method updates constraints that were already set in
setupConstraint(), which might cause layout issues. Consider invalidating the existing constraints before updating them or refactoring the common layout code to avoid duplication.func configureProfileView(profileImageURL: URL? = .none) { + // Remove existing constraints before updating + titleLabel.snp.removeConstraints() + profileImageView.snp.removeConstraints() + titleLabel.snp.updateConstraints { make in make.top.equalToSuperview().offset(28) } profileImageView.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(62) make.centerX.equalToSuperview() make.size.equalTo(166.adjustedWidth) }
202-207: Consider using proper localization for text strings.The hard-coded Korean text strings should be moved to a localization file for easier maintenance and future internationalization support.
Also, the button configuration updates are mixing styles. Consider creating a method for resetting button to completed state.
214-226: Extract hardcoded image references to constants.The switch statement contains hardcoded string values that could be defined as constants. Additionally, consider creating a dedicated method for profile image selection to avoid duplicating logic between
configureViewandconfigureProfileView.+private enum ProfileImageType { + static let purple = "PURPLE" + static let green = "GREEN" + static let blue = "BLUE" +} switch profileImageURL.absoluteString { -case "PURPLE": +case ProfileImageType.purple: profileImageView.image = .imgProfilePurple -case "GREEN": +case ProfileImageType.green: profileImageView.image = .imgProfileGreen -case "BLUE": +case ProfileImageType.blue: profileImageView.image = .imgProfileBlue default: profileImageView.kf.setImage( with: profileImageURL, placeholder: [UIImage.imgProfilePurple, UIImage.imgProfileBlue, UIImage.imgProfileGreen].randomElement() ) }Wable-iOS/Domain/UseCase/Profile/RemoveUserSessionUseCase.swift (2)
21-28: Consider using async/await for consistency.This method is implemented synchronously, while other use cases in the PR are moving toward async/await. Consider making it consistent with the project's direction.
-func removeUserSession() { +func removeUserSession() async { guard let userID = repository.fetchActiveUserID() else { WableLogger.log("유저 아이디를 찾을 수 없음.", for: .debug) return } - repository.removeUserSession(forUserID: userID) + await repository.removeUserSession(forUserID: userID) }This assumes that the repository methods would also be updated to be asynchronous.
23-23: Localize log messages for consistency.The log message is in Korean, which might be inconsistent with other log messages. Consider using a consistent language strategy for logs across the application.
-WableLogger.log("유저 아이디를 찾을 수 없음.", for: .debug) +WableLogger.log("User ID not found.", for: .debug)Wable-iOS/Domain/UseCase/Profile/FetchUserProfileUseCase.swift (1)
22-30: Redundant map operator in publisher chain.The
.map { $0 }operation doesn't transform the data and can be removed without changing the behavior.func execute(userID: Int) -> AnyPublisher<UserProfile?, WableError> { guard userID > .zero else { return .fail(.notFoundMember) } return repository.fetchUserProfile(memberID: userID) - .map { $0 } .eraseToAnyPublisher() }Wable-iOS/Presentation/Profile/ProfileSegmentedHeaderView.swift (1)
51-54: Consider adding proper documentation.Adding documentation comments for the
segmentDidChangemethod would help clarify its purpose, especially for maintaining closure-based callback patterns.@objc private func segmentDidChange() { + // Notify listeners about the segment change through the closure segmentDidChangeClosure?(segmentedControl.selectedSegmentIndex) }Wable-iOS/Presentation/TabBar/TabBarController.swift (2)
80-103: Complex dependency graph initialized inline.The initialization of the profile view controller includes a deep tree of dependencies created inline, which can make the code harder to maintain. Consider moving this initialization logic to a factory or dependency injection container.
- private let profileViewController = MyProfileViewController( - viewModel: .init( - userinformationUseCase: FetchUserInformationUseCase( - repository: UserSessionRepositoryImpl( - userDefaults: UserDefaultsStorage(jsonEncoder: .init(), jsonDecoder: .init()) - ) - ), - fetchUserProfileUseCase: FetchUserProfileUseCaseImpl( - repository: ProfileRepositoryImpl() - ), - fetchUserCommentListUseCase: FetchUserCommentListUseCaseImpl( - repository: CommentRepositoryImpl() - ), - fetchUserContentListUseCase: FetchUserContentUseCaseImpl( - repository: ContentRepositoryImpl() - ), - removeUserSessionUseCase: RemoveUserSessionUseCaseImpl( - repository: UserSessionRepositoryImpl( - userDefaults: UserDefaultsStorage( - jsonEncoder: .init(), jsonDecoder: .init() - ) - ) - ) - ) - ) + private let profileViewController = MyProfileViewControllerFactory.create()You could then define the factory in a separate file:
// MyProfileViewControllerFactory.swift struct MyProfileViewControllerFactory { static func create() -> MyProfileViewController { let userDefaults = UserDefaultsStorage(jsonEncoder: .init(), jsonDecoder: .init()) let userSessionRepository = UserSessionRepositoryImpl(userDefaults: userDefaults) let viewModel = MyProfileViewModel( userinformationUseCase: FetchUserInformationUseCase(repository: userSessionRepository), fetchUserProfileUseCase: FetchUserProfileUseCaseImpl(repository: ProfileRepositoryImpl()), fetchUserCommentListUseCase: FetchUserCommentListUseCaseImpl(repository: CommentRepositoryImpl()), fetchUserContentListUseCase: FetchUserContentUseCaseImpl(repository: ContentRepositoryImpl()), removeUserSessionUseCase: RemoveUserSessionUseCaseImpl(repository: userSessionRepository) ) return MyProfileViewController(viewModel: viewModel) } }
84-85: Redundant UserDefaultsStorage initialization.You're creating two separate instances of UserDefaultsStorage with the same configuration. Consider reusing the same instance to avoid duplicating shared resources.
private let profileViewController = MyProfileViewController( viewModel: .init( userinformationUseCase: FetchUserInformationUseCase( repository: UserSessionRepositoryImpl( - userDefaults: UserDefaultsStorage(jsonEncoder: .init(), jsonDecoder: .init()) + userDefaults: sharedUserDefaultsStorage ) ), // ... other use cases ... removeUserSessionUseCase: RemoveUserSessionUseCaseImpl( repository: UserSessionRepositoryImpl( userDefaults: UserDefaultsStorage( - jsonEncoder: .init(), jsonDecoder: .init() + userDefaults: sharedUserDefaultsStorage ) ) ) ) )Where
sharedUserDefaultsStoragecould be a private property of the class:private let sharedUserDefaultsStorage = UserDefaultsStorage(jsonEncoder: .init(), jsonDecoder: .init())Also applies to: 98-100
Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift (3)
2-2: Filename in comment header doesn't match actual filenameThe file header comment refers to
FetchUserContentUseCase.swiftwhile the actual filename isFetchUserContentListUseCase.swift.-// FetchUserContentUseCase.swift +// FetchUserContentListUseCase.swift
15-15: Class name should match protocol name for consistencyThe class name
FetchUserContentUseCaseImpldoesn't include "List" which is present in the protocol nameFetchUserContentListUseCase. This inconsistency can lead to confusion.-final class FetchUserContentUseCaseImpl: FetchUserContentListUseCase { +final class FetchUserContentListUseCaseImpl: FetchUserContentListUseCase {
22-25: Consider using guard pattern for consistency with other UseCasesYour validation logic uses an
ifstatement, while similar use cases in the codebase (likeFetchUserCommentListUseCaseandFetchUserProfileUseCase) use theguardpattern. Consider adopting the same pattern for consistency.-func execute(for userID: Int, last contentID: Int) -> AnyPublisher<[UserContent], WableError> { - if userID < .zero { - return .fail(.notFoundMember) - } +func execute(for userID: Int, last contentID: Int) -> AnyPublisher<[UserContent], WableError> { + guard userID > .zero else { + return .fail(.notFoundMember) + }Wable-iOS/Presentation/Profile/AccountInfo/View/Cell/AccountInfoCell.swift (3)
16-18: Remove hardcoded placeholder textThe placeholder "제목" (Title) will be replaced by actual data, but it's better to start with an empty text to avoid potential localization or testing issues.
private let titleLabel = UILabel().then { - $0.attributedText = "제목".pretendardString(with: .body2) + $0.font = UIFont.pretendardFont(for: .body2) $0.textColor = .gray600 }
20-23: Remove hardcoded placeholder textSimilar to the titleLabel, it's better to avoid hardcoded placeholder text in the descriptionLabel.
private let descriptionLabel = UILabel().then { - $0.attributedText = "본문".pretendardString(with: .body2) + $0.font = UIFont.pretendardFont(for: .body2) $0.textColor = .wableBlack }
38-53: Make description label handling more robustThe current implementation sets text for normal descriptions but uses attributedText for interactive ones. This could lead to inconsistency if
configureis called multiple times on the same cell with different values.func configure(title: String, description: String, userInteraction: (() -> Void)? = nil) { titleLabel.text = title + // Reset interactive state + descriptionLabel.isUserInteractionEnabled = false + if let gestureRecognizers = descriptionLabel.gestureRecognizers { + for recognizer in gestureRecognizers { + descriptionLabel.removeGestureRecognizer(recognizer) + } + } + guard let userInteraction else { descriptionLabel.text = description return } userInteractionClosure = userInteraction descriptionLabel.attributedText = description.pretendardString(with: .body2).addUnderline() descriptionLabel.isUserInteractionEnabled = true let tapGesture = UITapGestureRecognizer(target: self, action: #selector(descriptionDidTap)) descriptionLabel.addGestureRecognizer(tapGesture) }Wable-iOS/Presentation/Profile/Withdrawal/Reason/View/Cell/WithdrawalReasonCell.swift (2)
21-23: Remove hardcoded placeholder text and add accessibility supportSimilar to previous files, avoid hardcoded placeholder text. Also, add accessibility support for the description label.
private let descriptionLabel = UILabel().then { - $0.attributedText = "설명".pretendardString(with: .body2) + $0.font = UIFont.pretendardFont(for: .body2) + $0.isAccessibilityElement = true + $0.accessibilityTraits = .staticText }
57-60: Reconsider fixed checkbox size for better adaptabilityThe fixed size of 48x48 for the checkbox button might be too large on smaller devices. Consider a more adaptive approach.
checkboxButton.snp.makeConstraints { make in make.leading.centerY.equalToSuperview() - make.size.equalTo(48) + // Option 1: Use a more appropriate size that scales better + make.size.equalTo(24) + // Option 2: Scale with dynamic type for accessibility + // make.size.equalTo(UIFontMetrics.default.scaledValue(for: 24)) }Wable-iOS/Presentation/Profile/AccountInfo/ViewModel/AccountInfoViewModel.swift (1)
25-31: Extract hardcoded titles to constants and prepare for localizationThe current implementation hardcodes UI strings directly in the view model. Extract these to constants or a localization system for better maintainability and localization support.
+// Add at the top of the file +private enum AccountInfoStrings { + static let socialLogin = "소셜 로그인" + static let versionInfo = "버전 정보" + static let userID = "아이디" + static let joinDate = "가입일" + static let termsOfService = "이용약관" + static let viewDetails = "자세히 보기" +} // Then in the viewDidLoad method: items = [ - .init(title: "소셜 로그인", description: accountInfo.socialPlatform?.rawValue ?? ""), - .init(title: "버전 정보", description: accountInfo.version), - .init(title: "아이디", description: accountInfo.displayMemberID), - .init(title: "가입일", description: formatDate(accountInfo.createdDate ?? .now)), - .init(title: "이용약관", description: "자세히 보기", isUserInteractive: true) + .init(title: AccountInfoStrings.socialLogin, description: accountInfo.socialPlatform?.rawValue ?? ""), + .init(title: AccountInfoStrings.versionInfo, description: accountInfo.version), + .init(title: AccountInfoStrings.userID, description: accountInfo.displayMemberID), + .init(title: AccountInfoStrings.joinDate, description: formatDate(accountInfo.createdDate ?? .now)), + .init(title: AccountInfoStrings.termsOfService, description: AccountInfoStrings.viewDetails, isUserInteractive: true) ]Wable-iOS/Domain/UseCase/Onboarding/userProfileUseCase.swift (3)
2-2: Update file header comment to match the renamed class.The file header comment still references "CreateUserProfileUseCase.swift" while the actual class name has changed to "userProfileUseCase".
-// CreateUserProfileUseCase.swift +// userProfileUseCase.swift
20-21: Consider splitting responsibilities into separate use cases.This use case now handles both updating and fetching user profiles, which might violate the Single Responsibility Principle. Consider creating separate use cases for different operations or renaming to reflect the broader scope.
32-34: Add documentation for the new method.The new execute method lacks documentation explaining its purpose and parameters.
+/// Fetches a user profile by user ID +/// - Parameter userID: The ID of the user whose profile should be fetched +/// - Returns: A publisher that emits the user profile or an error func execute(userID: Int) -> AnyPublisher<UserProfile, WableError> { return repository.fetchUserProfile(memberID: userID) }Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/Subview/WithdrawalGuideView.swift (1)
119-120: Check grammar in the second withdrawal description.There seems to be a grammatical issue in the second description text. The sentence "탈퇴와 재가입을 통해 아이디를 교체하며 선량한 이용자들께 피해를 끼치는 행위를 방지하려는 조치 오니 넓은 양해 부탁드립니다." might be missing a word between "조치" and "오니".
-static let secondDescription = "탈퇴와 재가입을 통해 아이디를 교체하며 선량한 이용자들께 피해를 끼치는 행위를 방지하려는 조치 오니 넓은 양해 부탁드립니다." +static let secondDescription = "탈퇴와 재가입을 통해 아이디를 교체하며 선량한 이용자들께 피해를 끼치는 행위를 방지하려는 조치이오니 넓은 양해 부탁드립니다."Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/WithdrawalGuideViewController.swift (1)
95-116: Consider using dependency injection for login screen initialization.The method to present the login view creates multiple new instances and dependencies inline. Consider using dependency injection or a factory pattern to improve testability and reduce coupling.
func presentLoginView() { guard let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate else { return WableLogger.log("SceneDelegate 찾을 수 없음.", for: .debug) } - sceneDelegate.window?.rootViewController = LoginViewController( - viewModel: .init( - updateFCMTokenUseCase: UpdateFCMTokenUseCase(repository: ProfileRepositoryImpl()), - fetchUserAuthUseCase: FetchUserAuthUseCase( - loginRepository: LoginRepositoryImpl(), - userSessionRepository: UserSessionRepositoryImpl( - userDefaults: UserDefaultsStorage(jsonEncoder: .init(), jsonDecoder: .init()) - ) - ), - updateUserSessionUseCase: FetchUserInformationUseCase( - repository: UserSessionRepositoryImpl( - userDefaults: UserDefaultsStorage(jsonEncoder: .init(), jsonDecoder: .init()) - ) - ) - ) - ) + sceneDelegate.window?.rootViewController = DIContainer.shared.makeLoginViewController() }Consider implementing a DIContainer class for dependency injection:
// Example DIContainer implementation final class DIContainer { static let shared = DIContainer() private init() {} func makeLoginViewController() -> UIViewController { let userDefaults = UserDefaultsStorage(jsonEncoder: .init(), jsonDecoder: .init()) let userSessionRepository = UserSessionRepositoryImpl(userDefaults: userDefaults) return LoginViewController( viewModel: .init( updateFCMTokenUseCase: UpdateFCMTokenUseCase(repository: ProfileRepositoryImpl()), fetchUserAuthUseCase: FetchUserAuthUseCase( loginRepository: LoginRepositoryImpl(), userSessionRepository: userSessionRepository ), updateUserSessionUseCase: FetchUserInformationUseCase( repository: userSessionRepository ) ) ) } }Wable-iOS/Presentation/Profile/Withdrawal/Guide/ViewModel/WithdrawalGuideViewModel.swift (1)
38-44:isNextEnabledtoggles blindly; consider explicit boolean source of truth.Each tap simply inverts the flag, so double-tapping leaves it unchanged.
Using the checkbox’s actual on/off state (e.g., aBoolsent by the view) avoids desynchronisation after reuse or programmatic changes.- input.checkbox - .sink { _ in isNextEnabledRelay.value.toggle() } + input.checkboxState // Driver<Bool> + .assign(to: \.value, on: isNextEnabledRelay)Wable-iOS/Presentation/Profile/AlarmSetting/View/AlarmSettingViewController.swift (2)
29-32: Typo & naming consistency:openSettingbutton→openSettingButton.The lowercase “b” hurts readability and deviates from the project’s camel-case convention used elsewhere (
backButton,navigationView).
Renaming avoids future autocomplete mistakes.
161-163: Spelling –appDidBecomActive→appDidBecomeActive.Minor typo in selector/method name. While it compiles, fixing it prevents confusion for future maintainers.
Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift (1)
27-33: Parameter name typo (userinformationUseCase) reduces clarity.
userinformationUseCase(lower-case “i”) is easy to mis-read and differs from the propertyuserSessionUseCase.
Rename touserInformationUseCase(or simplyuserSessionUseCase) to stay consistent.Wable-iOS/Presentation/Profile/AccountInfo/View/AccountInfoViewController.swift (1)
116-133: Replace the unusedindexPathparameter with_to silence SwiftLint and improve clarity
indexPathis not referenced inside the registration closure; Swift-Lint is already warning about it.
Using_makes the intent explicit and removes the need for aswiftlint:disablecomment later.-let cellRegistration = CellRegistration<AccountInfoCell, Item> { cell, indexPath, item in +let cellRegistration = CellRegistration<AccountInfoCell, Item> { cell, _, item in🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 117-117: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
Wable-iOS/Presentation/Profile/Withdrawal/Reason/View/WithdrawalReasonViewController.swift (1)
120-132: Minor: mark the unusedindexPathparameter as_The closure does not use
indexPath; replacing it with_removes the Swift-Lint
warning and communicates intent more clearly.-let cellRegistration = CellRegistration<WithdrawalReasonCell, Item> { cell, indexPath, item in +let cellRegistration = CellRegistration<WithdrawalReasonCell, Item> { cell, _, item in🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 121-121: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
Wable-iOS/Presentation/Profile/My/View/MyProfileViewController.swift (4)
89-102: Silence Swift-Lint by discarding the unusedindexPath
indexPathis not required inside this closure.-let profileInfoCellRegistration = CellRegistration<ProfileInfoCell, UserProfile> { cell, indexPath, item in +let profileInfoCellRegistration = CellRegistration<ProfileInfoCell, UserProfile> { cell, _, item in🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 89-89: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
103-119: Same issue for content cell registration-let contentCellRegistration = CellRegistration<ContentCollectionViewCell, UserContent> { - cell, indexPath, item in +let contentCellRegistration = CellRegistration<ContentCollectionViewCell, UserContent> { + cell, _, item in🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 113-113: TODOs should be resolved (추후 기능 연결)
(todo)
[Warning] 104-104: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
121-138: …and for the comment cell registration-let commentCellRegistration = CellRegistration<CommentCollectionViewCell, UserComment> { - cell, indexPath, item in +let commentCellRegistration = CellRegistration<CommentCollectionViewCell, UserComment> { + cell, _, item in🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 131-131: TODOs should be resolved (추후 기능 연결)
(todo)
[Warning] 122-122: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
113-132: Address the lingering TODOs to avoid shipping dead code pathsBoth
// TODO: 추후 기능 연결markers indicate unfinished like-button logic.
Consider at least raising a tracking issue or implementing a stub handler
(showing a toast, etc.) before merging to keep the feature discoverable.🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 113-113: TODOs should be resolved (추후 기능 연결)
(todo)
[Warning] 131-131: TODOs should be resolved (추후 기능 연결)
(todo)
[Warning] 122-122: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
Wable-iOS/Presentation/Profile/ProfileEditViewController.swift (5)
15-18: Resolve TODO and remove redundant initialisations
defaultImageis initialised withnil, which is the default for optionals, and the TODO comment indicates pending refactor work. Removing the explicit= nilkeeps the code concise; consider also injecting the three use-cases via the initializer in the forthcoming refactor so the controller can be unit-tested without creating concrete repositories internally.- // MARK: Property - // TODO: 유즈케이스 리팩 후에 뷰모델 만들어 넘기기 - - private var defaultImage: String? = nil + // MARK: Property + // TODO: 유즈케이스 리팩터링 후 ViewModel 주입 + + private var defaultImage: String?🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 17-17: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
[Warning] 15-15: TODOs should be resolved (유즈케이스 리팩 후에 뷰모델 만들어 넘기기)
(todo)
64-65: Typo in section marker
Priviate→Privatefor consistency with the other// MARK:headers.
146-176: Improve error handling & retain cycle safety in profile update
- The
sinkignores thecompletionblock—network or decoding errors will silently fail.withUnretained(self)is good, but consider usingtryMap&catchto propagate UI errors.- Pulling only the nickname from the text field discards any changes the user may have made to
profileImageView; consider movingprofilemutation logic to a dedicated view-model.
208-228: Nested subscriptions can be flattenedThe two
sinkcalls form a pyramid of doom and are hard to cancel independently. UsingflatMap(or an async/await wrapper once the data layer supports it) keeps the Combine chain linear and makes cancellation viacancelBagmore predictable.userSessionUseCase.fetchActiveUserID() .compactMap { $0 } .flatMap(profileUseCase.execute(userID:)) .receive(on: DispatchQueue.main) .sink { … } .store(in: cancelBag)
235-243: Handle errors and silence SwiftLint warning in image pickerThe second parameter
erroris unused and currently produces a SwiftLint warning. Also, failing to load the image should notify the user.- results.first?.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in - guard let image = image as? UIImage else { return } + results.first?.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in + guard let self = self else { return } + if let error = error { + // TODO: Present an alert instead of print + print("Image load failed: \(error)") + return + } + guard let image = image as? UIImage else { return }🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 235-235: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
Wable-iOS.xcodeproj/project.pbxproj (1)
203-203: Fix naming convention inconsistency.The file
userProfileUseCase.swiftuses camelCase, which is inconsistent with the PascalCase naming convention used for other use case files likeFetchUserProfileUseCase.swift. Swift file names should use PascalCase for consistency.-DDED59782D78F1E500A0BEF1 /* userProfileUseCase.swift in Sources */, +DDED59782D78F1E500A0BEF1 /* UserProfileUseCase.swift in Sources */,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
⛔ Files ignored due to path filters (3)
Wable-iOS/Resource/Assets.xcassets/Icon/ic_dot.imageset/ic_dot.pngis excluded by!**/*.pngWable-iOS/Resource/Assets.xcassets/Icon/ic_dot.imageset/ic_dot@2x.pngis excluded by!**/*.pngWable-iOS/Resource/Assets.xcassets/Icon/ic_dot.imageset/ic_dot@3x.pngis excluded by!**/*.png
📒 Files selected for processing (51)
Wable-iOS.xcodeproj/project.pbxproj(31 hunks)Wable-iOS/App/SceneDelegate.swift(0 hunks)Wable-iOS/Data/Mapper/ErrorMapper.swift(1 hunks)Wable-iOS/Data/Mapper/ProfileMapper.swift(1 hunks)Wable-iOS/Data/RepositoryImpl/ProfileRepositoryImpl.swift(1 hunks)Wable-iOS/Domain/Entity/UserProfile.swift(1 hunks)Wable-iOS/Domain/Enum/WithdrawalReason.swift(2 hunks)Wable-iOS/Domain/RepositoryInterface/ProfileRepository.swift(1 hunks)Wable-iOS/Domain/UseCase/Home/FetchUserContentListUseCase.swift(0 hunks)Wable-iOS/Domain/UseCase/Onboarding/userProfileUseCase.swift(2 hunks)Wable-iOS/Domain/UseCase/Profile/FetchAccountInfoUseCase.swift(1 hunks)Wable-iOS/Domain/UseCase/Profile/FetchUserCommentListUseCase.swift(1 hunks)Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift(1 hunks)Wable-iOS/Domain/UseCase/Profile/FetchUserProfileUseCase.swift(1 hunks)Wable-iOS/Domain/UseCase/Profile/RemoveUserSessionUseCase.swift(1 hunks)Wable-iOS/Domain/UseCase/Profile/WithdrawUseCase.swift(1 hunks)Wable-iOS/Infra/Network/APIProvider.swift(1 hunks)Wable-iOS/Infra/Network/TargetType/CommentTargetType.swift(1 hunks)Wable-iOS/Infra/Network/TargetType/ContentTargetType.swift(2 hunks)Wable-iOS/Presentation/Home/View/HomeDetailViewController.swift(0 hunks)Wable-iOS/Presentation/Home/View/HomeViewController.swift(0 hunks)Wable-iOS/Presentation/Onboarding/View/ProfileRegisterView.swift(2 hunks)Wable-iOS/Presentation/Onboarding/ViewController/AgreementViewController.swift(1 hunks)Wable-iOS/Presentation/Onboarding/ViewController/ProfileRegisterViewController.swift(1 hunks)Wable-iOS/Presentation/Profile/AccountInfo/Model/AccountInfoCellItem.swift(1 hunks)Wable-iOS/Presentation/Profile/AccountInfo/View/AccountInfoViewController.swift(1 hunks)Wable-iOS/Presentation/Profile/AccountInfo/View/Cell/AccountInfoCell.swift(1 hunks)Wable-iOS/Presentation/Profile/AccountInfo/ViewModel/AccountInfoViewModel.swift(1 hunks)Wable-iOS/Presentation/Profile/AlarmSetting/View/AlarmSettingViewController.swift(1 hunks)Wable-iOS/Presentation/Profile/AlarmSetting/ViewModel/AlarmSettingViewModel.swift(1 hunks)Wable-iOS/Presentation/Profile/My/View/MyProfileViewController.swift(1 hunks)Wable-iOS/Presentation/Profile/My/View/Subview/MyProfileView.swift(1 hunks)Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift(1 hunks)Wable-iOS/Presentation/Profile/ProfileEditViewController.swift(1 hunks)Wable-iOS/Presentation/Profile/ProfileInfoCell.swift(1 hunks)Wable-iOS/Presentation/Profile/ProfileSegmentKind.swift(1 hunks)Wable-iOS/Presentation/Profile/ProfileSegmentedHeaderView.swift(1 hunks)Wable-iOS/Presentation/Profile/ProfileViewController.swift(0 hunks)Wable-iOS/Presentation/Profile/ProfileViewItem.swift(1 hunks)Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/Subview/WithdrawalGuideDescriptionView.swift(1 hunks)Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/Subview/WithdrawalGuideView.swift(1 hunks)Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/WithdrawalGuideViewController.swift(1 hunks)Wable-iOS/Presentation/Profile/Withdrawal/Guide/ViewModel/WithdrawalGuideViewModel.swift(1 hunks)Wable-iOS/Presentation/Profile/Withdrawal/Reason/Model/WithdrawalReasonCellItem.swift(1 hunks)Wable-iOS/Presentation/Profile/Withdrawal/Reason/View/Cell/WithdrawalReasonCell.swift(1 hunks)Wable-iOS/Presentation/Profile/Withdrawal/Reason/View/WithdrawalReasonViewController.swift(1 hunks)Wable-iOS/Presentation/Profile/Withdrawal/Reason/ViewModel/WithdrawalReasonViewModel.swift(1 hunks)Wable-iOS/Presentation/TabBar/TabBarController.swift(1 hunks)Wable-iOS/Presentation/Viewit/List/View/Cell/ViewitListCell.swift(1 hunks)Wable-iOS/Presentation/WableComponent/Navigation/NavigationView.swift(1 hunks)Wable-iOS/Resource/Assets.xcassets/Icon/ic_dot.imageset/Contents.json(1 hunks)
💤 Files with no reviewable changes (5)
- Wable-iOS/App/SceneDelegate.swift
- Wable-iOS/Presentation/Home/View/HomeViewController.swift
- Wable-iOS/Presentation/Home/View/HomeDetailViewController.swift
- Wable-iOS/Presentation/Profile/ProfileViewController.swift
- Wable-iOS/Domain/UseCase/Home/FetchUserContentListUseCase.swift
🧰 Additional context used
🧬 Code Graph Analysis (15)
Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/Subview/WithdrawalGuideDescriptionView.swift (2)
Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/Subview/WithdrawalGuideView.swift (1)
setupView(43-111)Wable-iOS/Presentation/Helper/Extension/UIView+.swift (1)
addSubviews(32-36)
Wable-iOS/Infra/Network/TargetType/ContentTargetType.swift (1)
Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift (1)
fetchUserContentList(126-139)
Wable-iOS/Data/Mapper/ErrorMapper.swift (1)
Wable-iOS/Core/Logger/WableLogger.swift (1)
log(14-25)
Wable-iOS/Domain/UseCase/Profile/WithdrawUseCase.swift (2)
Wable-iOS/Data/RepositoryImpl/AccountRepositoryImpl.swift (1)
deleteAccount(20-31)Wable-iOS/Data/Mapper/ErrorMapper.swift (1)
map(29-43)
Wable-iOS/Presentation/Profile/Withdrawal/Reason/View/Cell/WithdrawalReasonCell.swift (1)
Wable-iOS/Presentation/Helper/Extension/UIView+.swift (1)
addSubviews(32-36)
Wable-iOS/Domain/UseCase/Profile/FetchAccountInfoUseCase.swift (1)
Wable-iOS/Data/RepositoryImpl/ProfileRepositoryImpl.swift (1)
fetchAccountInfo(61-68)
Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift (3)
Wable-iOS/Domain/UseCase/Profile/FetchUserCommentListUseCase.swift (1)
execute(22-28)Wable-iOS/Domain/UseCase/Profile/FetchUserProfileUseCase.swift (1)
execute(22-30)Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift (1)
fetchUserContentList(126-139)
Wable-iOS/Domain/RepositoryInterface/ProfileRepository.swift (1)
Wable-iOS/Data/RepositoryImpl/ProfileRepositoryImpl.swift (1)
fetchAccountInfo(61-68)
Wable-iOS/Presentation/Profile/ProfileSegmentedHeaderView.swift (2)
Wable-iOS/Presentation/Helper/Extension/UIView+.swift (1)
addSubviews(32-36)Wable-iOS/Presentation/Helper/Extension/ConstraintMaker+.swift (1)
adjustedHeightEqualTo(45-48)
Wable-iOS/Presentation/Profile/AlarmSetting/View/AlarmSettingViewController.swift (4)
Wable-iOS/Presentation/Profile/AlarmSetting/ViewModel/AlarmSettingViewModel.swift (1)
checkAlarmAuthorization(15-20)Wable-iOS/Presentation/Helper/Extension/UIView+.swift (1)
addSubviews(32-36)Wable-iOS/Presentation/Helper/Extension/ConstraintMaker+.swift (1)
adjustedHeightEqualTo(45-48)Wable-iOS/Core/Logger/WableLogger.swift (1)
log(14-25)
Wable-iOS/Presentation/Onboarding/View/ProfileRegisterView.swift (1)
Wable-iOS/Presentation/WableComponent/Button/WableButton.swift (1)
updateStyle(51-53)
Wable-iOS/Presentation/Profile/ProfileInfoCell.swift (2)
Wable-iOS/Presentation/Helper/Extension/UIView+.swift (1)
addSubviews(32-36)Wable-iOS/Presentation/Helper/Extension/ConstraintMaker+.swift (1)
adjustedHeightEqualTo(45-48)
Wable-iOS/Presentation/Profile/AccountInfo/View/Cell/AccountInfoCell.swift (1)
Wable-iOS/Presentation/Helper/Extension/UIView+.swift (1)
addSubviews(32-36)
Wable-iOS/Domain/UseCase/Profile/RemoveUserSessionUseCase.swift (1)
Wable-iOS/Core/Logger/WableLogger.swift (1)
log(14-25)
Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift (5)
Wable-iOS/Domain/UseCase/Home/FetchUserInformationUseCase.swift (1)
fetchActiveUserInfo(28-31)Wable-iOS/Domain/UseCase/Profile/RemoveUserSessionUseCase.swift (1)
removeUserSession(21-28)Wable-iOS/Domain/UseCase/Profile/FetchUserCommentListUseCase.swift (1)
execute(22-28)Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift (1)
execute(22-28)Wable-iOS/Domain/UseCase/Profile/FetchUserProfileUseCase.swift (1)
execute(22-30)
🪛 SwiftLint (0.57.0)
Wable-iOS/Presentation/Profile/AccountInfo/View/AccountInfoViewController.swift
[Warning] 117-117: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/WithdrawalGuideViewController.swift
[Warning] 73-73: Using ternary to call Void functions should be avoided
(void_function_in_ternary)
Wable-iOS/Domain/UseCase/Onboarding/userProfileUseCase.swift
[Error] 12-12: Type name 'userProfileUseCase' should start with an uppercase character
(type_name)
Wable-iOS/Presentation/Profile/Withdrawal/Reason/View/WithdrawalReasonViewController.swift
[Warning] 121-121: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
Wable-iOS/Presentation/Profile/My/View/MyProfileViewController.swift
[Warning] 113-113: TODOs should be resolved (추후 기능 연결)
(todo)
[Warning] 131-131: TODOs should be resolved (추후 기능 연결)
(todo)
[Warning] 89-89: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
[Warning] 104-104: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
[Warning] 122-122: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
[Warning] 142-142: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
[Warning] 142-142: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
[Warning] 172-172: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
[Warning] 310-310: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
Wable-iOS/Presentation/Profile/ProfileEditViewController.swift
[Warning] 17-17: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
[Warning] 30-30: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
[Warning] 15-15: TODOs should be resolved (유즈케이스 리팩 후에 뷰모델 만들어 넘기기)
(todo)
[Warning] 235-235: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
🔇 Additional comments (34)
Wable-iOS/Domain/Entity/UserProfile.swift (1)
12-12:Details
✅ Verification successful
Good addition of Hashable conformance.
Adding the
Hashableprotocol conformance toUserProfileenables it to work in hash-based collections likeSetand as dictionary keys, which is beneficial for state management in the new profile UI components.
🏁 Script executed:
#!/bin/bash # Verify that the User type also conforms to Hashable, which is required for UserProfile to be properly Hashable # Search for the User struct definition echo "Checking User struct definition for Hashable conformance:" rg -A 5 "struct User" --type swiftLength of output: 2587
Hashable Conformance Verified
Verified that the
Userstruct inWable-iOS/Domain/Entity/User.swiftalready conforms toHashable, soUserProfile: Hashableis fully supported. No further changes required—good to merge.• Wable-iOS/Domain/Entity/User.swift:
struct User: Identifiable, Hashable { … }• Wable-iOS/Domain/Entity/UserProfile.swift:
struct UserProfile: Hashable { … }Wable-iOS/Infra/Network/TargetType/CommentTargetType.swift (1)
28-28:Details
✅ Verification successful
API endpoint path updated.
The endpoint path for fetching user comments has been simplified from "/v3/member/{memberID}/member-comments" to "/v3/member/{memberID}/comments". This change aligns with the newly introduced
FetchUserCommentListUseCase.
🏁 Script executed:
#!/bin/bash # Verify that the updated endpoint is used consistently across the codebase # Check for any references to the old endpoint path echo "Checking for any references to the old endpoint path:" rg "member-comments" --type swift # Check for references to the new endpoint in the associated use case echo "Checking for references to the new endpoint in use cases:" rg -A 5 "FetchUserCommentListUseCase" --type swiftLength of output: 3965
API endpoint path updated and verified
The endpoint for fetching user comments has been simplified from/v3/member/{memberID}/member-commentsto/v3/member/{memberID}/comments, in line with the newFetchUserCommentListUseCase. I confirmed:
- No references to the old
/member-commentspath remain.- The new
/commentsendpoint is used exclusively across the Swift codebase.Approved.
Wable-iOS/Domain/Enum/WithdrawalReason.swift (1)
2-2: Good renaming and protocol addition.The renaming from "AccountDeleteReason" to "WithdrawalReason" provides more user-friendly terminology while maintaining the same functionality. Adding the
CaseIterableprotocol is an excellent decision, as it enables easy iteration over all withdrawal reasons, which will be useful when displaying these options in UI components.Also applies to: 12-12
Wable-iOS/Presentation/Profile/ProfileSegmentKind.swift (1)
10-13: Well-designed enum for segmented control.The
ProfileSegmentKindenum is well-structured with appropriate Int raw values (0, 1) that align perfectly with UISegmentedControl indices. The naming clearly indicates the purpose of each segment.For even more flexibility, consider conforming to
CaseIterableif you need to iterate through all segment options programmatically:-enum ProfileSegmentKind: Int { +enum ProfileSegmentKind: Int, CaseIterable { case content = 0 case comment = 1 }Wable-iOS/Resource/Assets.xcassets/Icon/ic_dot.imageset/Contents.json (1)
1-23: Properly configured image assetThe
ic_dotimage asset is correctly configured with all required scale variations (1x, 2x, 3x) for universal device support, following iOS asset catalog best practices.Wable-iOS/Presentation/Profile/Withdrawal/Reason/Model/WithdrawalReasonCellItem.swift (1)
10-13: LGTM - Good implementation for cell data representationThis struct is well-designed for its purpose, conforming to
Hashablefor collection diffing support, and providing a clear model for representing withdrawal reasons with selection state.Wable-iOS/Presentation/Onboarding/ViewController/AgreementViewController.swift (1)
24-24: Refactored use case naming looks goodThe update from
CreateUserProfileUseCasetouserProfileUseCasealigns with the broader refactoring mentioned in the PR objectives, which aims to improve code readability through modern concurrency approaches.Wable-iOS/Infra/Network/TargetType/ContentTargetType.swift (2)
68-69: Correctly updated pattern matching for additional parameter.The
fetchUserContentListcase pattern matching now correctly handles the additional cursor parameter, ensuring the endpoint URL is properly constructed.
77-78: Query parameters updated to match new case format.The pattern matching in the
queryproperty now properly extracts the cursor from the second parameter while ignoring the first parameter with an underscore. This correctly maintains the existing functionality while supporting the updated case structure.Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/Subview/WithdrawalGuideDescriptionView.swift (1)
37-49: Clean UI setup with proper constraints.The view setup and constraint configuration are well-organized and follow good practices with SnapKit. The use of the
addSubviewsextension method keeps the code concise.Wable-iOS/Presentation/Viewit/List/View/Cell/ViewitListCell.swift (3)
264-268: Well-implemented random profile image selection logic.The array of profile images with random selection provides a good fallback when no profile image URL is available, ensuring the UI always displays something meaningful.
273-274: Good handling of empty/nil profile image URLs.Setting a random profile image when URL is nil or empty is appropriate, and the early return prevents unnecessary code execution.
277-286: Smart implementation of special keyword handling for profile images.The switch statement elegantly handles special case strings ("PURPLE", "GREEN", "BLUE") while defaulting to Kingfisher for remote images. This approach maintains consistency with the profile image handling pattern introduced elsewhere in the project.
Wable-iOS/Infra/Network/APIProvider.swift (1)
73-98: Excellent async/await implementation of the request method.The async implementation correctly wraps the existing callback-based method using
withCheckedThrowingContinuation, providing a modern alternative without breaking existing functionality. The error handling is comprehensive, categorizing different error types appropriately.A few observations:
- The method properly decodes the response and validates it
- Error handling is thorough with specific handling for decoding errors
- The implementation aligns with Swift concurrency best practices
This change supports the PR's objective of moving from Combine to async/await for improved readability and maintainability.
Wable-iOS/Presentation/Profile/AlarmSetting/ViewModel/AlarmSettingViewModel.swift (1)
12-14: Clean ViewModel implementation with good encapsulation.The view model correctly uses
@Publishedwithprivate(set)to allow external observation of the authorization state while preventing external modification. This is a good practice for maintaining proper data flow.Wable-iOS/Data/RepositoryImpl/ProfileRepositoryImpl.swift (1)
61-68: Good async/await implementation replacing Combine-based approach.The repository method effectively uses the new async/await pattern to fetch account information, aligning with the PR's objective to improve code readability. The error handling is clean and properly uses the new ErrorMapper to standardize error mapping.
This implementation:
- Correctly uses
async throwsfor error propagation- Properly calls the async version of the provider's request method
- Maps the DTO response to a domain model
- Handles errors through the centralized ErrorMapper
The change demonstrates a good migration pattern from reactive programming to Swift concurrency.
Wable-iOS/Domain/UseCase/Profile/FetchAccountInfoUseCase.swift (2)
11-13: Clean protocol definition.The protocol follows good design principles with a simple, clear async interface that matches the PR's objective of moving from Combine to async/await.
15-25: Proper implementation of async pattern.The implementation correctly follows the async/await pattern, delegating to the repository layer. This aligns with the PR objective of improving code readability by moving away from Combine.
Wable-iOS/Domain/UseCase/Profile/FetchUserProfileUseCase.swift (2)
11-13: Well-defined protocol with clear interface.The protocol provides a clean contract with a single method that follows the use case pattern, returning a publisher for asynchronous operation.
15-31:Details
❓ Verification inconclusive
Consider using async/await instead of Combine.
According to the PR objectives, this PR aims to introduce async/await syntax to improve code readability. Consider updating this implementation to use async/await instead of Combine publishers.
protocol FetchUserProfileUseCase { - func execute(userID: Int) -> AnyPublisher<UserProfile?, WableError> + func execute(userID: Int) async throws -> UserProfile? } final class FetchUserProfileUseCaseImpl: FetchUserProfileUseCase { private let repository: ProfileRepository init(repository: ProfileRepository) { self.repository = repository } - func execute(userID: Int) -> AnyPublisher<UserProfile?, WableError> { + func execute(userID: Int) async throws -> UserProfile? { guard userID > .zero else { - return .fail(.notFoundMember) + throw WableError.notFoundMember } - return repository.fetchUserProfile(memberID: userID) - .map { $0 } - .eraseToAnyPublisher() + return try await repository.fetchUserProfile(memberID: userID) } }
🏁 Script executed:
#!/bin/bash # Check if the repository layer supports async/await rg "fetchUserProfile.*async" --type swiftLength of output: 43
Verify
ProfileRepositoryasync/await support before refactoring UseCaseIt looks like there isn’t an
async throwssignature forfetchUserProfile(memberID:)in theProfileRepositoryprotocol, so switching the use case to async/await will break its dependency. Please confirm and update the repository layer before refactoring:• Update the protocol:
-protocol ProfileRepository { - func fetchUserProfile(memberID: Int) -> AnyPublisher<UserProfile?, WableError> +protocol ProfileRepository { + func fetchUserProfile(memberID: Int) async throws -> UserProfile? }• Ensure all conforming classes implement the new async API, either by calling a native async endpoint or by bridging the existing Combine publisher (e.g. using
withCheckedThrowingContinuationor publisher’s.values).Once the repository is async-ready, you can merge the diff below into
FetchUserProfileUseCase:protocol FetchUserProfileUseCase { - func execute(userID: Int) -> AnyPublisher<UserProfile?, WableError> + func execute(userID: Int) async throws -> UserProfile? } final class FetchUserProfileUseCaseImpl: FetchUserProfileUseCase { private let repository: ProfileRepository init(repository: ProfileRepository) { self.repository = repository } - func execute(userID: Int) -> AnyPublisher<UserProfile?, WableError> { + func execute(userID: Int) async throws -> UserProfile? { guard userID > .zero else { - return .fail(.notFoundMember) + throw WableError.notFoundMember } - return repository.fetchUserProfile(memberID: userID) - .map { $0 } - .eraseToAnyPublisher() + return try await repository.fetchUserProfile(memberID: userID) } }Wable-iOS/Domain/UseCase/Profile/FetchUserCommentListUseCase.swift (1)
11-13: Clear protocol interface with pagination support.The protocol is well-defined and supports pagination through the
last commentIDparameter.Wable-iOS/Presentation/Profile/ProfileSegmentedHeaderView.swift (3)
15-17: Good use of then pattern for UI initialization.Using the Then library provides a clean, declarative way to configure the segmented control.
19-19: Well-designed callback pattern.The use of a closure for segment changes instead of a delegate pattern is a good choice for this simple use case.
33-49: Clean UI setup with proper constraints.The view setup is well-organized with clear separation of UI element creation, hierarchy setup, and constraints.
Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/Subview/WithdrawalGuideView.swift (1)
13-39: LGTM! Well-structured UI component.The implementation follows project standards with clear separation of UI elements and proper configuration.
Wable-iOS/Presentation/Profile/My/View/Subview/MyProfileView.swift (1)
54-85: LGTM! Clean layout implementation.The view setup and constraints are well-organized using SnapKit, following project standards.
Wable-iOS/Presentation/Profile/Withdrawal/Guide/View/WithdrawalGuideViewController.swift (1)
128-136: LGTM! Good implementation of confirmation flow.The implementation of the withdrawal confirmation sheet is well done, using the appropriate UI components and handling the action properly.
Wable-iOS.xcodeproj/project.pbxproj (7)
33-33: File renaming for better semantics.The change from
AccountDeleteReason.swifttoWithdrawalReason.swiftimproves the clarity of terminology. "Withdrawal" better represents the user action of leaving the service compared to "AccountDelete".
245-250: Well-structured profile use cases added.These profile-related use cases follow clean architecture principles by encapsulating specific business logic operations. The separation into distinct use cases (Fetch Profile, Comments, Contents, AccountInfo, and Withdraw) promotes single responsibility and maintainability.
155-155: Well-organized UI components for profile feature.The implementation follows a proper MVVM structure with separate files for:
- Views (
MyProfileViewController,ProfileEditViewController)- Models (
ProfileViewItem,ProfileSegmentKind)- Supporting UI components (
ProfileSegmentedHeaderView,ProfileInfoCell)This organization makes the code more maintainable and testable.
Also applies to: 242-244, 309-315
301-308: Comprehensive implementation for account withdrawal flow.The withdrawal feature is thoroughly implemented with proper separation of concerns:
- Different views for reason selection and confirmation
- Models for withdrawal reasons
- Supporting UI components for explanation
- View models for business logic
This structure ensures a good user experience for a critical flow.
321-324: Well-structured AccountInfo feature implementation.The AccountInfo feature follows clean architecture with separate files for view, view model, and model. The cell-based approach allows for flexible display of different types of account information.
242-243: Addition of settings-related features.The implementation of
AlarmSettingViewControllerandAlarmSettingViewModelfollowing MVVM pattern allows users to manage their notification preferences, which is a standard feature for profile sections in modern apps.
1617-1713: Well-organized profile feature structure.The profile section is well-organized into logical sub-features:
- My Profile - for viewing and managing personal profile
- AlarmSetting - for notification preferences
- AccountInfo - for account details
- Withdrawal - for account termination flow
This organization maintains clean separation of concerns and makes the codebase more maintainable.
|
|
||
| rootView.configureView() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Added explicit view configuration call.
The addition of rootView.configureView() ensures that the view is properly configured after setting the default image. This change is aligned with the profile view configuration patterns introduced elsewhere in the onboarding flow.
🏁 Script executed:
#!/bin/bash
# Check how configureView is implemented in the ProfileRegisterView
echo "Looking for ProfileRegisterView implementation:"
fd ProfileRegisterView --type file --exec cat {} \; | rg -A 10 "func configureView" --context 10
# Check for potential issues with defaultImageList
echo "Checking for potential issues with defaultImageList access:"
fd ProfileRegisterView --type file --exec cat {} \; | rg "defaultImageList" --context 5Length of output: 2006
Swap configureView() and defaultImage assignment in setupView
The explicit call to rootView.configureView() shuffles defaultImageList (via configureDefaultImage()), so you should assign the view controller’s defaultImage after calling configureView(), just as you do in switchButtonDidTap(). This guarantees the defaultImage property matches the image shown in the UI.
• In ProfileRegisterViewController.swift – setupView():
func setupView() {
view.addSubview(rootView)
- defaultImage = rootView.defaultImageList[0].uppercased
- rootView.configureView()
+ rootView.configureView()
+ defaultImage = rootView.defaultImageList[0].uppercased
}Alternatively, you can call rootView.configureDefaultImage() directly before setting defaultImage.
📝 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.
| rootView.configureView() | |
| func setupView() { | |
| view.addSubview(rootView) | |
| rootView.configureView() | |
| defaultImage = rootView.defaultImageList[0].uppercased | |
| } |
🤖 Prompt for AI Agents
In
Wable-iOS/Presentation/Onboarding/ViewController/ProfileRegisterViewController.swift
around lines 61 to 62, the call to rootView.configureView() should be moved
before assigning the view controller's defaultImage property in setupView(),
because configureView() modifies defaultImageList and affects the defaultImage
shown. To fix this, reorder the code so that configureView() is called first,
then assign defaultImage, or alternatively call rootView.configureDefaultImage()
directly before setting defaultImage to ensure consistency between the property
and the UI.
| protocol WithdrawUseCase { | ||
| func execute(reasons: [WithdrawalReason]) -> AnyPublisher<Bool, WableError> | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using async/await for consistency.
This use case uses Combine publishers while FetchAccountInfoUseCase uses async/await. According to the PR objectives, you're moving toward async/await for better readability. Consider making this consistent.
protocol WithdrawUseCase {
- func execute(reasons: [WithdrawalReason]) -> AnyPublisher<Bool, WableError>
+ func execute(reasons: [WithdrawalReason]) async throws -> Bool
}📝 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.
| protocol WithdrawUseCase { | |
| func execute(reasons: [WithdrawalReason]) -> AnyPublisher<Bool, WableError> | |
| } | |
| protocol WithdrawUseCase { | |
| func execute(reasons: [WithdrawalReason]) async throws -> Bool | |
| } |
🤖 Prompt for AI Agents
In Wable-iOS/Domain/UseCase/Profile/WithdrawUseCase.swift around lines 11 to 13,
the WithdrawUseCase protocol currently uses Combine's AnyPublisher for its
execute method, but to maintain consistency with FetchAccountInfoUseCase and
improve readability, refactor the execute method to use async/await by changing
its signature to an async function that returns a Bool and throws WableError.
| func execute(reasons: [WithdrawalReason]) -> AnyPublisher<Bool, WableError> { | ||
| return repository.deleteAccount(reason: reasons.map { $0.rawValue }) | ||
| .map { _ in true } | ||
| .eraseToAnyPublisher() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Implement async method consistently with PR objectives.
The implementation should follow the async/await pattern for consistency with other use cases and to align with the PR's focus on improving code readability.
-func execute(reasons: [WithdrawalReason]) -> AnyPublisher<Bool, WableError> {
- return repository.deleteAccount(reason: reasons.map { $0.rawValue })
- .map { _ in true }
- .eraseToAnyPublisher()
+async func execute(reasons: [WithdrawalReason]) async throws -> Bool {
+ try await repository.deleteAccount(reason: reasons.map { $0.rawValue })
+ return true
}Note: This assumes that the AccountRepository.deleteAccount method would also be updated to use async/await. If that method hasn't been updated yet, you might need to create an adapter pattern or update both simultaneously.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In Wable-iOS/Domain/UseCase/Profile/WithdrawUseCase.swift around lines 22 to 26,
the execute method currently returns a Combine publisher but should be
refactored to use async/await for consistency with the PR objectives. Change the
method signature to async and have it call an async version of
repository.deleteAccount, awaiting its result and returning a Bool directly.
Ensure that repository.deleteAccount is also updated to support async/await or
create an adapter to bridge between Combine and async/await if needed.
| func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError> { | ||
| guard userID > .zero else { | ||
| return .fail(.notFoundMember) | ||
| } | ||
|
|
||
| return repository.fetchUserCommentList(memberID: userID, cursor: commentID) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
❓ Verification inconclusive
Consider using async/await instead of Combine.
According to the PR objectives, this PR aims to introduce async/await syntax. Consider updating this implementation to use async/await instead of Combine publishers for better code readability and alignment with the PR goals.
protocol FetchUserCommentListUseCase {
- func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError>
+ func execute(for userID: Int, last commentID: Int) async throws -> [UserComment]
}
final class FetchUserCommentListUseCaseImpl: FetchUserCommentListUseCase {
private let repository: CommentRepository
init(repository: CommentRepository) {
self.repository = repository
}
- func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError> {
+ func execute(for userID: Int, last commentID: Int) async throws -> [UserComment] {
guard userID > .zero else {
- return .fail(.notFoundMember)
+ throw WableError.notFoundMember
}
- return repository.fetchUserCommentList(memberID: userID, cursor: commentID)
+ return try await repository.fetchUserCommentList(memberID: userID, cursor: commentID)
}
}🏁 Script executed:
#!/bin/bash
# Check if the repository layer supports async/await
rg "fetchUserCommentList.*async" --type swiftLength of output: 47
Adopt async/await across use-case and repository
To align with this PR’s async/await goal, update both the use-case and its repository dependency:
• In FetchUserCommentListUseCase (protocol + implementation), change
func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError>to
func execute(for userID: Int, last commentID: Int) async throws -> [UserComment]and throw on userID <= 0, then call
try await repository.fetchUserCommentList(memberID: userID, cursor: commentID)• In CommentRepository (and its conforming types), change
func fetchUserCommentList(memberID: Int, cursor: Int) -> AnyPublisher<[UserComment], WableError>to
func fetchUserCommentList(memberID: Int, cursor: Int) async throws -> [UserComment]and update all implementations accordingly.
// Protocol
-protocol FetchUserCommentListUseCase {
- func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError>
-}
+protocol FetchUserCommentListUseCase {
+ func execute(for userID: Int, last commentID: Int) async throws -> [UserComment]
+}
// Implementation
-final class FetchUserCommentListUseCaseImpl: FetchUserCommentListUseCase {
+final class FetchUserCommentListUseCaseImpl: FetchUserCommentListUseCase {
private let repository: CommentRepository
init(repository: CommentRepository) {
self.repository = repository
}
- func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError> {
+ func execute(for userID: Int, last commentID: Int) async throws -> [UserComment] {
guard userID > .zero else {
- return .fail(.notFoundMember)
+ throw WableError.notFoundMember
}
-
- return repository.fetchUserCommentList(memberID: userID, cursor: commentID)
+ return try await repository.fetchUserCommentList(memberID: userID, cursor: commentID)
}
}// In CommentRepository.swift
-protocol CommentRepository {
- func fetchUserCommentList(memberID: Int, cursor: Int) -> AnyPublisher<[UserComment], WableError>
- // …
-}
+protocol CommentRepository {
+ func fetchUserCommentList(memberID: Int, cursor: Int) async throws -> [UserComment]
+ // …
+}🤖 Prompt for AI Agents
In Wable-iOS/Domain/UseCase/Profile/FetchUserCommentListUseCase.swift around
lines 22 to 28, refactor the execute function to use async/await instead of
Combine. Change the function signature to async throws returning [UserComment],
throw an error if userID is less than or equal to zero, and call the
repository's fetchUserCommentList method using try await. Also update the
CommentRepository protocol and its implementations to use async throws for
fetchUserCommentList to maintain consistency.
| userinformationUseCase: FetchUserInformationUseCase( | ||
| repository: UserSessionRepositoryImpl( | ||
| userDefaults: UserDefaultsStorage(jsonEncoder: .init(), jsonDecoder: .init()) | ||
| ) | ||
| ), | ||
| fetchUserProfileUseCase: FetchUserProfileUseCaseImpl( | ||
| repository: ProfileRepositoryImpl() | ||
| ), | ||
| fetchUserCommentListUseCase: FetchUserCommentListUseCaseImpl( | ||
| repository: CommentRepositoryImpl() | ||
| ), | ||
| fetchUserContentListUseCase: FetchUserContentUseCaseImpl( | ||
| repository: ContentRepositoryImpl() | ||
| ), | ||
| removeUserSessionUseCase: RemoveUserSessionUseCaseImpl( | ||
| repository: UserSessionRepositoryImpl( | ||
| userDefaults: UserDefaultsStorage( | ||
| jsonEncoder: .init(), jsonDecoder: .init() | ||
| ) | ||
| ) | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify the integration with async/await.
According to the PR objectives, this PR aims to introduce async/await syntax to improve code readability. Ensure that the view model properly handles the transition from Combine-based use cases to async/await-based ones.
🏁 Script executed:
#!/bin/bash
# Check how MyProfileViewModel handles asynchronous operations
rg -A 10 "class MyProfileViewModel" --type swiftLength of output: 1415
🏁 Script executed:
#!/bin/bash
# Check for async/await usage and Combine subscriptions in MyProfileViewModel
rg "await" -n Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift
rg "Task" -n Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift
rg "\.sink" -n Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swiftLength of output: 393
Migrate MyProfileViewModel to async/await
The view model still uses Combine’s .sink on lines 65 and 78 and has no await or Task calls. To complete the PR objective you’ll need to refactor these subscriptions to call the new async use cases directly.
• Remove .sink on:
– Line 65 (updating selectedSegment)
– Line 78 (sending profileViewItemRelay)
• Wrap each call in a Task and use await on the use‐case’s async method (for example, let profile = try await fetchUserProfileUseCase.execute())
• Handle errors and cancellation as needed, and send results to your relays
🤖 Prompt for AI Agents
In Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift around
lines 60 to 90, refactor the Combine `.sink` subscriptions on lines 65 and 78 to
use async/await instead. Remove the `.sink` calls and wrap the asynchronous use
case calls inside `Task` blocks, using `await` to call the use case methods.
Handle any errors or cancellation appropriately within the `Task`, and update
the relays with the results instead of using Combine subscriptions.
| .flatMap { owner, _ in | ||
| return owner.fetchUserProfile() | ||
| .combineLatest( | ||
| owner.fetchUserContentList(cursor: Constant.initialCursor), | ||
| owner.fetchUserCommentList(cursor: Constant.initialCursor) | ||
| ) | ||
| } | ||
| .map { ProfileViewItem(profileInfo: $0, content: $1, comment: $2) } | ||
| .sink { [weak self] in self?.profileViewItemRelay.send($0) } | ||
| .store(in: cancelBag) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect tuple destructuring after combineLatest causes build failure.
combineLatest with three publishers emits a single tuple, so $0, $1, $2 reference elements of that tuple, not separate parameters.
Unpack the tuple explicitly:
- .map { ProfileViewItem(profileInfo: $0, content: $1, comment: $2) }
+ .map { (profile, content, comment) in
+ ProfileViewItem(profileInfo: profile,
+ content: content,
+ comment: comment)
+ }📝 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.
| .flatMap { owner, _ in | |
| return owner.fetchUserProfile() | |
| .combineLatest( | |
| owner.fetchUserContentList(cursor: Constant.initialCursor), | |
| owner.fetchUserCommentList(cursor: Constant.initialCursor) | |
| ) | |
| } | |
| .map { ProfileViewItem(profileInfo: $0, content: $1, comment: $2) } | |
| .sink { [weak self] in self?.profileViewItemRelay.send($0) } | |
| .store(in: cancelBag) | |
| .flatMap { owner, _ in | |
| return owner.fetchUserProfile() | |
| .combineLatest( | |
| owner.fetchUserContentList(cursor: Constant.initialCursor), | |
| owner.fetchUserCommentList(cursor: Constant.initialCursor) | |
| ) | |
| } | |
| .map { (profile, content, comment) in | |
| ProfileViewItem( | |
| profileInfo: profile, | |
| content: content, | |
| comment: comment | |
| ) | |
| } | |
| .sink { [weak self] in self?.profileViewItemRelay.send($0) } | |
| .store(in: cancelBag) |
🤖 Prompt for AI Agents
In Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift around
lines 70 to 79, the code incorrectly treats the output of combineLatest as
separate parameters instead of a single tuple. To fix this, explicitly
destructure the tuple emitted by combineLatest in the map closure by unpacking
it into three variables, then use those variables to create the ProfileViewItem.
This will resolve the build failure caused by incorrect tuple handling.
| input.checkbox | ||
| .sink { reason in | ||
| guard let index = itemsRelay.value.firstIndex(where: { $0.reason == reason }) else { return } | ||
|
|
||
| var item = itemsRelay.value[index] | ||
| item.isSelected.toggle() | ||
| itemsRelay.value[index] = item | ||
|
|
||
| if item.isSelected { | ||
| selectedReasonsRelay.value.insert(reason) | ||
| } else { | ||
| selectedReasonsRelay.value.remove(reason) | ||
| } | ||
| } | ||
| .store(in: cancelBag) | ||
|
|
||
| selectedReasonsRelay | ||
| .map { !$0.isEmpty } | ||
| .sink { isNextEnabledRelay.send($0) } | ||
| .store(in: cancelBag) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Selection UI will not refresh because the relay isn’t notified after mutation
After toggling item.isSelected, the mutated array is assigned back to
itemsRelay.value without a subsequent send (or similar) call.
Unless CurrentValueRelay automatically publishes on didSet, the diffable
data-source in the view controller will never receive an update, so check-boxes
won’t visually toggle and isNextEnabled may stay stale.
itemsRelay.value[index] = item
+// Notify subscribers about the mutated array
+itemsRelay.send(itemsRelay.value)Do the same after the selectedReasonsRelay mutation if its setter is passive.
📝 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.
| input.checkbox | |
| .sink { reason in | |
| guard let index = itemsRelay.value.firstIndex(where: { $0.reason == reason }) else { return } | |
| var item = itemsRelay.value[index] | |
| item.isSelected.toggle() | |
| itemsRelay.value[index] = item | |
| if item.isSelected { | |
| selectedReasonsRelay.value.insert(reason) | |
| } else { | |
| selectedReasonsRelay.value.remove(reason) | |
| } | |
| } | |
| .store(in: cancelBag) | |
| selectedReasonsRelay | |
| .map { !$0.isEmpty } | |
| .sink { isNextEnabledRelay.send($0) } | |
| .store(in: cancelBag) | |
| input.checkbox | |
| .sink { reason in | |
| guard let index = itemsRelay.value.firstIndex(where: { $0.reason == reason }) else { return } | |
| var item = itemsRelay.value[index] | |
| item.isSelected.toggle() | |
| - itemsRelay.value[index] = item | |
| + itemsRelay.value[index] = item | |
| + // Notify subscribers about the mutated array | |
| + itemsRelay.send(itemsRelay.value) | |
| if item.isSelected { | |
| selectedReasonsRelay.value.insert(reason) | |
| } else { | |
| selectedReasonsRelay.value.remove(reason) | |
| } | |
| } | |
| .store(in: cancelBag) | |
| selectedReasonsRelay | |
| .map { !$0.isEmpty } | |
| .sink { isNextEnabledRelay.send($0) } | |
| .store(in: cancelBag) |
🤖 Prompt for AI Agents
In
Wable-iOS/Presentation/Profile/Withdrawal/Reason/ViewModel/WithdrawalReasonViewModel.swift
between lines 36 and 55, after mutating itemsRelay.value by toggling
item.isSelected and updating the array, explicitly call a method like send() or
assign the updated array back to itemsRelay to notify subscribers of the change
so the UI refreshes properly. Similarly, after modifying
selectedReasonsRelay.value by inserting or removing reasons, ensure you trigger
a send or equivalent notification to update any observers and keep the UI state
consistent.
| private let profileUseCase = userProfileUseCase(repository: ProfileRepositoryImpl()) | ||
| private let nicknameUseCase = FetchNicknameDuplicationUseCase(repository: AccountRepositoryImpl()) | ||
| private let userSessionUseCase = FetchUserInformationUseCase( | ||
| repository: UserSessionRepositoryImpl( | ||
| userDefaults: UserDefaultsStorage( | ||
| jsonEncoder: JSONEncoder(), | ||
| jsonDecoder: JSONDecoder() | ||
| ) | ||
| ) | ||
| ) | ||
| private let cancelBag = CancelBag() | ||
|
|
||
| private var sessionProfile: UserProfile? = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Favour dependency injection for testability
Creating concrete RepositoryImpl instances inside the view-controller tightly couples UI to data-layer details and makes unit testing cumbersome. Pass the three use-cases (or a single view-model) through the initializer instead.
init(
profileUseCase: UserProfileUseCaseProtocol,
nicknameUseCase: FetchNicknameDuplicationUseCaseProtocol,
userSessionUseCase: FetchUserInformationUseCaseProtocol
) { … }🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 30-30: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
🤖 Prompt for AI Agents
In Wable-iOS/Presentation/Profile/ProfileEditViewController.swift around lines
18 to 30, the view controller directly instantiates concrete repository
implementations, causing tight coupling and hindering testability. Refactor by
removing these direct instantiations and instead add an initializer that accepts
the three use-case instances (or a single view-model) as parameters conforming
to their protocols. Assign these passed-in use-cases to the private properties,
enabling dependency injection and easier unit testing.
| @objc func switchButtonDidTap() { | ||
| rootView.configureDefaultImage() | ||
| defaultImage = rootView.defaultImageList[0].uppercased | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compile-time error – call uppercased()
String.uppercased is a method, not a property; omitting the parentheses results in “reference to member ‘uppercased’ cannot be used on value of type ‘String’”.
- defaultImage = rootView.defaultImageList[0].uppercased
+ defaultImage = rootView.defaultImageList[0].uppercased()🤖 Prompt for AI Agents
In Wable-iOS/Presentation/Profile/ProfileEditViewController.swift around lines
107 to 111, the code incorrectly uses `uppercased` without parentheses, causing
a compile-time error. Fix this by adding parentheses to call the method as
`uppercased()`, ensuring the string is properly converted to uppercase.
| guard let text = rootView.nickNameTextField.text else { return } | ||
|
|
||
| nicknameUseCase.execute(nickname: text) | ||
| .receive(on: DispatchQueue.main) | ||
| .sink(receiveCompletion: { [weak self] completion in | ||
| let condition = completion == .finished | ||
|
|
||
| self?.rootView.conditiionLabel.text = condition ? "사용 가능한 닉네임입니다." : "이미 사용 중인 닉네임입니다." | ||
| self?.rootView.conditiionLabel.textColor = condition ? .success : .error | ||
| self?.rootView.nextButton.isUserInteractionEnabled = condition | ||
| self?.rootView.nextButton.updateStyle(condition ? .primary : .gray) | ||
| }, receiveValue: { _ in | ||
| }) | ||
| .store(in: cancelBag) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Incorrect success/failure logic when checking nickname duplication
Using completion == .finished treats every non-error path as “nickname usable”, which is fragile—network errors would incorrectly mark a nickname as taken. Treat Subscribers.Completion.failure explicitly and surface an error state to the user.
- .sink(receiveCompletion: { [weak self] completion in
- let condition = completion == .finished
+ .sink(receiveCompletion: { [weak self] completion in
+ let condition: Bool
+ switch completion {
+ case .finished:
+ condition = true
+ case .failure:
+ self?.rootView.conditiionLabel.text = "네트워크 오류가 발생했어요."
+ self?.rootView.conditiionLabel.textColor = .error
+ condition = false
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In Wable-iOS/Presentation/Profile/ProfileEditViewController.swift around lines
130 to 144, the current logic treats any completion that is not a failure as a
successful nickname check, which is incorrect. Update the sink's
receiveCompletion closure to explicitly handle the failure case by checking if
completion is a failure and setting the UI to indicate the nickname is taken or
an error occurred. Only when completion is .finished should the UI show the
nickname as available. This ensures network errors or other failures do not
incorrectly mark the nickname as usable.
youz2me
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
룩굿투미. 고생하셨습니다 (_ _)
| func request<D: Decodable>( | ||
| _ target: Target, | ||
| for type: D.Type | ||
| ) async throws -> D { | ||
| let response = try await withCheckedThrowingContinuation { continuation in | ||
| self.request(target) { result in | ||
| switch result { | ||
| case .success(let moyaResponse): | ||
| continuation.resume(returning: moyaResponse) | ||
| case .failure(let error): | ||
| continuation.resume(throwing: NetworkError.unknown(error)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| do { | ||
| let baseResponse = try jsonDecoder.decode(BaseResponse<D>.self, from: response.data) | ||
| return try validateResponse(baseResponse) | ||
| } catch let decodingError as DecodingError { | ||
| throw NetworkError.decodedError(decodingError) | ||
| } catch let error as NetworkError { | ||
| throw error | ||
| } catch { | ||
| throw NetworkError.unknown(error) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
| switch self { | ||
| case .fetchUserCommentList(memberID: let memberID, _): | ||
| return "/v3/member/\(memberID)/member-comments" | ||
| return "/v3/member/\(memberID)/comments" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ㅎㅎ ... 고생하셨씁니다
| switch profileImageURL.absoluteString { | ||
| case "PURPLE": | ||
| profileImageView.image = .imgProfilePurple | ||
| case "GREEN": | ||
| profileImageView.image = .imgProfileGreen | ||
| case "BLUE": | ||
| profileImageView.image = .imgProfileBlue | ||
| default: | ||
| profileImageView.kf.setImage( | ||
| with: profileImageURL, | ||
| placeholder: [UIImage.imgProfilePurple, UIImage.imgProfileBlue, UIImage.imgProfileGreen].randomElement() | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이것도 공통되는 로직이라 리팩토링할 때 한번 빼보면 좋을 것 같습니닷.
…o feat/#182-profile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
Wable-iOS/Domain/UseCase/Onboarding/UserProfileUseCase.swift (1)
20-34: 🛠️ Refactor suggestion
executeoverloads are ambiguous and difficult to evolve
Two overloads share the same base name
execute; at every call-site you have to rely on the compiler’s contextual type inference to decide which one you meant.
• Readability suffers (“what doesexecuteactually do here?”).
• Future additions will quickly break source-compatibility.The update variant exposes 6 optionals. Call-sites will end up passing long sequences of
nil, hurting both clarity and safety.Suggested approach (sketch):
final class UserProfileUseCase { … - func execute(profile: UserProfile? = nil, - isPushAlarmAllowed: Bool? = nil, - isAlarmAllowed: Bool? = nil, - image: UIImage? = nil, - defaultProfileType: String? = nil) -> AnyPublisher<Void, WableError> { + func update(_ command: UpdateUserProfileCommand) -> AnyPublisher<Void, WableError> { return repository.updateUserProfile( - profile: profile, - isPushAlarmAllowed: isPushAlarmAllowed, - isAlarmAllowed: isAlarmAllowed, - image: image, + profile: command.profile, + isPushAlarmAllowed: command.isPushAlarmAllowed, + isAlarmAllowed: command.isAlarmAllowed, + image: command.image, fcmToken: repository.fetchFCMToken(), - defaultProfileType: defaultProfileType + defaultProfileType: command.defaultProfileType ) - } - - func execute(userID: Int) -> AnyPublisher<UserProfile, WableError> { + .eraseToAnyPublisher() + } + + func fetch(userID: Int) -> AnyPublisher<UserProfile, WableError> { return repository.fetchUserProfile(memberID: userID) } } + +struct UpdateUserProfileCommand { + var profile: UserProfile? + var isPushAlarmAllowed: Bool? + var isAlarmAllowed: Bool? + var image: UIImage? + var defaultProfileType: String? +}Benefits:
• Call-sites read likeprofileUseCase.update(cmd)orprofileUseCase.fetch(id:).
• Adding fields becomes non-breaking.
• Encapsulating the arguments opens the door for validation logic inside the command.
♻️ Duplicate comments (3)
Wable-iOS/Presentation/Profile/ProfileEditViewController.swift (3)
18-27: Tight coupling to concrete repositories – prior feedback still appliesThe view-controller instantiates
ProfileRepositoryImpl,AccountRepositoryImpl, andUserSessionRepositoryImpldirectly. This hinders unit testing and breaks the dependency-inversion principle. Previous review already pointed this out; consider constructor injection of protocol-typed use-cases or a view-model.
107-110: Compile-time error – missing()onuppercased
String.uppercasedis a method. Without the parentheses the code won’t compile.- defaultImage = rootView.defaultImageList[0].uppercased + defaultImage = rootView.defaultImageList[0].uppercased()
129-141: Nickname duplication logic treats any non-error as “usable”Using
completion == .finishedassumes a network error means “nickname already in use”, which is the opposite of what you want. Explicitly distinguish.failureand surface an error to the user.
🧹 Nitpick comments (4)
Wable-iOS/Domain/UseCase/Onboarding/UserProfileUseCase.swift (1)
21-30: Publisher returned withouteraseToAnyPublisher()leaks implementation detailReturning the raw publisher you get from the repository means callers now depend on its concrete type. If the repository later changes (e.g. switches from
PassthroughSubjecttoFuture) you will break ABI.Add
.eraseToAnyPublisher()before returning.- ) + ) + .eraseToAnyPublisher()This keeps the use-case API stable.
Wable-iOS/Presentation/Profile/ProfileEditViewController.swift (3)
17-18: Redundant= nilinitialisation
Swiftinitialises optionals tonilby default. The assignment is unnecessary and triggers a SwiftLint warning.-private var defaultImage: String? = nil +private var defaultImage: String?🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 17-17: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
218-225: Unusedcompletionparameter – replace with_
SwiftLintflags this closure parameter as unused.- .sink { _ in } receiveValue: { [weak self] profile in + .sink { _ in } receiveValue: { [weak self] profile in(or simply supply only the
receiveValueclosure).
262-269: TypoconditiionLabel→conditionLabelThe double “i” slipped through everywhere (
rootView.conditiionLabel). Fixing the name improves readability and prevents future search/replace mistakes.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
📒 Files selected for processing (5)
Wable-iOS.xcodeproj/project.pbxproj(31 hunks)Wable-iOS/Domain/UseCase/Onboarding/UserProfileUseCase.swift(3 hunks)Wable-iOS/Presentation/Onboarding/ViewController/AgreementViewController.swift(1 hunks)Wable-iOS/Presentation/Profile/ProfileEditViewController.swift(1 hunks)Wable-iOS/Presentation/Viewit/List/View/Cell/ViewitListCell.swift(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- Wable-iOS/Presentation/Onboarding/ViewController/AgreementViewController.swift
- Wable-iOS/Presentation/Viewit/List/View/Cell/ViewitListCell.swift
🧰 Additional context used
🪛 SwiftLint (0.57.0)
Wable-iOS/Presentation/Profile/ProfileEditViewController.swift
[Warning] 17-17: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
[Warning] 30-30: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
[Warning] 15-15: TODOs should be resolved (유즈케이스 리팩 후에 뷰모델 만들어 넘기기)
(todo)
[Warning] 235-235: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
🔇 Additional comments (9)
Wable-iOS.xcodeproj/project.pbxproj (9)
33-33: Improved naming from AccountDeleteReason to WithdrawalReasonThis renaming provides better semantics by using "Withdrawal" which more precisely describes the user action of leaving the service, rather than the generic "Delete" term.
203-203: Improved naming from CreateUserProfileUseCase to UserProfileUseCaseThis change broadens the scope of the use case from only creation to potentially handling multiple profile operations, making it more versatile and reusable.
242-250: Well-structured addition of new Use Cases for Profile functionalityThe addition of these use cases (FetchUserProfileUseCase, FetchUserCommentListUseCase, FetchUserContentListUseCase, FetchAccountInfoUseCase, etc.) follows good separation of concerns and provides clear interfaces for different profile-related operations.
242-314: Comprehensive implementation of Profile architectureThe additions include a complete set of components for the profile feature, following MVVM architecture with:
- View components (MyProfileViewController, ProfileInfoCell, ProfileSegmentedHeaderView)
- View models (MyProfileViewModel)
- Data models (ProfileViewItem, ProfileSegmentKind)
- Associated use cases
This demonstrates good architecture and separation of concerns.
301-314: Well-structured Withdrawal flow implementationThe implementation introduces a complete withdrawal flow with:
- A reason selection screen (WithdrawalReasonViewController)
- A guide/confirmation screen (WithdrawalGuideViewController)
- Supporting components (cells, view models, models)
The hierarchical group structure with separate Reason and Guide subgroups keeps the code organized and maintainable.
Also applies to: 1981-2067
321-324: Clear separation of AccountInfo componentsThe AccountInfo implementation follows the project's architectural patterns with a dedicated view controller, cells, models, and view model, all properly organized in their own group.
Also applies to: 2131-2172
1617-1640: Proper implementation of AlarmSetting featureThe AlarmSetting components are organized into a separate group with a clear separation between view and view model, maintaining consistency with the project's architectural patterns.
1-3164: Comprehensive Profile Feature ImplementationThis PR delivers a substantial enhancement to the app with a well-structured profile feature implementation that includes:
- My Profile section with segmented views for content/comments
- Account information management
- Alarm settings configuration
- Complete account withdrawal flow
The architecture follows MVVM with clean separation between views, view models, and use cases, making the code maintainable and testable.
1-3164:Details
✅ Verification successful
Verify async/await implementation in APIProvider
Based on the PR description, a key objective was to "introduce async/await syntax to improve code readability and maintainability by moving away from complex Combine-based code" and specifically update the
requestmethod in APIProvider. However, I don't see direct changes to the APIProvider file in this project file. Please ensure those changes are included in a separate PR or file.
🏁 Script executed:
#!/bin/bash # Check if APIProvider.swift has been modified to include async/await syntax echo "Checking for async/await implementation in APIProvider.swift..." grep -n "async" "Wable-iOS/Infra/Network/APIProvider.swift" || echo "No async keyword found in APIProvider.swift"Length of output: 223
Async/Await Confirmed in APIProvider.swift
The
requestmethod in Wable-iOS/Infra/Network/APIProvider.swift has been updated to include theasync throws -> Dsignature (line 76). No further action is needed here.
👻 PULL REQUEST
📄 작업 내용
💻 주요 코드 설명
📍 async/await을 도입하여 Combine 코드 지옥에서 탈피해보자.
request를async/await으로도 동작하게끔 하여, 그 이후의 사용되는 로직에서도async/await으로 작성할 수 있도록 하였습니다.🔗 연결된 이슈
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Refactor
Chores