프로필 이미지의 사용 방식에 따라 다운로드 후 직접 처리와 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("이미지를 불러올 수 없습니다.")
}
}
}
'Project > FirebaseTest' 카테고리의 다른 글
FireBase - 회원 탈퇴 (Firestorage 폴더 삭제) (0) | 2024.12.05 |
---|---|
FireBase - 회원 정보 불러오기 (0) | 2024.12.05 |
FireBase - 회원가입할 때 프로필 이미지 포함 (1) | 2024.12.05 |
FireBase - 사용자 정보 불러오기 및 사용자 삭제 (0) | 2024.12.04 |
FireBase - 유효성 검사 + 사용자 등록 (0) | 2024.12.03 |