https://explorer89.tistory.com/303
Firebase - Combine 프레임워크 사용 비동기 작업 처리 (저장) map VS flatmap
https://explorer89.tistory.com/302 collectionUsers(retreive id: String)https://explorer89.tistory.com/301 CreateUser 함수?https://explorer89.tistory.com/300 AnyPublisher?https://explorer89.tistory.com/299 Combine, FirebaseStore 데이터 저장https:
explorer89.tistory.com
func collectionUsers(updateFields: [String: Any], for id: String) -> AnyPublisher<Bool, Error> {
db.collection(userPath).document(id).updateData(updateFields)
.map { _ in true }
.eraseToAnyPublisher()
}
- updateFields: 업데이트하려는 데이터의 키-값 쌍을 담고 있는 딕셔너리입니다.
- id: Firestore에서 업데이트할 문서의 ID입니다.
- db.collection(userPath).document(id).updateData(updateFields): Firestore의 updateData 메서드는 지정된 문서를 업데이트합니다. 이 메서드는 성공 시 Void를 반환하고, 실패 시 오류를 던집니다.
- .map { _ in true }: 업데이트 성공 시 결과를 true로 변환합니다.
- .eraseToAnyPublisher(): 반환 타입을 AnyPublisher<Bool, Error>로 제한합니다.
문제점
이 코드에는 몇 가지 문제가 있습니다:
- updateData는 Publisher가 아님:
- updateData는 Combine의 Publisher를 반환하지 않습니다. 대신, Firestore API는 비동기 작업을 클로저를 통해 처리합니다.
- 따라서 updateData를 Combine과 함께 사용하려면 적절히 변환해야 합니다.
- Combine의 Future를 사용하여 Firestore의 비동기 작업을 Combine 스트림으로 변환할 수 있습니다
- 에러 처리 부족:
- Firestore의 updateData 메서드에서 발생한 에러를 Combine의 스트림으로 처리할 수 있도록 개선이 필요합니다.
func collectionUsers(updateFields: [String: Any], for id: String) -> AnyPublisher<Bool, Error> {
Future<Bool, Error> { promise in
db.collection(userPath).document(id).updateData(updateFields) { error in
if let error = error {
promise(.failure(error)) // 에러 발생 시
} else {
promise(.success(true)) // 성공 시
}
}
}
.eraseToAnyPublisher()
}
수정된 코드의 작동 방식
- Future 사용:
- Firestore의 updateData 메서드는 클로저 기반으로 동작하기 때문에, 이를 Combine 스트림으로 감싸기 위해 Future를 사용했습니다.
- promise:
- Firestore 작업이 완료되면 promise를 호출하여 성공(true) 또는 실패(Error) 값을 반환합니다.
- eraseToAnyPublisher:
- 반환 타입을 AnyPublisher<Bool, Error>로 제한합니다.
요약
수정된 코드는 Firestore의 비동기 작업을 Combine과 통합하여 안정적으로 성공/실패를 처리할 수 있도록 만듭니다. Combine과 Firestore를 함께 사용하려면 Future 또는 다른 Combine 기반의 비동기 변환 방법을 사용하는 것이 필수적입니다.
private func updateUserData() {
guard let displayName,
let username,
let bio,
let avatarPath,
let id = Auth.auth().currentUser?.uid
else { return }
let updateFields: [String: Any] = [
"displayName": displayName,
"username": username,
"bio": bio,
"avatarPath": avatarPath,
"isUserOnboarded": true
]
DatabaseManager.shared.collectionUsers(updateFields: updateFields, for: id)
.sink { [weak self] completion in
if case .failure(let error) = completion {
print(error.localizedDescription)
self?.error = error.localizedDescription
}
} receiveValue: { [weak self] onboardingState in
self?.isOnboardingFinished = onboardingState
}
.store(in: &subscriptions)
}
전체적인 함수 동작 요약
- 사용자 정보(displayName, username, bio, avatarPath)와 현재 로그인한 사용자의 uid를 가져옵니다.
- Firestore 업데이트를 위해 필요한 데이터를 딕셔너리로 구성합니다.
- collectionUsers 함수를 호출하여 Firestore에 업데이트를 요청합니다.
- Combine의 sink를 사용해 작업 결과를 처리합니다:
- 실패 시 에러를 처리하고, 로그와 UI 상태를 업데이트합니다.
- 성공 시 온보딩 완료 상태를 업데이트합니다.
개선 포인트 적용
- Helper 메서드로 분리:
- prepareUpdateFields: 업데이트할 필드 준비를 별도 함수로 분리해 가독성을 높였습니다.
- handleCompletion 및 handleSuccess: 성공 및 실패 처리 로직을 별도의 함수로 나누어 코드 중복을 줄이고 유지보수를 용이하게 했습니다.
- 에러 메시지 세분화:
- prepareUpdateFields 또는 인증 실패 시 적절한 오류 메시지를 출력합니다.
- Firestore 작업이 실패한 경우와 성공했지만 비정상적인 값이 반환된 경우를 구분하여 처리합니다.
- 로깅 추가:
- 각 주요 단계에서 print를 통해 진행 상황과 오류를 기록합니다.
- UI 상태 업데이트 명확화:
- self.error와 self.isOnboardingFinished 값을 적절히 업데이트하여 UI와 동기화합니다.
- Guard 및 Optional 처리 강화:
- Optional 바인딩을 적극 활용해 불필요한 nil 상태의 작업을 방지했습니다.
private func updateUserData() {
guard let updateFields = prepareUpdateFields(),
let id = Auth.auth().currentUser?.uid
else {
print("Error: Missing required user data or user is not authenticated.")
self.error = "User data is incomplete or user is not authenticated."
return
}
DatabaseManager.shared.collectionUsers(updateFields: updateFields, for: id)
.sink { [weak self] completion in
self?.handleCompletion(completion)
} receiveValue: { [weak self] isSuccess in
self?.handleSuccess(isSuccess)
}
.store(in: &subscriptions)
}
// MARK: - Helper Methods
/// Prepares the fields to update for Firestore
private func prepareUpdateFields() -> [String: Any]? {
guard let displayName,
let username,
let bio,
let avatarPath
else { return nil }
return [
"displayName": displayName,
"username": username,
"bio": bio,
"avatarPath": avatarPath,
"isUserOnboarded": true
]
}
/// Handles the completion of the Firestore update
private func handleCompletion(_ completion: Subscribers.Completion<Error>) {
switch completion {
case .failure(let error):
print("Firestore update failed: \(error.localizedDescription)")
self.error = error.localizedDescription
case .finished:
print("Firestore update finished successfully.")
}
}
/// Handles successful Firestore update
private func handleSuccess(_ isSuccess: Bool) {
guard isSuccess else {
print("Unexpected error: Firestore update returned false.")
self.error = "Firestore update returned unexpected failure."
return
}
print("Firestore update was successful.")
self.isOnboardingFinished = true
}
'UIKIT' 카테고리의 다른 글
FireStore 특정 사용자의 트윗 정보 가져오기 + Combine (0) | 2025.01.10 |
---|---|
Firestore에 새로운 트윗 저장 + Combine 비동기 처리 (0) | 2025.01.09 |
Firebase - Combine 프레임워크 사용 비동기 작업 처리 (저장) map VS flatmap (0) | 2025.01.09 |
collectionUsers(retreive id: String) (0) | 2025.01.08 |
CreateUser 함수? (1) | 2025.01.08 |