[SwiftUI] TwitterClone: UserData Handling

Junyoung Park·2022년 11월 17일
0

SwiftUI

목록 보기
106/136
post-thumbnail

🔴 Let's Build Twitter with SwiftUI (iOS 15, Xcode 13, Firebase, SwiftUI 3.0)

TwitterClone: UserData Handling

구현 목표

  • 회원 정보를 통해 유저 정보 패치 및 UI 패치

구현 태스크

  • 유저 데이터 모델링
  • 유저 정보를 통한 데이터 패치
  • 퍼블리셔 데이터를 통한 UI 패치

핵심 코드

.toolbar {
            ToolbarItem(placement: .navigationBarLeading) {
                if let profileImage = viewModel.profileImage {
                    Button {
                        withAnimation(.easeInOut) {
                            showMenu.toggle()
                        }
                    } label: {
                        Image(uiImage: profileImage)
                            .resizable()
                            .scaledToFill()
                            .frame(width: 32, height: 32)
                            .clipShape(Circle())
                    }
                }
            }
        }
  • 환경 변수를 통해 관찰 중인 뷰 모델 내의 유저 정보 퍼블리셔 값의 변화에 따라 해당 데이터를 통해 UI를 패치
@Published var userSession: FirebaseAuth.User? {
        didSet {
            fetchUser()
        }
    }
  • 회원 가입, 로그인, 로그아웃 등 현재 파이어베이스 유저 정보에 따라 새롭게 데이터를 자동으로 패치하도록 didSet에 위치
func fetchUser() {
        guard let uid = userSession?.uid else {
            currentUser = nil
            profileImage = nil
            return
        }
        service.fetchUser(with: uid) { [weak self] result in
            switch result {
            case .failure(let error):
                print(error.localizedDescription)
            case .success(let user):
                self?.currentUser = user
                self?.downloadProfileImage(with: user.profileImageURL)
            }
        }
    }
  • 현재 유저 세션의 uid를 다큐먼트 아이디로 사용, 유저 정보 데이터 패치
struct UserService {
    func fetchUser(with uid: String, completion: @escaping(Result<UserModel, Error>) -> Void) {
        Firestore.firestore().collection("users")
            .document(uid)
            .getDocument { document, error in
                guard
                    let document = document,
                    let user = try? document.data(as: UserModel.self) else {
                    if let error = error {
                        completion(.failure(error))
                    } else {
                        completion(.failure(URLError(.badURL)))
                    }
                    return
                }
                completion(.success(user))
            }
    }
}
  • 유저 정보를 다운로드받는 서비스 클래스의 static 함수
  • 핸들러를 통해 다운로드받은 데이터를 비동기 리턴
private func downloadProfileImage(with urlString: String) {
        guard let url = URL(string: urlString) else { return }
        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard
                let response = response as? HTTPURLResponse,
                response.statusCode >= 200,
                response.statusCode < 400,
                error == nil,
                let data = data,
                let image = UIImage(data: data) else { return }
            DispatchQueue.main.async { [weak self] in
                self?.profileImage = image
            }
        }
        .resume()
    }
  • 유저 정보 내 프로필 URL을 통해 이미지를 다운로드
  • 뷰 모델 내 퍼블리셔인 profileImage에 해당 값을 넣어줌으로써 해당 값을 관찰 중인 실제 뷰에서 UI 인터렉션 가능
if let profileImage = authViewModel.profileImage {
                            Image(uiImage: profileImage)
                                .resizable()
                                .scaledToFill()
                                .clipShape(Circle())
                                .frame(width: 48, height: 48)
                        }
  • 뷰 모델 내 이미지 데이터가 존재할 때 해당 UIImage를 통해 이미지 렌더링
  • 이미지 다운로드를 메인 스레드에서 받았기 때문에 해당 이벤트는 메인 스레드에서 발생하는 게 보장

구현 화면

유저 정보를 관리하는 뷰 모델을 환경 변수로 받고 있으니 매우 편리하게 사용하지만, 과연 메모리 누수 문제는 없을지 걱정이 된다. 물론 현재 환경에서는 모든 뷰에서 해당 유저 정보에 접근 가능해야 한다는 점에서 매우 효율적이지만, 어느 시점에서 적절하게 DI를 통해 의존성을 낮출 수 있을까?

profile
JUST DO IT

0개의 댓글