본문 바로가기

Project/FirebaseTest

FireBase - 회원 정보 불러오기_프로필 이미지 사용방식 (다운로드 vs URL 기반 로딩)

프로필 이미지의 사용 방식에 따라 다운로드 후 직접 처리URL 기반 로딩 중 어떤 방식을 선택할지 결정하는 것은 중요한 디자인 선택입니다. 아래에서는 프로필 이미지의 특성두 가지 방법의 장단점을 비교하여 어떤 경우에 어떤 방식을 선택하는 것이 더 적합한지 설명드리겠습니다.

 

프로필 이미지의 특성

  • 정적 데이터:
    • 프로필 이미지는 사용자가 자주 변경하지 않으며, 변경된 이후에는 비교적 오랜 시간 동안 동일한 이미지를 사용.
  • 반복 접근:
    • 앱에서 여러 화면에서 프로필 이미지를 사용할 가능성이 높음.
    • 자주 접근하는 데이터는 캐싱이 중요.
  • 중요 데이터:
    • 프로필 이미지는 사용자의 시각적 식별에 중요한 데이터로, 앱에서 항상 정확히 표시되어야 함.

 

두 가지 방식의 비교

방식 장점 단점
1. 다운로드 후
메모리에서 처리
- 이미지가 메모리에 직접 저장되어
빠르게 접근 가능.

- 오프라인 상태에서도 사용 가능.
- 고해상도 이미지의 경우 메모리 사용량 증가.
- 초기 로딩 시 다운로드로 인해 지연 발생.
2. URL 기반 동적 로딩
(SDWebImage)
- 초기 메모리 사용량 감소.
- SDWebImage가 제공하는 자동 캐싱을 통해
 네트워크 요청 최소화.

- 이미지 업데이트 시 실시간 반영.
- 캐시된 이미지를 제거하지 않으면 장기적으로 저장 공간 증가.
- 오프라인 상태에서는 사용할 수 없음.

 

 

어떤 방식이 더 나은가?

1) 다운로드 후 처리 방식

 

프로필 이미지가 다음과 같은 경우라면 다운로드 후 처리 방식이 더 적합합니다:

  • 오프라인 상태에서 사용 가능해야 함:
    • 예: 메시징 앱, 사용자 데이터 기반 앱.
  • 이미지가 자주 변경되지 않음:
    • 예: 프로필 이미지가 사용자의 정체성을 나타내며, 업데이트 빈도가 낮음.
  • 이미지 크기가 작음:
    • 예: 이미지가 고해상도가 아니며, 메모리에 부담을 주지 않을 정도의 크기.

 

2) URL 기반 로딩 방식

프로필 이미지가 다음과 같은 경우라면 URL 기반 방식이 더 적합합니다:

  • 이미지가 자주 변경됨:
    • 예: 사용자가 자주 프로필 사진을 변경하거나 실시간으로 업데이트가 필요함.
  • 캐싱을 활용한 네트워크 요청 감소:
    • SDWebImage는 자동 캐싱을 제공하므로, 변경이 없는 이미지는 네트워크 요청 없이 사용할 수 있음.
  • 다양한 디바이스에서 동일 데이터를 접근:
    • 예: 클라우드 환경에서 다중 디바이스를 사용하는 경우.

 

결론 및 추천

정적 프로필 이미지의 경우:

  • 다운로드 후 처리 방식 추천.
  • 사용자가 프로필을 변경하지 않는 이상, 메모리에서 빠르게 접근 가능.
  • 오프라인 상태에서도 이미지를 사용할 수 있음.

 

동적으로 자주 변경되는 이미지의 경우:

  • URL 기반 로딩 방식 추천.
  • SDWebImage의 캐싱을 활용하여 네트워크 요청을 최소화.
  • 실시간으로 변경된 프로필 이미지를 반영할 수 있음.

 

 

자주 변경되지 않는 프로필 이미지다운로드 후 메모리에서 처리.

실시간으로 변경될 가능성이 있는 이미지URL 기반 로딩.

 

 

 

URL 기반 로딩 방식

User 구조체 (데이터 모델)

import Foundation
import UIKit

struct User {
    let username: String
    let email: String
    let userUID: String
    
    let userImage: String?
}

 

 

 

