forEach 循环跳过最后一项

forEach Loop skips the last item

这个 forEach 循环有时有效,有时会跳过。我不确定我在这里做错了什么。循环将跳过最后一项并且永远不会退出。所以完成块根本不会被触发。

我正在使用 firebase、Eureka 表单及其 ImageRow 扩展。

在此提供一些帮助,我将不胜感激。

//MARK: - Get Form Values
var returnedValues: [String: Any] = [:]
fileprivate func getFormValues(values: [String: Any], completion: @escaping ([String:Any])->()) {

    if let name = values["name"] as? String,
        let description = values["description"] as? String,
        let images = values["images"] as? [UIImage],
        let category = values["category"] as? String,
        let price = values["price"] as? Double,
        let deliveryFee = values["deliveryFee"] as? Double,
        let deliveryAreas = values["deliveryArea"] as? Set<String>,
        let deliveryTime = values["deliveryTime"] as? String {

        guard let uid = Auth.auth().currentUser?.uid else { return }
        var imagesData = [[String: Any]]()
        var counter = 0

        images.forEach({ (image) in

            let imageName = NSUUID().uuidString
            let productImageStorageRef = Storage.storage().reference().child("product_images").child(uid).child("\(imageName).jpg")
            var resizedImage = UIImage()

            if image.size.width > 800 {
                resizedImage = image.resizeWithWidth(width: 800)!
            }

            if let uploadData = UIImageJPEGRepresentation(resizedImage, 0.5) {
                productImageStorageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
                    if error != nil {
                        print("Failed to upload image: \(error?.localizedDescription ?? "")")
                        return
                    }

                    //Successfully uploaded product Image
                    print("Successfully uploaded product Image")
                    if let productImageUrl = metadata?.downloadURL()?.absoluteString {
                        counter += 1
                        let imageData: [String: Any] = [imageName: productImageUrl]
                        imagesData.append(imageData)

                        if counter == images.count {
                            let deliveryAreasArr = Array(deliveryAreas)
                            self.returnedValues = ["name": name, "description": description, "images": imagesData , "category": category, "price": price, "deliveryFee": deliveryFee, "deliveryArea": deliveryAreasArr, "deliveryTime": deliveryTime, "creationDate": Date().timeIntervalSince1970, "userId": uid]
                            completion(self.returnedValues)
                        }

                    }

                })
            }
        })

    } else {

        let alert = UIAlertController(title: "Missing Information", message: "All fields are required. Please fill all fields.", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
            alert.dismiss(animated: true, completion: nil)
        }))
        UIActivityIndicatorView.stopActivityIndicator(indicator: self.activityIndicator, container: self.activityIndicatorContainer, loadingView: self.activityIndicatorLoadingView)
        self.present(alert, animated: true, completion: nil)
    }
}

for 循环中有许多 if 语句可能导致 counter 不递增。如果其中任何一个失败,那么您将永远不会调用完成处理程序。

我了解到您正在使用 counter 来尝试了解所有异步任务何时完成,但是调度组是更好的解决方案。

在所有路径中调用完成处理程序也很重要;例如当初始 guard 失败时或在初始 ifelse 子句中 - 您的完成处理程序可能应该接受一个 Error 参数,以便它知道有一个问题。

//MARK: - Get Form Values

fileprivate func getFormValues(values: [String: Any], completion: @escaping ([String:Any]?)->()) {
    var returnedValues: [String: Any] = [:]

    if let name = values["name"] as? String,
        let description = values["description"] as? String,
        let images = values["images"] as? [UIImage],
        let category = values["category"] as? String,
        let price = values["price"] as? Double,
        let deliveryFee = values["deliveryFee"] as? Double,
        let deliveryAreas = values["deliveryArea"] as? Set<String>,
        let deliveryTime = values["deliveryTime"] as? String {

        guard let uid = Auth.auth().currentUser?.uid else {
            completion(nil) 
            return 
        }
        var imagesData = [[String: Any]]()

        let dispatchGroup = DispatchGroup() // Create a Dispatch Group

        images.forEach({ (image) in

            let imageName = NSUUID().uuidString
            let productImageStorageRef = Storage.storage().reference().child("product_images").child(uid).child("\(imageName).jpg")
            var resizedImage = UIImage()

            if image.size.width > 800 {
                resizedImage = image.resizeWithWidth(width: 800)!
            }

            if let uploadData = UIImageJPEGRepresentation(resizedImage, 0.5) {

                dispatchGroup.enter()  // Enter the group

                productImageStorageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
                   guard error == nil else {
                        print("Failed to upload image: \(error?.localizedDescription ?? "")")
                        dispatchGroup.leave()  // Leave the dispatch group if there was an error
                        return
                    }

                    //Successfully uploaded product Image
                    print("Successfully uploaded product Image")
                    if let productImageUrl = metadata?.downloadURL()?.absoluteString {
                        let imageData: [String: Any] = [imageName: productImageUrl]
                        imagesData.append(imageData)
                    }
                    dispatchGroup.leave() // Leave the dispatch group in normal circumstances
                })
            }
        })

        // Schedule a notify closure for execution when the dispatch group is empty

        dispatchGroup.notify(queue: .main) {
            let deliveryAreasArr = Array(deliveryAreas)
            returnedValues = ["name": name, "description": description, "images": imagesData , "category": category, "price": price, "deliveryFee": deliveryFee, "deliveryArea": deliveryAreasArr, "deliveryTime": deliveryTime, "creationDate": Date().timeIntervalSince1970, "userId": uid]
            completion(self.returnedValues)
        }

    } else {
        completion(nil)
        let alert = UIAlertController(title: "Missing Information", message: "All fields are required. Please fill all fields.", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
            alert.dismiss(animated: true, completion: nil)
        }))
        UIActivityIndicatorView.stopActivityIndicator(indicator: self.activityIndicator, container: self.activityIndicatorContainer, loadingView: self.activityIndicatorLoadingView)
        self.present(alert, animated: true, completion: nil)
    }
}

其他几点:

  • 最好传递结构而不是字典。使用 struct 作为您的输入将摆脱函数开头的大量 if let,因为您会知道值的类型,并通过使它们成为结构的非可选属性,您会知道这些值存在。
  • 像这样的功能发出警报是不寻常的;它通常只是 return 通过完成或可能 throw 返回给调用者以表明存在问题并让调用者处理它的错误
  • 我不明白为什么 imagesData 需要是字典数组。数组中的每个字典只有一个条目,因此您可以只使用 [String:String] 的字典(当您知道类型是什么时,无需使用 Any