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. However, my code doesn't execute after/within the second API endpoint request's URLSession.shared.dataTask’s line of code/scope.
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.
I’m using the Yelp Fusion API’s “Search” endpoint here. Documentation: 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
Code:
Venue.swift
:
import Foundation
struct Venue {
var name: String?
var id: String?
var rating: Float?
}
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
:
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!):
Updated FetchData.swift
version using @Paulw11's solution without async/await code:
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
. You can also useCodable
to handle the JSON parsing.First, create
Codable
structs to handle the result (hint app.quicktype.io can do this for you):Then you can create an api class that uses async/await to fetch the data.
The basic strategy is:
offset
each time, until you have the expected results.I have used
URLComponents
rather than string interpolation as this handles things like percent encoding where required.