ios swift -如何正确上传多个图像并等待返回值,然后保存在firebase db中

sxpgvts3  于 2023-06-07  发布在  iOS
关注(0)|答案(1)|浏览(417)

我目前正在开发一个应用程序,我被一个涉及完成处理程序和async/await的问题卡住了。
我正在尝试上传多个文件到fire storage,并在一个简单的自定义结构中返回文件名和url:

struct PicData: Codable, Equatable {
    let id: String
    let link: URL
}

我用这个功能上传一张图片。我将完成返回从“Void”改为我的结构体,但这两个都给我的下一个函数带来了问题。

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 {
                    //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)
        })
    }

在我的视图模型中,我试图在调用另一个函数之前等待数据:

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
            }
        }
    }

我花了很多时间试图搜索和弄清楚事情,我了解async/await和完成处理程序。我已经用不同的方法重写了我的函数多次,图像确实成功上传,但我的其他函数不会等待。我知道线程并没有等待响应,但是很难修复它。
其中一个主要的错误,我一直运行到其他比它不等待,是当我试图从一个函数的值-

// from function: uploadPictures
    Cannot convert value of type '()' to closure result type 'PicData'

(That问题虽然可能是更好的小康作为它自己的问题)
我也试过使用信号量,尽管我的理解非常有限--

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))
}

在我的模型中

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
            }
        }
    }

我的文件上传,但我不能让它等待,无论我已经尝试。我需要将文件上传到存储,然后将我的结构数据保存到Firestore DB。
我还尝试使用putDataAsync与代码我发现在另一个SO答案,但不断得到这个错误-
类型“StorageReference”的值没有成员“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
    }
}

在这一点上,我给予了,任何想法,我在这方面的错误?

uubf1zoe

uubf1zoe1#

感谢@loremipsum的评论,它引导我朝着正确的方向去解决它。
首先,我尝试通过File -> Packages -> Update..更新我的包,清理构建,重置包缓存,解析版本,关闭Xcode,重建等。但什么都没用我所要做的就是删除Firebase iOS SDK,然后添加一个全新的安装。
这样做之后,出现了很多原本不存在的错误,之前没有造成任何问题。例如强制解包和guard let语句,我还必须在一些firebase调用中添加try await。我现在可以使用putDataAsync
使用@loremipsum提供的链接,我能够修改我的代码并使其工作-

///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
    }

然后在我的视图模型中,我可以使用try await调用,这次它实际上是在等待。

相关问题