Swift开发中如何从Firebase Storage正确返回downloadURL
SwiftUI 上传图片到Firebase Storage无法返回下载链接解决方案
核心问题
Firebase Storage的putData上传、downloadURL获取接口均为异步闭包回调逻辑,函数执行到return语句时异步网络任务尚未完成,此时返回的是初始空值,自然无法拿到正确的下载链接。
原有问题代码
上传逻辑代码
if let data = data { mainRef.putData(data, metadata: metadata) { (metadata, error) in if let error = error { print("Error while uploading file: ", error) } if let metadata = metadata { let pathRef = self.storage.reference(withPath: "\(dir)/\(metadata.name ?? "Image not found")") pathRef.downloadURL { url, error in if let error = error { print(error.localizedDescription) return } else { // 获取到的下载链接 let urlStr:String = (url?.absoluteString) ?? "" print(urlStr) // 需要将该值回传给调用方 } } } } } return(urlReturn)
原有调用代码
currentSite.imageMain = storageManager.upload(image: image, siteRef: newSiteRef.documentID, size: 1000, dir: "ImagesMain/")
修复方案
异步逻辑无法通过同步return直接返回值,根据项目支持的系统版本二选一即可:
方案1:闭包回调(兼容所有iOS版本)
给上传方法新增逃逸闭包作为完成回调,异步任务拿到结果后通过闭包向外传递:
// 修改upload方法定义,新增completion回调 func upload(image: UIImage, siteRef: String, size: CGFloat, dir: String, completion: @escaping (Result<String, Error>) -> Void) { // 保留原有图片压缩、metadata初始化逻辑 guard let data = image.jpegData(compressionQuality: 0.8) else { completion(.failure(NSError(domain: "UploadError", code: -1, userInfo: [NSLocalizedDescriptionKey: "图片转换失败"]))) return } let metadata = StorageMetadata() metadata.contentType = "image/jpeg" let mainRef = storage.reference().child("\(dir)\(siteRef).jpg") mainRef.putData(data, metadata: metadata) { (metadata, error) in // 上传失败直接返回错误 if let error = error { completion(.failure(error)) return } guard let _ = metadata else { completion(.failure(NSError(domain: "UploadError", code: -2, userInfo: [NSLocalizedDescriptionKey: "上传元数据为空"]))) return } let pathRef = self.storage.reference(withPath: "\(dir)/\(metadata.name ?? "Image not found")") pathRef.downloadURL { url, error in if let error = error { completion(.failure(error)) return } guard let urlStr = url?.absoluteString else { completion(.failure(NSError(domain: "UploadError", code: -3, userInfo: [NSLocalizedDescriptionKey: "下载链接为空"]))) return } // 拿到有效链接后通过回调返回 completion(.success(urlStr)) } } }
调用时不要直接同步赋值,在回调内拿到结果后再给属性赋值,UI操作必须回到主线程:
storageManager.upload(image: image, siteRef: newSiteRef.documentID, size: 1000, dir: "ImagesMain/") { [weak self] result in switch result { case .success(let urlStr): DispatchQueue.main.async { self?.currentSite.imageMain = urlStr } case .failure(let error): print("上传失败: \(error.localizedDescription)") } }
方案2:async/await 语法(推荐,支持iOS 15及以上)
如果项目最低支持版本为iOS15,直接使用Firebase提供的异步接口,代码更简洁易读:
// 修改upload方法为async throws类型 func upload(image: UIImage, siteRef: String, size: CGFloat, dir: String) async throws -> String { guard let data = image.jpegData(compressionQuality: 0.8) else { throw NSError(domain: "UploadError", code: -1, userInfo: [NSLocalizedDescriptionKey: "图片转换失败"]) } let metadata = StorageMetadata() metadata.contentType = "image/jpeg" let mainRef = storage.reference().child("\(dir)\(siteRef).jpg") // 等待上传完成 let _ = try await mainRef.putDataAsync(data, metadata: metadata) // 等待获取下载链接 let downloadURL = try await mainRef.downloadURL() return downloadURL.absoluteString }
调用时用Task包裹异步逻辑,主线程更新UI:
Task { do { let urlStr = try await storageManager.upload(image: image, siteRef: newSiteRef.documentID, size: 1000, dir: "ImagesMain/") await MainActor.run { currentSite.imageMain = urlStr } } catch { print("上传失败: \(error.localizedDescription)") } }
注意:不要在异步回调里直接做强制解包,避免出现值为空时的崩溃问题。
内容的提问来源于stack exchange,提问作者user1465078
相关产品推荐
相关产品推荐

