본문 바로가기

Project/MovieClip

❌ 리뷰 삭제 ... 왜 안되니?

🔍 GTMSessionFetcher 오류 분석

GTMSessionFetcher 0x10141bab0 (https://firebasestorage.googleapis.com:443/v0/b
/movieclip-6a2c3.firebasestorage.app/o?delimiter=/&prefix=users
/L21gNu8OeIQa9BIDGuaoFwJmXD63/reviews/E6380A5A-CB53-490C-A044-D8E962791A2D/) 
was already running

이 오류 메시지는 Firebase Storage에서 같은 URL에 대한 여러 개의 비동기 요청이 동시에 실행되고 있음을 의미합니다.

 

 

🤔 리뷰 삭제 

1️⃣ Firebase Storage에서 이미지 삭제

 

2️⃣ Firestore 데이터 삭제

🔥 최종 정리: 동작 흐름

1️⃣ Firebase Storage에서 리뷰와 관련된 이미지 삭제 요청
2️⃣ 이미지 삭제가 완료되면 Firestore에서 해당 리뷰 문서 삭제

 

 

📌 해결책 

 

✅ Firebase의 Storage에서 리뷰로 등록된 이미지 삭제하는 메서드 구현 

 func deleteReviewPhotos(userID: String, reviewID: String) -> AnyPublisher<Void, Error> {
        let storageRef = storage.reference()
        let folderPath = "users/\(userID)/reviews/\(reviewID)/"
        let reviewFolderRef = storageRef.child(folderPath)

        return Future<Void, Error> { promise in
            reviewFolderRef.listAll { result in
                switch result {
                case .success(let storageListResult):
                    let items = storageListResult.items
                    if items.isEmpty {
                        print("✅ 삭제할 이미지 없음 (리뷰만 삭제)")
                        promise(.success(())) // ✅ 이미지가 없어도 Firestore 리뷰 삭제 진행
                        return
                    }

                    let dispatchGroup = DispatchGroup()
                    var deletionErrors: [Error] = []

                    for item in items {
                        dispatchGroup.enter()
                        item.delete { error in
                            if let error = error {
                                print("❌ 이미지 삭제 실패: \(error.localizedDescription)")
                                deletionErrors.append(error)
                            } else {
                                print("✅ 이미지 삭제 성공: \(item.fullPath)")
                            }
                            dispatchGroup.leave()
                        }
                    }

                    dispatchGroup.notify(queue: .main) {
                        if deletionErrors.isEmpty {
                            print("✅ 모든 이미지 삭제 완료")
                            promise(.success(()))
                        } else {
                            print("❌ 일부 이미지 삭제 실패")
                        }
                    }

                case .failure(let error):
                    print("❌ 이미지 목록 불러오기 실패: \(error.localizedDescription)")
                    promise(.failure(error))
                }
            }
        }
        .eraseToAnyPublisher()
    }

 

✅ Firebase의 Database에서 리뷰ID를 통해 리뷰 삭제하는 메서드 구현 

func collectionReviews(delete reviewID: String) -> AnyPublisher<Void, Error> {
        guard let userID = Auth.auth().currentUser?.uid else {
            return Fail(error: NSError(domain: "AuthError", code: -1, userInfo: [NSLocalizedDescriptionKey: "로그인한 사용자가 없습니다."]))
                .eraseToAnyPublisher()
        }
        
        return Future<Void, Error> { promise in
            self.db.collection("users")
                .document(userID)
                .collection("reviews")
                .document(reviewID)
                .delete { error in
                    if let error = error {
                        promise(.failure(error))
                    } else {
                        promise(.success(()))
                    }
                }
        }
        .eraseToAnyPublisher()
        
    }

 

✅ ViewModel에서 리뷰로 등록된 이미지를 삭제 후 (deleteReviewPhotos), reviewID에 해당하는 리뷰 삭제 

func deleteUserReview(reviewID: String) {
    guard let userID = Auth.auth().currentUser?.uid else {
        self.error = "유저 정보 없음 "
        return
    }

    StorageManager.shared.deleteReviewPhotos(userID: userID, reviewID: reviewID)
        .flatMap {
            DatabaseManager.shared.collectionReviews(delete: reviewID)
        }
        .sink { [weak self] completion in
            switch completion {
            case .failure(let error):
                self?.error = "리뷰 삭제 실패: \(error.localizedDescription)"
            case .finished:

                self?.isUserReviewDeleted = true
            }
        } receiveValue: {[weak self] in
            self?.isUserReviewDeleted = true
        }
        .store(in: &cancellables)
}