【问题标题】:Is this a good way to display asynchronous data?这是显示异步数据的好方法吗?
【发布时间】:2017-06-05 23:17:53
【问题描述】:

我是异步编码的新手,我想知道我用来获取和显示数据的方法在 swift 中是否被认为是正确的。

此方法从数据库中的用户部分获取对象列表,然后为列表中的每个项目获取图片。我告诉是否所有图像都已获取的方式是在它们到达时将它们推送到一个数组,然后如果该数组的长度等于我重新加载视图的对象列表。这是我的代码:

var pets = [String]()
var imgs = [UIImage]()

override func viewDidAppear(_ animated: Bool) {
        imgsLoaded = 0
        imgs.removeAll(keepingCapacity: false)  // Clear images array
        pets.removeAll(keepingCapacity: false)  // Clear list of objects

        let userRef = FIRDatabase.database().reference().child("users").child((FIRAuth.auth()?.currentUser?.uid)!).child("pets")

        userRef.observeSingleEvent(of: .value, with: { snapshot in  // fetch list of objects from database
            if (snapshot.value as? Bool) != nil {                   // User has no pets added
                self.loadScrollView()                               // Reload view
            } else if let snap = snapshot.value as? NSDictionary {  // User has pets
                for value in snap {
                    self.pets.append(value.key as! String)          // Append object to list of objects
                }
                for i in 0..<self.pets.count {                      // For each item in the list, fetch its corresponding image
                    let imgRef = FIRStorage.storage().reference().child("profile_images").child(self.pets[i]+".png")
                    imgRef.data(withMaxSize: 15 * 1024 * 1024) { (data, error) -> Void in
                        if error != nil {
                            print(error)
                        }

                        // Create a UIImage, add it to the array
                        self.imgs.append(UIImage(data: data!)!)      // Push image to list of images
                        self.imgsLoaded+=1
                        if self.imgsLoaded == self.pets.count {      // If same number of images loaded, reload the view
                            self.loadScrollView()
                        }
                    }
                }
            }
        })
    }

正如我所说,我是异步编码的新手,想听听我正在尝试的正确方法。我的方法的一个问题是图像数组可能与数据数组不对齐,因为可以乱序获取图像。我很想学习最好的方法,所以请告诉我!

编辑:确保我的数据与相应图像对齐的一种方法是设置一个排序字典,其中键是数据,值是我猜的图像。

【问题讨论】:

  • 如果您的图像和数据没有存储在同一条记录中,那么可以肯定的是,它们的顺序会被破坏。

标签: swift asynchronous firebase firebase-realtime-database


【解决方案1】:

我建议使用DispatchGroup 而不是计算 2 个数组中的项目数。这也将允许跟踪多个线程的进度,每个块在组上注册enter()leave()。然后,一旦所有块进入和离开,notify 块就会被调用,它可以刷新你的 UI。还可以使用带有占位符图像的字典,以防其中一个图像的加载失败。总比忽略失败案例要好。

这也可能更容易阅读和推理,因为没有跟踪 2 个数组的计数。 enter()leave() 的意图更明确

var imgs = [String: UIImage]()
var dispatchGroup = DispatchGroup()

 override func viewDidAppear(_ animated: Bool) {

    let userRef = FIRDatabase.database().reference().child("users").child((FIRAuth.auth()?.currentUser?.uid)!).child("pets")

    userRef.observeSingleEvent(of: .value, with: { [weak self] snapshot in  // fetch list of objects from database
        if (snapshot.value as? Bool) != nil {                   // User has no pets added
            self?.loadScrollView()                               // Reload view
        } else if let snap = snapshot.value as? NSDictionary {  // User has pets
            for value in snap {
                self?.dispatchGroup.enter()
                let imgRef = FIRStorage.storage().reference().child("profile_images").child(self!.pets[i]+".png")
                imgRef.data(withMaxSize: 15 * 1024 * 1024) { (data, error) -> Void in
                    if let error = error {
                        print(error) // handle the error here but still call .leave() on the group to be sure the notify block is called.
                        imgs[value] = somePlaceHolderImage
                        self?.dispatchGroup.leave()
                    } else if let data = data, let image = UIImage(data: data) {
                        imgs[value] = image
                        self?.dispatchGroup.leave()
                    }

                    dispatchGroup.notify(queue: DispatchQueue.main, execute: {
                        self?.loadScrollView()
                    })
                }
            }
        }
    })
}

【讨论】:

  • 我一直在阅读 GCD 并尝试您的解决方案,但我仍然有点困惑,也许您可​​以为我澄清一下。我不得不在进入和离开调度组前面添加self.,因为这些数据库请求是异步的。因为它们是异步的,所以我想知道将事物放入调度组有什么用处,如果无论如何都将在获取请求后调用它们的回调。你能帮我理解为什么吗?谢谢
  • 调度组考虑到回调在不同线程上的可能性,通过使用enter()leave() 方法,您可以确保直到所有都被平衡(即所有对enter() 的调用必须有对leave() 的相应调用)通知块不会被过早调用。在您的情况下,您可能有许多并行发生的同时调用,这些调用可能在不同时间完成,并且您只想在全部完成后loadScrollView()
  • 我在没有编译的情况下写了答案,但自我会警告可能在块内自我保留问题。为了防止这种情况,您应该将 [weak self] 添加到块中,以防止回调意外保留 self。我将更新上面的答案以反映这一点。
  • 关于 [weak self] 有很多详尽的解释,但基本上是通过在释放 self 时将 self 捕获为闭包中的弱本地变量(例如在进程之前离开视图控制器已完成)在回调和自身之间不会产生保留周期,从而导致内存泄漏。我正在略过这个想法,但通常如上所述使用弱自我有助于防止这个问题。
  • 知道了。还有一点,dispatchGroup.notify怎么知道所有的任务都完成了?是不是因为它在一个 for 循环中,它知道应该有多少 enters 和离开?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-17
  • 1970-01-01
  • 2014-12-17
  • 1970-01-01
  • 1970-01-01
  • 2013-11-06
  • 1970-01-01
相关资源
最近更新 更多