이제 우리 프로젝트 코드보고 공부할건데,,
이게 어려운게
파베만 공부하면되는게 아니라 우리코드는 rx로 되어있어서,,
rx도 알아야한다,,,^_ㅜ
rx 공부도 올려야지..
일단 로그인쪽 코드먼저 봐보겠음..
보면 rx로 코드가 되어있어서 내가 여태 알아본 파이어베이스 코드랑 다름..
뭐 당연히 엄청 다른건 아니지만^^
이제 지티피한테 물어봐서 rx코드 제거할거임
func emailSignIn(email: String, password: String, completion: @escaping (Result<user, Error>) -> Void) {
Auth.auth().signIn(withEmail: email, password: password) { result, error in
if let error = error {
// 오류 처리
let nsError = error as NSError
if let errorCode = AuthErrorCode(rawValue: nsError.code) {
switch errorCode {
case .invalidCredential:
completion(.failure(AuthError.invalidCredential))
default:
completion(.failure(error))
}
} else {
completion(.failure(error))
}
return
}
guard let result = result else {
completion(.failure(AuthError.signInFailError))
return
}
if result.user.isEmailVerified {
completion(.success(result.user))
} else {
completion(.failure(AuthError.emailVerificationFailError))
}
}
}
Single과 관련된 RxSwift 코드 제거.
completion 클로저를 사용해서 결과 처리. 이 클로저는 Result<User, Error> 타입 반환
AuthError 를 정의하고 사용하는 방법 조정. AuthError 는 사용자 정의 오류 타입
@escaping 을 쓰는 이유를 잘몰라서 물어봤는데
@escaping 키워드는 클로저가 함수가 반환된 이후에도 실행될 수 있음을 나타내는것이라고한다.
즉, 함수의 실행이 끝난 후에도 클로저가 호출될 수 있는 경우 @escaping을 사용해야 한다.
emailSignIn 함수의 경우 completion 클로저가 Auth.auth().signIn 의 비동기 작업이 완료된 후에 호출된다.
이 비동기 작업은 네트워크 요청이므로 함수가 종료된 후에도 일정 시간이 지나야 완료된다.
따라서 completion 클로저가 함수가 반환된 이후에 호출될 수 있고, 이 경우 @escaping 이 필요하다.
만약 @escaping 을 사용하지 않으면, 컴파일러가 클러저가 함수 내부에서만 사용된다고 가정하게 되어 오류가 발생한다.
흠 이부분 말고 다른 부분도 뜯어보장
이건 내가 작성했던건데
잘 모른다 ㅎ
이걸 다시 작성해 보자!
일단 RxSwift를 사용하지 않은 코드로 작성해줄건데 그럼 Single 대신 클로저를 사용해서 작성할거다.
// 즐겨찾기 추가 메서드 (Rx 제거 버전)
func addBookmark(forUserId userId: String, bookmarkId: String, completion: @escaping (Result<Void, Error>) -> Void) {
guard let currentUser = Auth.auth().currentUser?.uid else {
let error = NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "사용자 인증 실패"])
completion(.failure(error))
return
}
let ref = db.collection("member").document(currentUser)
ref.updateData(["bookMarkUsers": FieldValue.arrayUnion([bookmarkId])]) { error in
if let error = error {
completion(.failure(error))
} else {
completion(.success(()))
}
}
}
흠,,, 흠,, 알듯말듯 알쏭달쏭~~
다른,, 코드도 보자고용
그리고 read해오는 것도 찾아볼거임
read 해올때 필요한게 그거지 구조체 있어야하고 응.. 마저! 뎡답아닐까?
get을 쓸거다?어? 알게찌?
그러면 우리 코드중에 퍼피등록이랑 퍼피정보 가져오는거를 보겠음..
PuppyRegistrationViewModel 에는 퍼피 등록하는 코드, 업데이트하는 코드가 작성되어있고 (creat, update)
MyPageViewModel 에는 불러오는 코드가 작성되어있음 (read)
remove(delete)는 .. 채팅에 있는데 그거 보기 힘들어... 일단 저것들만 보자,,
func creatPuppy(userId: String, name: String, age: In, petImage: String, tag: [String], completion: @escaping (Result<Pet, Error>) -> Void) {
let docRef = db.collection("pet").document()
let petId = docRef.documentID
let pet = Pet(id: petId, userId: userId, name: name, age: age, petImage: petImage, tag: tag)
docRef.setData(pet.dictionary) { error in
if let error = error {
completion(.failure(error))
} else {
let memberDocRef = db.collection("member").document(userId)
memberDocRef.updateData([
"pyppies": FieldValue.arrayUnion([petId])
]) { error in
if let error = error {
completion(.failure(error))
} else {
completion(.success(pet))
}
}
}
}
}
func updatePuppy(petId: String, userId: String, name: String, age: Int, petImage: String, tag: [Sting], completion: @escaping (Result<Pet, Error>) -> Void) {
let docRdf = db.collection("pet").document(petId)
let updatedPet = pet(id: petId, userId: userId, name: name, age: age, petImage: petImage, tag: tag)
docRef.setData(updatePet.dictionary, merge: true) { error in
if let error = error {
completion(.failure(error)
} else {
completion(.success(updatePet))
}
}
}
기존 프로젝트 코드에서는 RxSwift로 작성되어있지만 Single대신 completion 클로저를 사용해서 작업의 결과를 전달한다.
이거는 한번 다시 Rx로 바꿔보자
func createPuppy(userId: String, name: String, age: Int, petImage: String, tag: [String]) -> Single<Pet> {
return Single.create { single in
let docRef = self.db.collection("pet").document()
let petId = docRef.documentID
let pet = Pet(id: petId, userId: userId, name: name, age: age, petImage: petImage, tag: tag)
docRef.setData(pet.dictionary) { error in
if let error = error {
single(.failure(error))
} else {
let memberDocRef = self.db.collection("member").document(userId)
memberDocRef.updateData([
"puppies": FieldValue.arrayUnion([petId])
]) { error in
if let error = error {
single(.failure(error))
} else {
single(.success(pet))
}
}
}
}
return Disposables.creat()
}
}
func updatePuppy(petId: String, userId: String, name: String, age: Int, petImage: String, tag: [String]) -> Single<Pet> {
return Single.create { single in // Single.create는 Single을 직접 생성할 수 있게 해주는 생성함수
let docRef = self.db.collection("pet").document(petId)
let updatePet = Pet(id: petId, userId: userId, name: name, age: age, petImage: petImage, tag: tag)
docRef.setData(updatePet.dictionary, merge: true) { error in
if let error = error {
single(.failure(error))
} else {
single(.success(updatePet))
}
}
return Disposables.create()
}
}
rx로 바꾸면서 궁금한건
Single을 쓴것,,
아니 내가 강의 들은것중에서는 Single을 몰라도 된다했단말야!
그럼 Single을 사용한 이유는 뭘까!
얘는 단일 이벤트 이기 때문이당
한 번의 Firebase 작업으로 끝나는 비동기 작업. 즉, 한 번의 성공(Pet 객체) 또는 한 번의 실패 (Error)만 발생한다.
Single을 사용하면 코드가 깔끔해지고, 비동기 작업의 결과를 다루는 방식이 명확해진다.
RxSwift의 subscribe(onSuccess:onFailure:)와 같은 연산자를 이용해 쉽게 성공과 실패를 처리할 수 있다.
이게 정보가져오는게,,
멤버 정보 가져오고,, 가져온 멤버 문서 중에 puppies 정보 가져오고 그 다음에 pet 컬렉션 중에서 가져온 petId 랑 맞는 정보를.. 찾아야해서 원래 코드가 넘 길다길어
걍 다 해볼까 첨부터.. 걍 응.. 다.. 멤버정보는 일단.. 회원가입할때 저장이되고!!!
class MyPageViewModel {
private let db = Firestore.firestore()
let memberSubject = PublichSubject<Member>()
let petListSubject = PublichSubject<[Pet]>()
private let disposeBag = DisposeBag()
// 멤버 정보 가져오기 생략~
...
// 가져온 멤버 문서 중 puppies 정보 가져오기
func fetchMemberPets(memberId: String) -> Single<[Pet]> {
return Single.create { single in
let docRef = self.db.colletion("member").document(memberId)
docRef.getDocument { (document, error) in
if let document = document, document.exists, let data = document.data(), let puppies = data["puppies"] as? [String] {
print("불러온 강아지 ID 리스트: \(puppies)")
self.getPetsInfo(petIds: puppies)
.subscribe(onSuccess: { petsInfo in
single(.success(petsInfo))
}, onFailure: { error in
single(.failure(error))
}).disposed(by: self.disposeBag)
} else if let error = error {
single(.failure(error))
} else {
single(.success([]))
}
}
return Disposables.creat()
}
}
// pet 컬렉션 중 가져온 petId와 맞는 정보 탐색
func getPetsInfo(petIds: [String]) -> Single<[Pet]> {
return Single.create { single in
var pets: [Pet] = []
let dispatchGroup = DispatchGroup()
for petId in petIds {
dispatchGroup.enter()
self.db.collection("pet").document(petId).getDocument { (document, error) in
if let document = document, document.exists, let data = document.data() {
print("불러온 강아지 데이터: \(data)")
if let id = data["id"] as? String,
let age = data["age"] as? Int,
let name = data["name"] as? String,
let petImage = data["petImage"] as? String,
let tag = data["tag"] as? [String],
let userId = data["userId"] as? String {
let pet = Pet(id: id, userId: userId, name: name, age: age, petImage: petImage, tag: tag)
pets.append(pet)
}
} else {
print("Error fetching pet data for ID \(petId):
\(error?.localizedDescription ?? "Unknown error")")
}
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
if pets.isEmpty {
single(.success([]))
} else {
single(.success(pets))
}
}
return Disposables.create()
}
나 이거 하면서 처음보는 것들이 있으니까 어려운거야,,,
dispatchGroup.enter()
- 이 코드는 DispatchGroup 에 새로운 비동기 작업이 시작되었음을 알린다. enter() 를 호출하면, 비동기 작업이 완료되기 전에 그룹이 끝나는 것을 방지할 수 있다.
- 즉, Firestore에서 해당 petId 에 대한 문서를 가져오는 작업이 시작되었음을 알리고, 나중에 dispatchGroup.leave() 를 호ㅜㄹ할 때까지 그룹에서 기다리게 된다.
dispatchGroup.leave()
- 작업이 완료되었음을 DispatchGroup 에 알린다. 이 부분은 dispatchGroup.enter() 에 대응하는 부분으로, 현재 진행중인 작업이 끝났다는 신호를 준다. 비동기 작업이 끝났으니 그룹에서 빠져나가는 역할을 한다.
- 이를 통해, Firebase에서 데이터를 가져오는 작업이 끝났음을 DispatchGroup 에 알려준다.
dispatchGroup.notify(queue: .main) { ... }
- DispatchGroup 에 등록된 모든 작업이 완료되면 호출되는 부분이다. 모든 작업이 leave() 되면 이 클로저가 호출되어 결과를 반환한다.
- 여기서는 Firebase에서 가져온 pets 배열을 성공적으로 반환하거나, 빈 배열을 반환한다.
if pets.isEmpty { single(.success([])) } else { single(.success(pets)) }
- Firebase에서 데이터를 가져온 결과를 기반으로 pets 배열이 비었으면 빈 배열을 반환하고, 그렇지 않으면 pets 배열을 반환한다.
- single(.success(pets)) 을 통해 Single 이 성공적으로 결과를 반환하도록 한다.
여기서 내가 궁금했던거는 enter 랑 leave 이거였음..
dispatchGroup.enter() 와 dispatchGroup.leave() 의 역할:
- dispatchGroup.enter() 는 새로운 비동기 작업이 시작될 때 호출된다. 비동기 작업이 끝나기 전까지는 그룹이 완료된 것으로 간주되지 않는다.
- dispatchGroup.leave() 는 비동기 작업이 완료되었음을 알린다. 이 부분이 호출될 때까지 그룹은 대기상태에 있고, 모든 작업이 leave() 될 때까지 notify() 는 호출되지 않는다.
- 이를 통해 여러 개의 비동기 작업을 병렬로 처리한 뒤, 모든 작업이 완료된 후 후속 작업을 진행할 수 있다.
콘솔창을 함 봐보자고~!
어.. 음.. 이제 바로 내꺼 해볼까?? 했는데
아니, 음, 조금만 더 공부해볼게! 좀 더 블로그 글들을 구경하고 코드 작성해봐야겠다
'iOS (스파르타)' 카테고리의 다른 글
JSON 을 알아보쟈,, (1) | 2024.09.30 |
---|---|
아악! 공부해야할것 (0) | 2024.09.19 |
파이어베이스 공부하깅 - 3 (0) | 2024.09.12 |
파이어베이스 공부하깅 - 2 (1) | 2024.09.11 |
파이어베이스 공부하깅 - 1 (2) | 2024.09.10 |