如何再次等待 Swift 的 URLSession 在 运行 之前完成?

How to wait for Swift's URLSession to finish before running again?

可能是个愚蠢的问题,但我是这方面的初学者。

下面的代码应该通过关键字搜索从 Google 本书中获取图书信息。然后它会检查结果并检查我在 Firebase 数据库中是否有匹配的 ISBN。它有效,但目前只能搜索 40 本书,因为这是每次搜索最多 Google 本书 API。

幸运的是,我可以指定索引的起始位置并获取接下来要搜索的 40 本书。不幸的是,我已经尝试了几个小时来理解 URLSession 是如何工作的。我尝试过的所有方法都告诉我 URLSession 块之后的代码不一定要等待会话完成。所以如果我之后检查我是否找到任何匹配项,它可能甚至没有完成搜索。

我怀疑答案在完成处理中,但到目前为止我的尝试都没有成功。下面是我的代码,带有 URL 设置以获取各种起始索引值。

var startingIndex = 0

        //encode keyword(s) to be appended to URL
        let query = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        let url = "https://www.googleapis.com/books/v1/volumes?q=\(query)&&maxResults=40&startIndex=\(startingIndex)"

        URLSession.shared.dataTask(with: URL(string: url)!) { (data, response, error) in
            if error != nil {
                print(error!.localizedDescription)
            }else{

                let json = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String: AnyObject]

                if let items = json["items"] as? [[String: AnyObject]] {

                    //for each result make a book and add title
                    for item in items {
                        if let volumeInfo = item["volumeInfo"] as? [String: AnyObject] {
                            let book = Book()
                            //default values
                            book.isbn13 = "isbn13"
                            book.isbn10 = "isbn10"
                            book.title = volumeInfo["title"] as? String

                            //putting all authors into one string
                            if let temp = volumeInfo["authors"] as? [String] {
                                var authors = ""
                                for i in 0..<temp.count {
                                    authors = authors + temp[i]
                                }
                                book.author = authors
                            }

                            if let imageLinks = volumeInfo["imageLinks"] as? [String: String] {
                                book.imageURL = imageLinks["thumbnail"]
                            }

                            //assign isbns
                            if let isbns = volumeInfo["industryIdentifiers"] as? [[String: String]] {

                                for i in 0..<isbns.count {

                                    let firstIsbn = isbns[i]
                                    if firstIsbn["type"] == "ISBN_10" {
                                        book.isbn10 = firstIsbn["identifier"]
                                    }else{
                                        book.isbn13 = firstIsbn["identifier"]
                                    }
                                }
                            }

                            //adding book to an array of books
                            myDatabase.child("listings").child(book.isbn13!).observeSingleEvent(of: .value, with: { (snapshot) in
                                if snapshot.exists() {
                                    if listings.contains(book) == false{
                                        listings.append(book)
                                    }
                                    DispatchQueue.main.async { self.tableView.reloadData() }
                                }
                            })
                            myDatabase.child("listings").child(book.isbn10!).observeSingleEvent(of: .value, with: { (snapshot) in
                                if snapshot.exists() {
                                    if listings.contains(book) == false{
                                        listings.append(book)
                                    }
                                    DispatchQueue.main.async { self.tableView.reloadData() }
                                }
                            })
                        }
                    }
                }
            }

            SVProgressHUD.dismiss()
            }.resume()

