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
Since you are targeting iOS 15, you are making things much harder for yourself by not embracing async/await
. You can also use Codable
to handle the JSON parsing.
First, create Codable
structs to handle the result (hint app.quicktype.io can do this for you):
// 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.
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)
- Keep making requests, increasing the
offset
each time, until you have the expected results. - 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.
Status code 400: NSHTTPURLResponse on calls made using URLSession.shared.dataTask
I have pinpoint the issue. Although the reason is still elusive.
In the generateRequest
function I pass a dictionary of [String:String]
with default values ["":""]
for the request headers.
It appears that request.allHTTPHeaderFields = ["":""]
will break your request.
In prior builds this did not cause any issues but this will result in a bad request on the current beta.
Update
I filled a bug with Apple when I first came across this issue and this was their response:
The problem was that in CFNetwork-856.4.7 (part of seed 1) we made a mistake and removed headers that were blank. In Seed 2, (CFNetwork-861.1) we limited the headers we would remove to 4 “special” headers (Content-Type, User-Agent, Accept-Language, Accept-Encoding). So, that’s why you saw the change in behavior.
macOS El Capitan also sends the “=“ header that results the 400 HTTP status code. So, this is behaving correctly now in Seed 2. Seed 1 was an anomaly and was wrong.
Please let us know whether the issue is resolved for you by updating your bug report.
How to display website status code to user?
You set up the .dataTask
, but never actually execute that code...
let dataURL = "https://google.com"
let request = URLRequest(url: URL(string: dataURL)!)
let task = URLSession.shared.dataTask(with: request) { [self] (data, response, error) in
// properly unwrap the optional response
if let httpResponse = response as? HTTPURLResponse {
let responsecode = httpResponse.statusCode
ServerStausLabel.text = String(responsecode)
} else {
print("Request Failed")
}
}
// this executes the request
task.resume()
How to use NSURLSession to determine if resource has changed?
I created a HTTPEntityFingerprint structure which stores some of the entity headers: Content-MD5
, Etag
, and Last-Modified
.
import Foundation
struct HTTPEntityFingerprint {
let contentMD5 : String?
let etag : String?
let lastModified : String?
}
extension HTTPEntityFingerprint {
init?(response : NSURLResponse) {
if let httpResponse = response as? NSHTTPURLResponse {
let h = httpResponse.allHeaderFields
contentMD5 = h["Content-MD5"] as? String
etag = h["Etag"] as? String
lastModified = h["Last-Modified"] as? String
if contentMD5 == nil && etag == nil && lastModified == nil {
return nil
}
} else {
return nil
}
}
static func match(first : HTTPEntityFingerprint?, second : HTTPEntityFingerprint?) -> Bool {
if let a = first, b = second {
if let md5A = a.contentMD5, md5B = b.contentMD5 {
return md5A == md5B
}
if let etagA = a.etag, etagB = b.etag {
return etagA == etagB
}
if let lastA = a.lastModified, lastB = b.lastModified {
return lastA == lastB
}
}
return false
}
}
When I get an NSHTTPURLResponse
from an NSURLSession
, I create an HTTPEntityFingerprint
from it and compare it against a previously stored fingerprint using HTTPEntityFingerprint.match
. If the fingerprints match, then the HTTP resource hasn't changed and thus I do not need to deserialized the JSON response again; however, if the fingerprints do not match, then I deserialize the JSON response and save the new fingerprint.
This mechanism only works if your server returns at least one of the 3 entity headers: Content-MD5
, Etag
, or Last-Modified
.
More Details on NSURLSession and NSURLCache Behavior
The caching provided by NSURLSession
via NSURLCache
is transparent, meaning when you request a previously cached resource NSURLSession
will call the completion handlers/delegates as if a 200 response occurred.
If the cached response has expired then NSURLSession will send a new request to the origin server, but will include the If-Modified-Since
and If-None-Match
headers using the Last-Modified
and Etag
entity headers in the cached (though expired) result; this behavior is built in, you don't have to do anything besides enable caching. If the origin server returns a 304 (Not Modified), then NSURLSession
will transform this to a 200 response the application (making it look like you fetched a new copy of the resource, even though it was still served from the cache).
Related Topics
Get Path of a File in a Data Set Located in Assets.Xcassets
Include Dictionary or Array Value in Swift String with Backslash Notation
Why This Line Is Not Covered? Xcode Code Coverage
Nscalendar in Swift - Init Can Return Nil, But Isn't Optional
Table View Cells Changing Colors When Scrolling Swift
How to Detect If The User Was Deleted from Firebase Auth
Convert/Wrap Swift Struct as Nsvalue for Caanimation Purposes
Detect When a Custom Cell Is Selected from Within The Cell Itself
Apple Turicreate Always Return The Same Label
Facebook Graphrequest for Swift 5 and Facebook Sdk 5
Make Tabview Background Transparent
Difference Between @Propertydelegate and @Propertywrapper
Convert from Nsdictionary to [String:Any]
Crash Casting Wknsurlrequest As? Other Type
Macos Security Scoped Url Bookmark for Folder
Check Os Version Using Swift on MAC Os X