英文:
swift - how to properly upload multiple images and await return values to then save in firebase db
问题
I understand that you want me to translate the code portion you provided. Here it is:
struct PicData: Codable, Equatable {
let id: String
let link: URL
}
private func uploadPicture(picture: UIImage, completion: @escaping (PicData) -> PicData) async throws {
guard let data = picture.jpegData(compressionQuality: 1.0) else {
throw StorageError(message: "No data found in picture")
}
let fileName = UUID().uuidString + ".jpg"
let picRef = storage.child("photos").child(someId).child(fileName)
picRef.putData(data, metadata: nil) { (metadata, error) in
if let error = error {
print("Error while uploading file: ", error)
return
}
picRef.downloadURL(completion: { (url, error) in
if let theUrl = url {
completion(PicData(id:fileName, link:theUrl))
}
})
}
}
func uploadPictures(_ pics: [UIImage], completion: @escaping ([PicData])->()) async throws {
try await withThrowingTaskGroup(of: PicData.self, body: { group in
for pic in pics {
group.addTask {
try await self.uploadPicture(picture: pic, completion: { (files) in
return files
})
}
}
var fileNames: [PicData] = []
for try await fileName in group{
fileNames.append(PicData(id: fileName.id, link: fileName.link))
}
completion(fileNames)
})
}
func createNew(data: data, controller: UIViewController) async {
self.isLoading = true
Task{
do {
try await storageRepository.uploadPictures(data.pictures, completion: { pics in
Task {
do {
try await self.firestoreRepository.create(data: data, pictures: pics)
DispatchQueue.main.async {
self.isLoading = false
}
}
}
})
} catch {
return
}
}
}
Please note that I've translated the code while maintaining the same structure and logic. If you have any specific questions or need further assistance with this code, please let me know.
英文:
I am currently working on an app and I have been stuck on a problem involving completion handlers and async/await.
I am trying to upload multiple files to fire storage and return the file name and url in a simple custom struct:
struct PicData: Codable, Equatable {
let id: String
let link: URL
}
I use this function to upload a single image. I changed the completion return from "Void" to my struct, but both give me problems in my next function.
private func uploadPicture(picture: UIImage, completion: @escaping (PicData) -> PicData) async throws {
guard let data = picture.jpegData(compressionQuality: 1.0) else {
throw StorageError(message: "No data found in picture")
}
let fileName = UUID().uuidString + ".jpg"
let picRef = storage.child("photos").child(someId).child(fileName)
picRef.putData(data, metadata: nil) { (metadata, error) in
if let error = error {
print("Error while uploading file: ", error)
return
}
picRef.downloadURL(completion: { (url, error) in
if let theUrl = url {
completion(PicData(id:fileName, link:theUrl))
}
})
}
}
I use the following function to upload multiple images. It's the main upload function Im calling from my View model -
func uploadPictures(_ pics: [UIImage], completion: @escaping ([PicData])->()) async throws {
try await withThrowingTaskGroup(of: PicData.self, body: { group in
for (pic) in pics {
group.addTask {
//let combo =
try await self.uploadPicture(picture: pic, completion: { (files) in
return files
})
//return combo
}
}
var fileNames: [PicData] = []
for try await fileName in group{
fileNames.append(PicData(id: fileName.id, link: fileName.link))
}
completion(fileNames)
})
}
And in my view model, I am trying to wait for the data before calling another function:
func createNew(data: data, controller: UIViewController) async {
self.isLoading = true
Task{
do {
try await storageRepository.uploadPictures(data.pictures, completion: { pics in
Task {
do {
try await self.firestoreRepository.create(data: data, pictures: pics)
DispatchQueue.main.async {
self.isLoading = false // when this is published next view is shown.
}
}
}
})
} catch {
return
}
}
}
I've spent a lot of hours trying to search and figure things out, and I understand about async/await and completion handlers. I've rewritten my functions multiple times in different ways and the images are indeed successfully uploaded but my other functions don't wait. I understand the thread isn't waiting for a response, but having a hard time fixing it.
One of the main errors I keep running into other than it not waiting, is when I am trying to get the value from a function -
// from function: uploadPictures
Cannot convert value of type '()' to closure result type 'PicData'
(That issue though is probably better off as it's own question)
I've also tried using Semaphores although my understanding is very limited --
actor Datas {
var combos: [PicData] = []
func append(combo: PicData) {
combos.append(combo)
}
}
func runSema(pics: [UIImage]) async -> [PicData] {
let combo = Datas()
let queue = DispatchQueue(label: "com.app.myQueue", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: pics.count)
for pic in pics {
queue.async {
semaphore.wait()
Task {
do {
let com = try await self.uploadPicture(picture: pic)
print(com)
await combo.append(combo: com)
} catch {
}
}
semaphore.signal()
}
}
return await combo.combos
}
private func uploadPicture(picture: UIImage) async throws -> PicData {
let semaphore = DispatchSemaphore(value: 1)
guard let data = picture.jpegData(compressionQuality: 1.0) else {
throw StorageError(message: "No data found in picture")
}
let fileName = UUID().uuidString + ".jpg"
let picRef = storage.child("users").child(userId!).child(fileName)
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
DispatchQueue.global().async {
semaphore.wait()
picRef.putData(data, metadata: metadata) { (metadata, error) in
if let error = error {
print("Error while uploading file: ", error)
// return
}
picRef.downloadURL { (url, error) in
guard let theUrls = url else { return }
semaphore.signal()
}
}
}
let theUrl = try await picRef.downloadURL() // trying to get url outside of closure
return(PicData(id: fileName, link: theUrl))
}
and in my view model -
func createNew(data: data, controller: UIViewController) {
self.isLoading = true
Task{
do {
let combos = await storageRepository.runSema(pics: profileData.pictures)
try await self.firestoreRepository.create(data: data, pictures: combos)
DispatchQueue.main.async {
self.isLoading = false // when this is published next view is shown.
}
} catch {
publishError(message: error.localizedDescription)
return
}
}
}
My files are uploaded but I can not get it to wait regardless what I've tried. I need to upload the files to storage then after, save my struct's data to Firestore DB.
I also tried to use putDataAsync with code I found on another SO answer, but kept getting this error -
> Value of type 'StorageReference' has no member 'putDataAsync'
func someUpload(someImagesData: [Data]) async throws -> [String] {
// your path
let storageRef = storage.child("users").child(userId!).child("fileName")
return try await withThrowingTaskGroup(of: String.self) { group in
// the urlStrings you are eventually returning
var urlStrings = [String]()
// just using index for testing purposes so each image doesnt rewrites itself
// of course you can also use a hash or a custom id instead of the index
// looping over each image data and the index
for (index, data) in someImagesData.enumerated() {
// adding the method of storing our image and retriving the urlString to the task group
group.addTask(priority: .background) {
let _ = try await storageRef.putDataAsync(data)
return try await storageRef.downloadURL().absoluteString
}
}
for try await uploadedPhotoString in group {
// for each task in the group, add the newly uploaded urlSting to our array...
urlStrings.append(uploadedPhotoString)
}
// ... and lastly returning it
return urlStrings
}
}
At this point I give up, any ideas where I'm going wrong on this?
答案1
得分: 0
感谢@loremipsum的评论,它引导我走上了正确的方向来解决问题。
首先,我尝试通过File -> Packages -> Update..
来更新我的包并进行清理构建,重置包缓存,解决版本冲突,关闭Xcode,重新构建等等,但没有什么效果。我不得不删除Firebase iOS SDK,然后重新安装它。
在这样做之后,出现了很多原本不存在且之前没有引起任何问题的错误,比如强制解包和guard let
语句,我还不得不在一些Firebase调用中添加try await
。现在我能够使用putDataAsync
了。
使用@loremipsum提供的链接,我能够修改我的代码并使其正常工作 -
///将数据上传到`FirebaseStorage`中的指定路径
func upload(picture: UIImage) async throws -> PicData {
let fileName = UUID().uuidString + ".jpg"
let imageRef = storage.child("someRef")
guard let data = picture.jpegData(compressionQuality: 1.0) else {
throw StorageError(message: "No data found in picture")
}
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
let meta = try await imageRef.putDataAsync(data, metadata: metadata)
let url = try await imageRef.downloadURL()
return PicData(id: fileName, link: url)
}
///以JPEG格式上传多张图片并返回图片的URL
///并行运行上传任务
func uploadAsJPEG(pics: [UIImage]) async throws -> [PicData]{
return try await withThrowingTaskGroup(of: PicData, body: { group in
for pic in pics {
group.addTask {
return try await self.upload(picture: pic)
}
}
var urls: [PicData] = []
for try await url in group{
urls.append(url)
}
return urls
})
}
func uploadAsJPEG1x1(images: [UIImage]) async throws -> [PicData]{
var urls: [PicData] = []
for image in images {
let url = try await upload(picture: image)
urls.append(url)
}
return urls
}
然后在我的视图模型中,我可以使用try await
调用它,这次它实际上等待了。
英文:
Thanks to @loremipsum's comments, it lead me in the right direction to get it figured out.
First I tried updating my packages via File -> Packages -> Update..
and clean building, reset package cache, resolve versions, close Xcode, rebuild, etc. and nothing worked. What I had to do was remove the Firebase iOS SDK and then add a fresh install.
After doing so, a lot of errors popped up that originally wasn't there and didn't cause any problems before. Such as force unwrapping, and guard let statements, and I also had to add try await to some firebase calls. I was now able to use putDataAsync
.
Using the link provide by @loremipsum I was able to modify my code and get it to work -
///Uploads Data to the designated path in `FirebaseStorage`
func upload(picture: UIImage) async throws -> PicData {
let fileName = UUID().uuidString + ".jpg"
let imageRef = storage.child("someRef")
guard let data = picture.jpegData(compressionQuality: 1.0) else {
throw StorageError(message: "No data found in picture")
}
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
let meta = try await imageRef.putDataAsync(data, metadata: metadata)
let url = try await imageRef.downloadURL()
return PicData(id: fileName, link: url)
}
///Uploads a multiple images as JPEGs and returns the URLs for the images
///Runs the uploads simultaneously
func uploadAsJPEG(pics: [UIImage]) async throws -> [PicData]{
return try await withThrowingTaskGroup(of: PicData, body: { group in
for pic in pics {
group.addTask {
return try await self.upload(picture: pic)
}
}
var urls: [PicData] = []
for try await url in group{
urls.append(url)
}
return urls
})
}
func uploadAsJPEG1x1(images: [UIImage]) async throws -> [PicData]{
var urls: [PicData] = []
for image in images {
let url = try await upload(picture: image)
urls.append(url)
}
return urls
}
And then in my view model I was able to call using try await and it actually waited this time.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论