简体   繁体   English

为什么没有在第二个 URLSession.shared.dataTask 之后/内执行代码,即在初始 URLSession.shared.dataTask 的 do 块内? 迅速

[英]Why isn't code being executed after/within a second URLSession.shared.dataTask, that is within an initial URLSession.shared.dataTask’s do block? Swift

I'm trying to make a second API endpoint request, and to do so, am making a second URLSession.shared.dataTask within the initial/first API endpoint request's URLSession.shared.dataTask's do block.我正在尝试发出第二个 API 端点请求,为此,我在初始/第一个 API 端点请求的 URLSession.shared.dataTask 的 do 块中创建了第二个 URLSession.shared.dataTask。 However, my code doesn't execute after/within the second API endpoint request's URLSession.shared.dataTask's line of code/scope.但是,我的代码不会在第二个 API 端点请求的 URLSession.shared.dataTask 的代码行/范围内/之后执行。

I keep getting an infinite while loop that executes outside the scope of the second API endpoint request's URLSession.shared.dataTask when I run the program.当我运行程序时,我不断得到一个无限循环,该循环在第二个 API 端点请求的 URLSession.shared.dataTask 范围之外执行。

I'm using the Yelp Fusion API's “Search” endpoint here.我在这里使用 Yelp Fusion API 的“搜索”端点。 Documentation: https://www.yelp.com/developers/documentation/v3/business_search文档: https ://www.yelp.com/developers/documentation/v3/business_search

The tutorial that I got the original code and format from: https://medium.com/@khansaryan/yelp-fusion-api-integration-af50dd186a6e我从以下网址获得原始代码和格式的教程: https ://medium.com/@khansaryan/yelp-fusion-api-integration-af50dd186a6e

Code:代码:

Venue.swift : Venue.swift

import Foundation

struct Venue {
    var name: String?
    var id: String?
    var rating: Float?
}

FetchData.swift : FetchData.swift

import Foundation

extension ViewController {
    
