【问题标题】:Images not going straight into array图像不直接进入数组
【发布时间】:2020-07-09 16:53:06
【问题描述】:

我对这里的一些代码有一个小问题。我正在尝试使用五个名称、描述和图像填充集合视图。

我能够成功地将以上所有内容下载到他们尊重的数组中。

问题是我第一次执行 segue 时,图像数组中的值为零。然后我返回一个页面,重新进入该页面,发现所有的数组都填充成功了....

这真的很烦人。这是我的代码:

//arrays of names, descriptions and images
    var names:[String] = []
    var descriptions: [String] = []
    var imagesArray: [UIImage] = []

这是我获取图像的地方:

func downloadImages(){
        for x in 1...5{
            
            let url = URL(string: "https://www.imagesLocation.com/(x).png")
            
            let task = URLSession.shared.dataTask(with: url!){(data, response, error) in
                
                guard
                    let data = data,
                    let newImage = UIImage(data: data)
                    else{
                        print("Could not load image from URL: ",url!)
                        return
                }
                
                DispatchQueue.main.async {
                    self.imagesArray.append(newImage)
                }
            }
            
            task.resume()
        }
        
        loadDataFromFirebase()
    }
    

我从这里下载名称和描述:

 func loadDataFromFirebase() {
        // Fetch and convert data
        let db = Firestore.firestore()
        db.collection(self.shopName).getDocuments { (snapshot, err) in
            if let err = err {
                print("Error getting documents: \(err)")
                return
            } else {
                for document in snapshot!.documents {
                    let name = document.get("Name") as! String
                    let description = document.get("Description") as! String
                    self.names.append(name)
                    self.descriptions.append(description)
                }
                self.setupImages() //safe to do this here as the firebase data is valid
            }
        }
    }

在这里我使用名称、描述和图像数组内容设置集合视图:

func setupImages(){
    
    do {
        if imagesArray.count < 5 || names.count < 5  || descriptions.count < 5 {
            throw MyError.FoundNil("Something hasnt loaded")
        }
        
        self.pages = [
            Page(imageName: imagesArray[0], headerText: names[0], bodyText: descriptions[0]),
            
            Page(imageName: imagesArray[1], headerText: names[1], bodyText: descriptions[1]),
            
            Page(imageName: imagesArray[2], headerText: names[2], bodyText: descriptions[2]),
            
            Page(imageName: imagesArray[3], headerText: names[3], bodyText: descriptions[3]),
            
            Page(imageName: imagesArray[4], headerText: names[4], bodyText: descriptions[4]),
        ]
    }
    catch {
        print("Unexpected error: \(error).")
    }
}

从下图中可以看出,除了 images 数组之外,每个数组都已成功填充:

这是上一页代码的转场:

DispatchQueue.main.async(){
            self.performSegue(withIdentifier: "goToNext", sender: self)   
        }

欢迎任何帮助:)

【问题讨论】:

  • 你在哪里调用这个函数?
  • 我从 viewWillAppear 调用“downloadImages”函数
  • print("Could not load image from URL: ",url!) 在调试中检查这一行
  • 请先阅读异步执行。
  • 找到零个匹配项。我认为他们下载成功,但添加到数组的速度不够快

标签: ios swift


【解决方案1】:

您的问题只是经典的变体,“为什么我的异步函数返回空数据?”我已经回答了其中的几个问题,我将用一个类比来解释这个问题。你可能已经理解了这个问题,但我还是会为未来的读者提供它:

你妈妈正在做晚饭,请你去买柠檬。

她开始做饭,但她没有柠檬!

为什么?因为你还没有从超市回来,而且你的 妈妈没有等。

Source


这里的主要问题是你打电话给loadDataFromFirebase 太早了。您假设它只会在您的 URL 请求完成后执行,但事实并非如此。为什么?因为 URL 请求是异步执行的。也就是说,它们在另一个线程上运行,而不是阻塞调用dataTask.resume 的线程。这就是为什么,正如 Shashank Mishra 所建议的,您应该使用 DispatchGroup。此外,无法保证您的图像将按照您开始数据任务的顺序加载。我在下面包含了一个修复程序。

一般来说,我建议在您需要它们的范围内严格定义变量。将namesdescriptionsimages 保持在如此高的范围内,很容易出错。我建议重构你的函数并删除这三个类级数组。而是:

func loadDataFromFirebase(images: [UIImage]) {
    // same function as you posted, except make names and descriptions local variables and
    // replace self.setupImages() with:
    DispatchQueue.main.async {
        self.setupImages(images: images, names: names, descriptions: descriptions)
    }
}