下面是我修改后的代码:

 func searchForSale(query: String, startingIndex: Int) {

        directionsTextLabel.isHidden = true
        tableView.isHidden = false
        listings.removeAll()
        DispatchQueue.main.async { self.tableView.reloadData() }
        SVProgressHUD.show(withStatus: "Searching")

        //clear previous caches of textbook images
        cache.clearMemoryCache()
        cache.clearDiskCache()
        cache.cleanExpiredDiskCache()


        let url = "https://www.googleapis.com/books/v1/volumes?q=\(query)&&maxResults=40&startIndex=\(startingIndex)"

        URLSession.shared.dataTask(with: URL(string: url)!) { (data, response, error) in
            if error != nil {
                print(error!.localizedDescription)
            }else{

                var needToContinueSearch = true

                let json = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String: AnyObject]

                if json["error"] == nil {

                    let totalItems = json["totalItems"] as? Int
                    if totalItems == 0 {
                        SVProgressHUD.showError(withStatus: "No matches found")
                        return
                    }

                    if let items = json["items"] as? [[String: AnyObject]] {

                        //for each result make a book and add title
                        for item in items {

                            if let volumeInfo = item["volumeInfo"] as? [String: AnyObject] {

                                let book = Book()
                                //default values
                                book.isbn13 = "isbn13"
                                book.isbn10 = "isbn10"
                                book.title = volumeInfo["title"] as? String

                                //putting all authors into one string
                                if let temp = volumeInfo["authors"] as? [String] {
                                    var authors = ""
                                    for i in 0..<temp.count {
                                        authors = authors + temp[i]
                                    }
                                    book.author = authors
                                }

                                if let imageLinks = volumeInfo["imageLinks"] as? [String: String] {
                                    book.imageURL = imageLinks["thumbnail"]
                                }

                                //assign isbns
                                if let isbns = volumeInfo["industryIdentifiers"] as? [[String: String]] {

                                    for i in 0..<isbns.count {

                                        let firstIsbn = isbns[i]
                                        //checks if isbns have invalid characters
                                        let isImproperlyFormatted = firstIsbn["identifier"]!.contains {".$#[]/".contains([=13=])}

                                        if isImproperlyFormatted == false {
                                            if firstIsbn["type"] == "ISBN_10" {
                                                book.isbn10 = firstIsbn["identifier"]
                                            }else{
                                                book.isbn13 = firstIsbn["identifier"]
                                            }
                                        }
                                    }
                                }

                                //adding book to an array of books
                                myDatabase.child("listings").child(book.isbn13!).observeSingleEvent(of: .value, with: { (snapshot) in
                                    if snapshot.exists() {
                                        if listings.contains(book) == false{
                                            listings.append(book)
                                            needToContinueSearch = false
                                        }
                                        DispatchQueue.main.async { self.tableView.reloadData() }
                                    }
                                })
                                myDatabase.child("listings").child(book.isbn10!).observeSingleEvent(of: .value, with: { (snapshot) in
                                    if snapshot.exists() {
                                        if listings.contains(book) == false{
                                            listings.append(book)
                                            needToContinueSearch = false
                                        }
                                        DispatchQueue.main.async { self.tableView.reloadData() }
                                        return
                                    }
                                    if startingIndex < 500 {
                                        if needToContinueSearch {
                                            let nextIndex = startingIndex + 40
                                            self.searchForSale(query: query, startingIndex: nextIndex)
                                        }
                                    }
                                })
                            }
                        }
                    }
                }else{
                    return
                }
            }

            SVProgressHUD.dismiss()
            }.resume()

        //hide keyboard
        self.searchBar.endEditing(true)
    }

将 bool 变量声明为 isLoading,如果该函数正在加载,则不要触发 urlsession。希望以下示例对您有所帮助。

var isLoading : Bool = false

func loadMore(with pageCount: Int){
    if isLoading { return }
    isLoading = true
    // call the network
    URLSession.shared.dataTask(with: URL(string: "xxxxx")!) { (data, response, error) in

        // after updating the data set isloding to false again
        // do the api logic here
        // 
        DispatchQueue.main.async {
            // self.items = downloadedItems 
            self.tableView.reloadData()
            self.isLoading = false
        }

    }.resume()

}

在您的完成处理程序中,如果返回了任何结果,您将以:

DispatchQueue.main.async { self.tableView.reloadData() }

触发使用更新后的信息重新加载 table。在同一点,您可以确定可能有更多结果并启动下一个异步 URL 任务。概括地说,您的代码可能是:

let needToContinueSearch : Bool = ...;

DispatchQueue.main.async { self.tableView.reloadData() }

if needToContinueSearch
{  // call routine it initiate next async URL task
}

(如果有任何理由从主线程启动任务,if 将在块中。)

在处理完第一个搜索的结果之前不启动下一个搜索,您可以避免处理尝试在与前一个搜索同时更新数据的后续回调中遇到的任何问题。

但是,如果您发现以这种方式延迟第二次搜索太慢,您可以研究重叠操作的方法,例如您可能让回调只是将结果处理传递给串行队列上的异步任务(以便一次只处理一组结果)并启动下一个异步 URL 任务。

HTH