    func retrieveVenues(latitude: Double,
                        longitude: Double,
                        category: String,
                        limit: Int,
                        sortBy: String,
                        completionHandler: @escaping ([Venue]?, Error?) -> Void) {

        //Prints
        print("Check 1")

        //Making API Call
        let apikey =
        "API key"

        let baseURL =
        "https://api.yelp.com/v3/businesses/search?latitude=\(latitude)&longitude=\(longitude)&categories=\(category)&limit=\(limit)&sort_by=\(sortBy)"

        let url = URL(string: baseURL)

        // Creating Request
        var request = URLRequest(url: url!)
        request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
        request.httpMethod = "GET"

        //Prints before "boringssl_metrics_log_metric_block_invoke" log statement.
        print("Check 2")

        //Log statement "boringssl_metrics_log_metric_block_invoke" printed after the below line of code.

        //Initialize session and task
        URLSession.shared.dataTask(with: request) { (data, response, error) in

            //Prints after "boringssl_metrics_log_metric_block_invoke" log statement.
            print("Check 3")

            if let error = error {
                completionHandler(nil, error)

               //Doesn't print. Is set to print after "boringssl_metrics_log_metric_block_invoke" log statement.
               print("Check 4")

            }
            
            //Prints after "boringssl_metrics_log_metric_block_invoke" log statement.
            print("Check 5")
            
            do {

                //Prints.
                print("Check 6")

                // Read data as JSON
                let json = try JSONSerialization.jsonObject(with: data!, options: [])

                //Prints.
                print("Check 7")

                // Main dictionary
                guard let resp = json as? NSDictionary else {return}

                //Prints.
                print("Check 8")

                guard let totalBusinesses = resp.value(forKey: "total") as? Int else {return}
                
                //Prints.
                print("totalBusinesses outisde and after guard-let statment:", totalBusinesses)

                
                // Businesses
                guard let businesses = resp.value(forKey: "businesses") as? [NSDictionary] else {return}

                //Prints.
                print("Check 9")

                var venuesList: [Venues] = []

                //Prints.
                print("Check 10")              

                //Accessing each business
                for business in businesses {
                    var venue = Venues()
                    venue.name = business.value(forKey: "name") as? String
                    venue.id = business.value(forKey: "id") as? String
                    venue.rating = business.value(forKey: "rating") as? Float
                     
                    venuesList.append(venue)

                    //Prints.
                    print("venuesList.count inside the first initial API Search endpoint request:", venuesList.count)

                }
                
                //Prints.
                print("venuesList.count outside the first initial API Search endpoint request, and its for-loop: for business in businesses { and before the while loop for extra needed API Search endpoint requests below:", venuesList.count)


                //Code for making the amount of API requests to show and add all businesses to venuesList using limit and offsset pararmeters, and totalBusinesses variable. Limit is always 50, and offsset parameter as of now is also always 50, and will be incrimented by 50 at then end of the while loop's executing code's body (within the while loop).

                //Code for an if-statement if the total number of businesses from the initial API Search enpdoint request is more than 50, and therefore, need to make more API "Search" endpoint requests.
                if totalBusinesses > 50 {

                    //Code for making more requests.

                    //Offset value counter. Will add a 50 at the end of every while loop iteration (within evey while loop iteration.)
                    var offsetValue = 50

                    //Print check for offsetValue before while loop for any extra needed requests. Should just print 50. Prints 50. 
                    print("offsetValue before while loop for any extra needed API Search endpoint requests:", offsetValue)

                    //Print Check for seeing what venuesList.count is before the while loop below for any extra needed requests. Prints.
                    print("venuesList.count before while loop for any extra needed API Search endpoint requests:", venuesList.count)

                    //While loop for making requests and adding venue to VeneusList until the total number of businesses have been added.
                    while venuesList.count != totalBusinesses {

                        let baseURL =
                        "https://api.yelp.com/v3/businesses/search?latitude=\(latitude)&longitude=\(longitude)&categories=\(category)&limit=\(limit)&offset=\(offsetValue)&sort_by=\(sortBy)"
                        
                        let url = URL(string: baseURL)

                        // Creating Request
                        var request = URLRequest(url: url!)
                        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
                        request.httpMethod = "GET"

                        //Prints, however, the "boringssl_metrics_log_metric_block_invoke" log statement isn't printed after it, when it should be. Is described more below.
                        print("Check 2")

                        //Log statement "boringssl_metrics_log_metric_block_invoke" is not printed after the below line of code.

                        //Print statements aren't being printed after the below line of code starting with "URLSession.shared.dataTask".
                        //Initialize session and task
                        URLSession.shared.dataTask(with: request) { (data, response, error) in

                            //Print check after below code doesnt print, therefore code under it isn't being executed.

                            //Doesn't print.
                            print("Check 3")

                            if let error = error {
                                completionHandler(nil, error)

                                //Doesn't print.
                                print("Check 4")
                            }
                            //Doesn't print.
                            print("Check 5")

                            do {

                                //Doesn't print.
                                print("Check 6")

                                // Read data as JSON
                                let json = try JSONSerialization.jsonObject(with: data!, options: [])

                                //Doesn't print.
                                print("Check 7")

                                // Main dictionary
                                guard let resp = json as? NSDictionary else {return}

                                //Doesn't print.
                                print("Check 8")

                                // Businesses
                                guard let businesses = resp.value(forKey: "businesses") as? [NSDictionary] else {return}

                                
                                //Print Check for businesses at start of first needed extra API Endpoint Request with offset of 50. Doesn't print.
                                print("Businesses at start of first needed extra API Endpoint Request with offset of 50:", businesses)

                                //Doesn't print.
                                print("Check 9")

                                //Accessing each business
                                for business in businesses {
                                    var venue = Venues()
                                    venue.name = business.value(forKey: "name") as? String
                                    venue.id = business.value(forKey: "id") as? String
                                    venue.rating = business.value(forKey: "rating") as? Float
                                     
                                    venuesList.append(venue)
                                }
                                

                            } catch {
                                print("Caught error")
                            }
                            }.resume()

                        offsetValue += 50

                        //Prints.
                        print("offsetValue after its incrimented by 50 at end of and still within while-loop:", offsetValue)
                        
                        //Prints.
                        print("venuesList.count after offsetValue print statement where its incrimented by 50 at the end of and still within while-loop:",  venuesList.count)


                    }
                    //While Loop closing bracket is one line above this comment.

                    //Print check for exitting while loop.
                    //Still isn't being printed yet, because am stuck in an infinite while loop.
                    print("Exited while loop for any needed extra API endpoint requests.")
                }
                //closing bracket of if-statement: "if totalBusinesses > 50 {" is one line above this comment.
                
                completionHandler(venuesList, nil)
                
            } catch {
                print("Caught error")
            }
            }.resume()

    }
}