func setupImages(images: [UIImage], names: [String], descriptions: [String]) {
    guard images.count == 5, names.count == 5, descriptions.count == 5 else {
        print("Missing data.")
        return
    }

    self.pages = (0..<5).map({ Page(image: images[$0], header: names[$0], body: descriptions[$0]) })
    // super important!!!
    tableView.reloadData()
}

最后,这是我对线程安全downloadImages 函数的建议:

func downloadImages() {
    var images = [UIImage?](repeating: nil, count: 5)
    let dispatchGroup = DispatchGroup()
    
    for i in 1...5 {
        dispatchGroup.enter()
        
        let url = URL(string: "https://www.imagesLocation.com/\(i).png")!
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            guard let data = data, let image = UIImage(data: data) else {
                print("Could not load image from", url)
                dispatchGroup.leave()
                return
            }
            
            images[i] = image
            dispatchGroup.leave()
        }.resume()
    }
    
    dispatchGroup.notify(queue: .main) {
        guard images.allSatisfy({$0 != nil}) else {
            print("Failed to fetch all images.")
            return
        }
        self.loadDataFromFirebase(images: images.compactMap({$0}))
    }
}

正如 Fattie 所指出的,您应该使用 addSnapshotListener 而不是 getDocuments。此外,您应该在下载图像而不是之后添加 listener/get 文档,这样会更快。但是,我不会在我的答案中添加任何一个,因为这已经很长了,如果您遇到问题,可以发布另一个问题。

【讨论】:

  • 谢谢丹尼尔!我会试试这个并回复你!
【解决方案2】:

可以使用DispatchGroup实现异步调用-

func downloadImages() {

   let dispatchGroup = DispatchGroup()

    for x in 1...5 {
         
         dispatchGroup.enter()
        
         let url = URL(string: "https://www.imagesLocation.com/(x).png")
         let task = URLSession.shared.dataTask(with: url!){(data, response, error) in
             guard
                 let data = data,
                 let newImage = UIImage(data: data)
                 else{
                     print("Could not load image from URL: ",url!)
                     dispatchGroup.leave()
                     return
             }
             self.imagesArray.append(newImage)
             dispatchGroup.leave()
         }
         task.resume()
     }
     dispatchGroup.notify(queue: DispatchQueue.main) {
        self.loadDataFromFirebase()
     }
 }

调用“loadDataFromFirebase()”方法以获取上述所有 5 个响应。在将其加载到视图之前,它将始终具有所有图像。

【讨论】:

  • 感谢您的回复!不幸的是,这不起作用:(我第一次转到页面时它不起作用但是当我放松并返回同一页面时,数组会正确填充:(
  • 您应该在“dispatchGroup.notify()”之后对页面进行segue,否则您将无法在下一页获取图像,因为图像是异步下载的。你能把segue代码也给我看一下吗?
  • 这个答案是完全错误的。 OP 误解了 Firebase 的工作原理。
  • @Fattie downloadImages() 正在异步下载图像。 loadDataFromFirebase() 在 for 循环之后被调用(无论图像下载状态如何),并且 loadDataFromFirebase() 正在使用该图像数组。所以 DispatchQueue 的使用是正确的。
  • @ShashankMishra ,您对 Dispatch 的理解是正确的。但是(1)在我们离那一英里之前,Firebase 的方法是完全错误的 :) (2) 在使用 Firebase 时,您无论如何都不要使用 Dispatch。
【解决方案3】:

您误解了 Firebase 的工作原理。

基本上。

  1. 不要使用getDocuments。使用.addSnapshotListener

  1. 基本上每次快照到达时,只需在桌面上调用.reloadData()即可。

完整的教程超出了此处的答案范围,但是周围有很多很多教程。

只是一个典型的片段......

    let db = Firestore.firestore().db.collection("yourCollection")
        .whereField("user", isEqualTo: uid)
        .addSnapshotListener { [weak self] documentSnapshot, error in
            guard let self = self else { return }
            
            guard let ds = documentSnapshot else {
                return print("error: \(error!)")
            }
            
            self.displayItems =  .. that data
            self.tableView.reloadData()
    }

注意.reloadData()

还有..

确实可以在 Firestore 中存储图像(二进制数据)。

但真的,永远不要那样做——完全没用。

只需使用简单易用的 Firebase/Storage 系统,您就可以免费托管图像。然后他们有完全正常的 URL 等等。

完整教程:https://stackoverflow.com/a/62626214/294884

【讨论】:

  • 感谢您的回复,Firebase 不是这里的问题,它的 downloadImages 函数只会在页面第二次打开时填充图像数组
猜你喜欢
  • 2020-12-01
  • 2011-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-23
  • 1970-01-01
相关资源
最近更新 更多