AutheService 클래스 내 fetUser 메서드

    public func fetchUser(completion: @escaping (User?, Error?) -> Void) {
        guard let userUID = Auth.auth().currentUser?.uid else {
            completion(nil, NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "사용자가 로그인되어 있지 않습니다."]))
            return
        }
        
        let db = Firestore.firestore()
        
        db.collection("users").document(userUID).getDocument { snapshot, error in
            if let error = error {
                completion(nil, error)
                return
            }
            
            guard let snapshotData = snapshot?.data(),
                  let username = snapshotData["username"] as? String,
                  let email = snapshotData["email"] as? String else {
                completion(nil, NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Firestore 데이터가 올바르지 않습니다."]))
                return
            }
            
            let pathRef = Storage.storage().reference(withPath: "profile_images/\(userUID).jpg")
            
            // Firebase Storage에서 URL 가져오기
            pathRef.downloadURL { url, error in
                if let error = error {
                    print("Firebase Storage 다운로드 URL 가져오기 실패: \(error.localizedDescription)")
                    completion(nil, error)
                    return
                }
                
                let profileImageURL = url?.absoluteString
                print("Firebase Storage에서 다운로드 URL: \(profileImageURL ?? "없음")")
                
                // User 객체 생성 (URL 사용)
                let user = User(username: username, email: email, userUID: userUID, userImage: profileImageURL!)
                completion(user, nil)
            }
        }
    }

 

  • downloadURL 사용:
    • pathRef.downloadURL 메서드를 사용하여 Firebase Storage의 다운로드 URL을 가져옵니다.
    • 이 URL은 이미지를 직접 다운로드하지 않고, 필요한 시점에 네트워크 요청을 통해 이미지를 로드할 수 있도록 도와줍니다.

 

CustomImageView 클래스

import UIKit
import SDWebImage

class CustomImageView: UIImageView {

    enum ImageType {
        case system(String, pointSize: CGFloat)
        case user(userImageSource)
    }
    
    enum userImageSource {
        case image(UIImage)
        case url(URL)
    }
    
    private var imageType: ImageType? {
        didSet {
            updateImageAppearance()
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        configureImageViewStyle()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func configureImageViewStyle() {
        
        self.layer.borderWidth = 3
        self.layer.borderColor = UIColor.black.cgColor
        self.backgroundColor = .white
        self.layer.cornerRadius = 50
        self.clipsToBounds = true
        
    }
    
    func setImageType(_ type: ImageType) {
        self.imageType = type
    }
    
    
    private func updateImageAppearance() {
        
        switch imageType {
        case .system(let systemName, let pointSize):
            let config = UIImage.SymbolConfiguration(pointSize: pointSize)
            self.image = UIImage(systemName: systemName, withConfiguration: config)
            self.tintColor = .black
            self.contentMode = .center
            
        case .user(let source):
            switch source {
            case .image(let userImage):
                self.contentMode = .scaleAspectFill
                self.image = userImage
            case .url(let url):
                self.contentMode = .scaleAspectFill
                self.sd_setImage(with: url, placeholderImage: UIImage(named: "profile"))
            }
        case .none:
            return self.image = nil
        }
    }
}

 

URL을 추가할 수 있도록 새로운 케이스 추가

enum ImageType {
    case system(String, pointSize: CGFloat)
    case user(userImageSource)
}

enum userImageSource {
    case image(UIImage)
    case url(URL)
}

 

setImageType 메서드 수정: SDWebImage 적용

private func updateImageAppearance() {

    switch imageType {
    case .system(let systemName, let pointSize):
        let config = UIImage.SymbolConfiguration(pointSize: pointSize)
        self.image = UIImage(systemName: systemName, withConfiguration: config)
        self.tintColor = .black
        self.contentMode = .center

    case .user(let source):
        switch source {
        case .image(let userImage):
            self.contentMode = .scaleAspectFill
            self.image = userImage
        case .url(let url):
            self.contentMode = .scaleAspectFill
            self.sd_setImage(with: url, placeholderImage: UIImage(named: "profile"))
        }
    case .none:
        return self.image = nil
    }

}

 

HomeController 클래스

override func viewDidLoad() {
    super.viewDidLoad()

    configureConstraints()
    self.profileImage.setImageType(.system("person", pointSize: 25))

    AutheService.shared.fetchUser { [weak self] user, error in
        guard let self = self else { return }

        if let error = error {
            AlertManager.showFetchingUserError(on: self, with: error)
            return
        }

        if let user = user {
            DispatchQueue.main.async {
                self.label.text = "Username : \(user.username) \n UserEmail : \(user.email)"
                if let profileImageURL = user.userImage,
                   let url = URL(string: profileImageURL) {
                    self.profileImage.setImageType(.user(.url(url)))
                } else {
                    self.profileImage.setImageType(.user(.image(UIImage(named: "profile")!)))
                }
            }

        }
    }
}

 

RegisterController 클래스

extension RegisterController: PHPickerViewControllerDelegate {
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        
        // PHPicker 닫기
        picker.dismiss(animated: true)
        
        // 첫 번째 선택된 아이템 가져오기
        let itemProvider = results.first?.itemProvider
        
        if let itemProvider = itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) {
            // UIImage 불러오기
            itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
                if let image = image as? UIImage { // 불러온 이미지 타입 확인
                    DispatchQueue.main.async {
                        // CustomImageView에 이미지 설정
                        self.profileImage = image
                        self.userPofileView.setImageType(.user(.image(image)))
                    }
                }
                else {
                    print("이미지가 올바르지 않습니다.")
                }
            }
        } else {
            print("이미지를 불러올 수 없습니다.")
        }
    }
}