Current Return Statement in Terminal : Current Return Statement in Terminal

Check 1
Check 2
[Date and time when project is run and project name] [boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed to log metrics
Check 3
Check 5
Check 6
Check 7
Check 8
totalBusinesses outisde and after guard-let statment: 103
Check 9
Check 10
venuesList.count inside the first initial API Search endpoint request: 1
venuesList.count inside the first initial API Search endpoint request: 2
...
venuesList.count inside the first initial API Search endpoint request: 49
venuesList.count inside the first initial API Search endpoint request: 50
venuesList.count outside the first initial API Search endpoint request, and its for-loop: for business in business { and before the while loop for extra needed API Search endpoint requests below: 50
offsetValue before while loop for extra needed requests: 50
venuesList.count before while loop for any extra needed API Search endpoint requests: 50
Check 2
offsetValue before while loop for extra needed requests: 100
venuesList.count before while loop for any extra needed API Search endpoint requests: 50
Check 2
offsetValue before while loop for extra needed requests: 150
venuesList.count before while loop for any extra needed API Search endpoint requests: 50
Check 2
offsetValue before while loop for extra needed requests: 200
venuesList.count before while loop for any extra needed API Search endpoint requests: 50
Check 2
Continues in an infinite while loop until quitting program (closing or stopping simulator).

Thanks!谢谢!

-- --

Update:更新:

Below is an updated FetchData.swift version using @Paulw11's solution (Doesn't include the async/await code yet, because I want to figure out how to return the value (the variable totalBusinesses's value) totalBusinesses from the makeInitialAPIRequest function that also contains/sends back a completion handler, to the retrieveVenues function, first. This is a current sticking point. Thanks for the help!):下面是使用@Paulw11 的解决方案的更新FetchData.swift版本(还不包​​括 async/await 代码,因为我想弄清楚如何从 makeInitialAPIRequest 函数返回值(变量 totalBusinesses 的值)totalBusinesses 还包含首先将完成处理程序发送回retrieveVenues函数。这是当前的症结所在。感谢您的帮助!):

Updated FetchData.swift version using @Paulw11's solution without async/await code:使用@Paulw11 的解决方案更新FetchData.swift版本,没有 async/await 代码:

import Foundation

extension ViewController {
    
    //Below code is actually located at top of ViewController class.
    var outerScopeRunningVenuesList: [Venue] = []
    
    func retrieveVenues(latitude: Double,
                        longitude: Double,
                        category: String,
                        limit: Int,
                        sortBy: String,
                        completionHandler: @escaping ([Venue]?, Error?) -> Void) {

        //Code for making first/Intial API request, and using outerScopeRunningVenuesList for venuesList values.
        makeInitialAPIRequest(latitude: latitude,
                              longitude: longitude,
                              category: category,
                              limit: limit,
                              sortBy: sortBy) { (response, error) in
        
            if let response = response {
                
                self.outerScopeRunningVenuesList = response
                
                //*Still need to handle the error here, do later.

            }
        }
        
        //Code for getting totalBusinesses return value from makeInitialAPIRequest function.
        var overallMakeInitialAPIRequestReturnValue = makeInitialAPIRequest(latitude: latitude,
                                                                     longitude: longitude,
                                                                     category: category,
                                                                     limit: limit,
                                                                     sortBy: sortBy) { (response, error) in
                                               
                                                   if let response = response {
                                                       
                                                       self.outerScopeRunningVenuesList = response
                                                       
                                                       //*Still need to handle the error here, do later.

                                                   }
                                               }
        
        
        //Getting totalBusinesses return value.
        var recievedTotalBusinesses = overallMakeInitialAPIRequestReturnValue.0
        

        //Code for making the amount of API requests to show and add all businesses to venuesList using limit and offsset pararmeters, and totalBusinesses variable. Limit is always 50, and offsset parameter as of now is also always 50, and will be incrimented by 50 at then end of the while loop's executing code's body (within the while loop).

        //Code for an if-statement if the total number of businesses from the initial API Search enpdoint request is more than 50, and therefore, need to make more API "Search" endpoint requests.
        if recievedTotalBusinesses > 50 {

            //Code for making more requests.

            //Offset value counter. Will add a 50 at the end of every while loop iteration (within evey while loop iteration.)
            var offsetValue = 50

            //Print check for offsetValue before while loop for any extra needed requests. Should just print 50.
            print("offsetValue before while loop for extra needed requests:", offsetValue)

            //Print Check for seeing what venuesList.count is before the while loop below.
            print("outerScopeRunningVenuesList.count before while loop for any extra needed API Search endpoint requests:", outerScopeRunningVenuesList.count)

            //While loop for making requests and adding venue to VeneusList until the total number of businesses have been added.
            while outerScopeRunningVenuesList.count != recievedTotalBusinesses {

                //Code for making extra needed API requests, and using outerScopeRunningVenuesList for venuesList values.
                makeAnyExtraNeededAPIRequest(venuesList: outerScopeRunningVenuesList,
                                             offsetValue: offsetValue,
                                             latitude: latitude,
                                             longitude: longitude,
                                             category: category,
                                             limit: limit,
                                             sortBy: sortBy) { (response, error) in
                
                    if let response = response {
                        
                        self.outerScopeRunningVenuesList = response

                        //*Still need to handle the error here, do later.
                    }


                }

                offsetValue += 50

                print("offsetValue after its incrimented by 50 at end of and still within while-loop:", offsetValue)

                print("outerScopeRunningVenuesList.count after offsetValue print statement where its incrimented by 50 at the end of and still within while-loop:",  outerScopeRunningVenuesList.count)


            }
            //While Loop closing bracket is one line above this comment.

            //Print check for exitting while loop.
            //Still isn't being printed yet, because am stuck in an infinite while loop.
            print("Exitted while loop for any needed extra API Endpoint requests.")
        }
        //Closing bracket of if-statement: "if totalBusinesses > 50 {" is one line above this comment.
                
        completionHandler(outerScopeRunningVenuesList, nil)

    }
    
    func makeInitialAPIRequest(latitude: Double,
                               longitude: Double,
                               category: String,
                               limit: Int,
                               sortBy: String,
                               completionHandler: @escaping ([Venue]?, Error?) -> Void) -> (totalBusinesses: Int) {
        
        print("Check 1")
        
        //Making API Call
        let apikey =
        "API key"

        let baseURL =
        "https://api.yelp.com/v3/businesses/search?latitude=\(latitude)&longitude=\(longitude)&categories=\(category)&limit=\(limit)&sort_by=\(sortBy)"

        let url = URL(string: baseURL)

        // Creating Request
        var request = URLRequest(url: url!)
        request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
        request.httpMethod = "GET"
        
        print("Check 2")

        //Initialize session and task
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            
            print("Check 3")

            if let error = error {
                completionHandler(nil, error)
                
                print("Check 4")

            }
            
            print("Check 5")
            
            do {
                
                print("Check 6")

                // Read data as JSON
                let json = try JSONSerialization.jsonObject(with: data!, options: [])
                
                print("Check 7")

                // Main dictionary
                guard let resp = json as? NSDictionary else {return}
                
                print("Check 8.1: Before totalBusinesses.")

                guard let totalBusinesses = resp.value(forKey: "total") as? Int else {return}
                
                print("Check 8.2: After totalBusinesses and before businesses.")

                
                // Businesses
                guard let businesses = resp.value(forKey: "businesses") as? [NSDictionary] else {return}
                
                print("Check 9")

                var venuesList: [Venues] = []

                
                //Accessing each business
                for business in businesses {
                    var venue = Venues()
                    venue.name = business.value(forKey: "name") as? String
                    venue.id = business.value(forKey: "id") as? String
                    venue.rating = business.value(forKey: "rating") as? Float
                     
                    venuesList.append(venue)
                }
                
                completionHandler(venuesList, nil)
                return totalBusinesses
                
            } catch {
                print("Caught error")
            }
            }.resume()
        
    }
    
    func makeAnyExtraNeededAPIRequests(veneusList: [Venue]?,
        offsetValue: Int,
        latitude: Double,
        longitude: Double,
        category: String,
        limit: Int,
        sortBy: String,
        completionHandler: @escaping ([Venue]?, Error?) -> Void)  {
        
        print("Check 1")
        
        //Code for making any needed extra API endpoint requests.
        let baseURL =
        "https://api.yelp.com/v3/businesses/search?latitude=\(latitude)&longitude=\(longitude)&categories=\(category)&limit=\(limit)&offset=\(offsetValue)&sort_by=\(sortBy)"
        
        let url = URL(string: baseURL)

        // Creating Request
        var request = URLRequest(url: url!)
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.httpMethod = "GET"

        print("Check 2")

        //Print statements arent being printed after below line of code starting with "URLSession.shared.dataTask".
        //Initialize session and task
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            
            print("Check 3")

            if let error = error {
                completionHandler(nil, error)

                
                print("Check 4")
            }
            
            print("Check 5")

            do {

                
                print("Check 6")

                // Read data as JSON
                let json = try JSONSerialization.jsonObject(with: data!, options: [])

                
                print("Check 7")

                // Main dictionary
                guard let resp = json as? NSDictionary else {return}

                
                print("Check 8")

                // Businesses
                guard let businesses = resp.value(forKey: "businesses") as? [NSDictionary] else {return}

                
                //Print Check for businesses at needed extra API Endpoint Request.
                print("Businesses at needed extra API Endpoint Request:", businesses)

                
                print("Check 9")

                //Accessing each business
                for business in businesses {
                    var venue = Venues()
                    venue.name = business.value(forKey: "name") as? String
                    venue.id = business.value(forKey: "id") as? String
                    venue.rating = business.value(forKey: "rating") as? Float
                     
                    venuesList.append(venue)
                }
                
                
                completionHandler(venuesList, nil)

            } catch {
                print("Caught error")
            }
            }.resume()
}

Since you are targeting iOS 15, you are making things much harder for yourself by not embracing async/await .由于您的目标是 iOS 15,因此不采用async/await会使您自己的事情变得更加困难。 You can also use Codable to handle the JSON parsing.您还可以使用Codable来处理 JSON 解析。

First, create Codable structs to handle the result (hint app.quicktype.io can do this for you):首先,创建Codable结构来处理结果(提示app.quicktype.io可以为您执行此操作):

// MARK: - BusinessSearchResult
struct BusinessSearchResult: Codable {
    let total: Int
    let businesses: [Business]
    let region: Region
}

// MARK: - Business
struct Business: Codable {
    let rating: Double
    let price, phone, alias: String?
    let id: String
    let isClosed: Bool?
    let categories: [Category]
    let reviewCount: Int?
    let name: String
    let url: String?
    let coordinates: Center
    let imageURL: String?
    let location: Location
    let distance: Double
    let transactions: [String]

    enum CodingKeys: String, CodingKey {
        case rating, price, phone, id, alias
        case isClosed
        case categories
        case reviewCount
        case name, url, coordinates
        case imageURL
        case location, distance, transactions
    }
}

// MARK: - Category
struct Category: Codable {
    let alias, title: String
}

// MARK: - Center
struct Center: Codable {
    let latitude, longitude: Double
}

// MARK: - Location
struct Location: Codable {
    let city, country, address2, address3: String?
    let state, address1, zipCode: String?

    enum CodingKeys: String, CodingKey {
        case city, country, address2, address3, state, address1
        case zipCode
    }
}

// MARK: - Region
struct Region: Codable {
    let center: Center
}

Then you can create an api class that uses async/await to fetch the data.然后你可以创建一个使用 async/await 来获取数据的 api 类。

The basic strategy is:基本策略是:

  • Fetch the first results获取第一个结果
  • Take a note of the total results that are expected记下预期的总结果
  • Limit the total to 1000 (This is an API limit)将总数限制为 1000(这是 API 限制)
  • Keep making requests, increasing the offset each time, until you have the expected results.不断提出请求,每次都增加offset ,直到获得预期的结果。
  • Return the results返回结果
class YelpApi {
    
    enum SortOption: String {
        case bestMatch="best_match"
        case rating="rating"
        case reviewCount="review_count"
        case distance="distance"
    }
    
    private var apiKey: String
    
    init(apiKey: String) {
        self.apiKey = apiKey
    }
    
    func searchBusiness(latitude: Double,
                        longitude: Double,
                        category: String? = nil,
                        sortBy: SortOption? = nil) async throws -> [Business] {
        
        var queryItems = [URLQueryItem]()
        queryItems.append(URLQueryItem(name:"latitude",value:"\(latitude)"))
        queryItems.append(URLQueryItem(name:"longitude",value:"\(longitude)"))
        if let category = category {
            queryItems.append(URLQueryItem(name:"categories", value:category))
        }
        if let sortOption = sortBy {
            queryItems.append(URLQueryItem(name:"sort_by",value:sortOption.rawValue))
        }
        
        var results = [Business]()
        
        var expectedCount = 0
        let countLimit = 50
        var offset = 0
        
        queryItems.append(URLQueryItem(name:"limit", value:"\(countLimit)"))
        
        repeat {
            
            var offsetQueryItems = queryItems
            
            offsetQueryItems.append(URLQueryItem(name:"offset",value: "\(offset)"))
            
            var urlComponents = URLComponents(string: "https://api.yelp.com/v3/businesses/search")
            urlComponents?.queryItems = offsetQueryItems
            
            guard let url = urlComponents?.url else {
                throw URLError(.badURL)
            }
            
            var request = URLRequest(url: url)
            request.setValue("Bearer \(self.apiKey)", forHTTPHeaderField: "Authorization")
            
            let (data, _) = try await URLSession.shared.data(for: request)
            let businessResults = try JSONDecoder().decode(BusinessSearchResult.self, from:data)

            expectedCount = min(businessResults.total,1000)
            
            results.append(contentsOf: businessResults.businesses)
            offset += businessResults.businesses.count
        } while (results.count < expectedCount)
        
        return results
    }
}

I have used URLComponents rather than string interpolation as this handles things like percent encoding where required.我使用了URLComponents而不是字符串插值,因为它可以在需要时处理诸如百分比编码之类的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM