Firebase를 이용하면서 이미지를 저장해야하는 경우에는 Storage에 저장을 시킨다. Storage에 저장을 시키는 이유는 다음과 같다고 한다.
스케일링: Firebase Storage는 Google Cloud Storage를 기반으로 하고 있으며, 이는 거의 무한한 스케일링이 가능하다. 이는 사용자의 앱이 성장함에 따라 많은 양의 이미지나 다른 파일을 저장하는 데 문제가 없음을 의미한다.
성능: 이미지를 Firebase Storage에 저장하면, 이미지가 전 세계에 걸쳐 분산된 서버에 저장된다. 사용자가 이미지를 요청할 때, 그들에게 가장 가까운 서버에서 이미지가 제공되어 더 빠른 로딩 시간과 더 나은 사용자 경험을 제공할 수 있다.
보안: Firebase Storage는 파일에 대한 세밀한 액세스 제어를 허용한다. 개발자는 보안 규칙을 설정하여 특정 사용자가 특정 파일에 액세스하거나 수정할 수 있는지 여부를 정확하게 제어할 수 있다.
관리 용이성: Firebase Console을 통해 업로드된 이미지와 파일을 쉽게 관리하고 검색할 수 있다. 이는 개발자가 파일을 프로그래매틱하게 관리할 필요 없이 직접 관리할 수 있음을 의미한다.
무결성 및 신뢰성: Google Cloud Storage의 기반이 되므로, Firebase Storage는 높은 수준의 무결성과 신뢰성을 제공한다. 데이터는 중복 저장되며, 여러 가지 재난 복구 시나리오에서도 안전하게 보호된다.
백업 및 복구: 데이터는 자동으로 백업되며, 실수로 삭제되거나 손상된 경우에도 복구할 수 있는 옵션이 있다.
CDN 통합: Firebase Storage는 Content Delivery Network (CDN)과 통합될 수 있어, 전 세계 어디에서나 콘텐츠를 빠르게 제공할 수 있다.
흐름은 로컬에서 추가 → 함수의 매개변수에 추가 → Storage에 저장 이다. 로컬에서 추가를 하기 위한 딕셔너리를 만들어주자. RealTime Database와 Storage가 딕셔너리 형태로 저장이 되기 때문에 딕셔너리 형태로 만들어주었다.
var selectedImage: [String: UIImage] = [:]
이제 이미지를 업로드 하는 함수를 만들자. Storage에 지정된 경로에 이미지를 업로드 해야하기 때문에 Storage의 참조를 설정해주어야 한다.
let storageRef = Storage.storage().reference().child("noticeBoards").child(clubID).child(noticeBoardID).child("images")
저장하려는 이미지를 JPEG 포맷으로 변환하고, putData
메서드를 사용하여 업로드 한다. PNG가 아닌 JPEG로 포맷을 결정한 이유는, 크기가 더 작기 때문이다.
let uploadData = image.jpegData(compressionQuality: 0.5)
이미지를 업로드하는 동안 다른 작업이 실행되지 않도록 DispatchGroup
을 사용하여 동기화 처리를 해준다.
let dispatchGroup = DispatchGroup()
모든 이미지 작업이 완료되면 dispatchGroup.notify
블록이 호출되고, 결과가 반환된다. 모든 이미지가 성공적으로 업로드되면 true
와 함께 업로드된 이미지의 경로 목록이 반환된다.
이미지의 경로는 ref.fullPath
를 사용하여 이미지의 전체 경로를 가져오고, 이를 imageURLs
배열에 저장한다.
var imageURLs: [String] = []
.
.
.
let fullPath = ref.fullPath
imageURLs.append(fullPath)
func uploadImages(clubID: String, noticeBoardID: String, imageList: [String: UIImage], completion: @escaping (Bool, [String]?) -> Void) {
let storageRef = Storage.storage().reference().child("noticeBoards").child(clubID).child(noticeBoardID).child("images")
var imageURLs: [String] = []
let dispatchGroup = DispatchGroup()
for (index, image) in imageList {
dispatchGroup.enter()
if let uploadData = image.jpegData(compressionQuality: 0.5) {
ref.putData(uploadData, metadata: nil) { _, error in
if let error = error {
print("Failed to upload image:", error)
} else {
// fullPath 속성을 사용하여 참조 경로를 저장
let fullPath = ref.fullPath
imageURLs.append(fullPath)
}
dispatchGroup.leave()
}
} else {
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
completion(imageURLs.count == imageList.count, imageURLs.sorted())
}
}
게시글을 처음 만들 때, 이미지도 같이 저장을 하기위해 createNoticeBoard()
에 넣었다.
func createNoticeBoard(title: String, content: String, clubID: String, completion: @escaping (Bool) -> Void) {
let ref = Database.database().reference().child("noticeBoards").child(clubID)
let newNoticeBoardID = ref.childByAutoId().key ?? ""
guard let currentUserID = MyProfile.shared.myUserInfo?.id else { return }
guard let currentUserNickName = MyProfile.shared.myUserInfo?.nickName else { return }
guard let currentUserProfileURL = MyProfile.shared.myUserInfo?.profileImageURL else { return }
let currentUser = UserSummary(id: currentUserID, profileImageURL: currentUserProfileURL, nickName: currentUserNickName)
self.uploadImages(clubID: clubID, noticeBoardID: newNoticeBoardID, imageList: self.selectedImage) { success, imageURLs in
if success {
let createDate = Date()
let newNoticeBoard = NoticeBoard(id: newNoticeBoardID, rootUser: currentUser, createDate: createDate, clubID: clubID, title: title, content: content, imageList: imageURLs ?? [], commentCount: "0")
self.saveNoticeBoard(noticeBoard: newNoticeBoard) { success in
if success {
self.noticeBoards.insert(newNoticeBoard, at: 0)
self.delegate?.reloadData()
}
completion(success)
}
} else {
completion(false)
}
}
}
갤러리에서 사진을 선택한 후, 앞에서 만들었던 딕셔너리에 추가를 해준다.
let index = firebaseManager.selectedImage.count
firebaseManager.selectedImage[String(index)] = image
그 다음, 게시글 작성 완료 버튼에서 앞에서 만들었던 createNoticeBoard()
를 불러주자.
firebaseManager.createNoticeBoard(title: newTitleText, content: newContentText, clubID: club.id) { success in
if success {
print("게시판 생성 성공")
}
else {
print("게시판 생성 실패")
}
}