NSURLRequest with cachePolicy ReloadIgnoringCacheData return stale data - swift

I have an usecase where i need to poll a rest api for authentication on Apple TV. I get stale response JSON response for 90-120 sec after which i am getting proper JSON response.
Below is my code
static func getFileNoCache(url:NSURL?, completionHandler:(NSData?, String?)->Void) {
if let fileUrl = url {
let request = NSURLRequest(URL: fileUrl, cachePolicy: .ReloadIgnoringCacheData, timeoutInterval: 5)
let dataTask = NSURLSession.sharedSession().dataTaskWithRequest(request,
completionHandler: { data, response, error in
if let err = error {
// failed !
print("!! Error - Download Failed \n\t\(fileUrl) reason:\(err.localizedDescription)")
completionHandler (nil, err.localizedDescription)
return
}
if let statusCode = (response as? NSHTTPURLResponse)?.statusCode {
if statusCode == 200 {
completionHandler(data, nil)
}
else {
completionHandler (nil, "Message")
}
}
else {
completionHandler (nil, "Invalid response")
print("!! Error - Downloading EPG Config")
}
})
dataTask.resume()
}
}
I m really clueless whats going wrong

Could be any number of things, from proxies to server-side caching to bugs in the shared session's behavior.
Try disabling the cache entirely by using your own session configuration, setting its URLCache property to nil, then creating a session based on that configuration.
If that doesn't work, then the caching is not in your machine, and your best bet is to add a cache-buster to the URL (e.g. &ignoredParameter=monotonically_increasing_number).

Related

Making HTTP GET request with Swift 5

I am obviously missing something very fundamental/naïve/etc., but for the life of me I cannot figure out how to make simple GET requests.
I'm trying to make an HTTP GET request with Swift 5. I've looked at these posts/articles: one, two, but I can't get print() statements to show anything. When I use breakpoints to debug, the entire section within the URLSession.shared.dataTask section is skipped.
I am looking at the following code (from the first link, above):
func HTTP_Request() {
let url = URL(string: "http://www.stackoverflow.com")!
let task = URLSession.shared.dataTask(with: url) {(data: Data?, response: URLResponse?, error: Error?) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
}
task.resume()
}
HTTP_Request()
I am running this in a MacOS Command Line Project created through XCode.
I would greatly appreciate any help I can get on this, thank you.
Right now, if there is an error, you are going to silently fail. So add some error logging, e.g.,
func httpRequest() {
let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let data = data,
let string = String(data: data, encoding: .utf8)
else {
print(error ?? "Unknown error")
return
}
print(string)
}
task.resume()
}
That should at least give you some indication of the problem.
A few other considerations:
If command line app, you have to recognize that the app may quit before this asynchronous network request finishes. One would generally start up a RunLoop, looping with run(mode:before:) until the network request finishes, as advised in the run documentation.
For example, you might give that routine a completion handler that will be called on the main thread when it is done. Then you can use that:
func httpRequest(completion: #escaping () -> Void) {
let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http
let task = URLSession.shared.dataTask(with: url) { data, response, error in
defer {
DispatchQueue.main.async {
completion()
}
}
guard
error == nil,
let data = data,
let string = String(data: data, encoding: .utf8)
else {
print(error ?? "Unknown error")
return
}
print(string)
}
task.resume()
}
var finished = false
httpRequest {
finished = true
}
while !finished {
RunLoop.current.run(mode: .default, before: .distantFuture)
}
In standard macOS apps, you have to enable outgoing (client) connections in the “App Sandbox” capabilities.
If playground, you have to set needsIndefiniteExecution.
By default, macOS and iOS apps disable http requests unless you enable "Allow Arbitrary Loads” in your Info.plist. That is not applicable to command line apps, but you should be aware of that should you try to do this in standard macOS/iOS apps.
In this case, you should just use https and avoid that consideration altogether.
Make sure the response get print before exiting the process, you could try to append
RunLoop.main.run()
or
sleep(UINT32_MAX)
in the end to make sure the main thread won't exit. If you want to print the response and exit the process immediately, suggest using DispatchSemaphore:
let semphare = DispatchSemaphore(value: 0)
func HTTP_Request() {
let url = URL(string: "http://www.stackoverflow.com")!
let task = URLSession.shared.dataTask(with: url) {(data: Data?, response: URLResponse?, error: Error?) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
semphare.signal()
}
task.resume()
}
HTTP_Request()
_ = semphare.wait(timeout: .distantFuture)
This works for me many times I suggest you snippet for future uses!
let url = URL(string: "https://google.com")
let task = URLSession.shared.dataTask(with: ((url ?? URL(string: "https://google.com"))!)) { [self] (data, response, error) in
do {
let jsonResponse = try JSONSerialization.jsonObject(with: data!, options: [])
print(jsonResponse)
guard let newValue = jsonResponse as? [String:Any] else {
print("invalid format")
}
}
catch let error {
print("Error: \(error)")
}
task.resume()
}

Very slow post request with Swift's URLSession.shared.dataTask

I'm currently porting an Android app over to iOS and I've noticed a significant decrease in performance for my HTTPS post requests. On Android, using Java HttpsURLConnection objects, post requests would take about 0.5-1.0 seconds on average (for transferring a maximum of about 100 characters). On Swift, using URLSession.shared.dataTask to perform the same post requests takes anywhere from half a second to 15 seconds, sometimes timing out.
One trend I've noticed is that, while running the app, the various requests that are made are either all slow (> 5 seconds) or all faster. The time for requests sporadically changes every time the app is restarted.
Below is my current dataTask code. getResponse and onSuccess are functions for response handling, but I've found that they aren't what's slowing down the requests. From a few "tests" (print statements), it seems like the slow-downs occur as network connections are first being established.
func execute() {
guard let url = URL(string: "https://website.com") else {
print("ERROR > INVALID URL")
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
let postDataString = "data"
guard let requestBody: Data = postDataString.data(using: String.Encoding.utf8) else {
print("ERROR > FAILED TO ENCODE POST STRING")
return
}
request.httpBody = requestBody
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if let unwrappedError = error {
print("ERROR > FAILED TO CONNECT TO SERVER: \(error)")
return
}
guard let unwrappedData = data else {
print("ERROR > FAILED TO GET DATA")
return
}
guard let dataString = String(data: data, encoding: String.Encoding.utf8)?.replacingOccurrences(of: "+", with: "%2B") else {
print("ERROR > FAILED TO DECODE RESPONSE TO STRING")
return
}
let dataStringLines: [String] = dataString.components(separatedBy: "\n")
guard let unwrappedGetResponse = self.getResponse else {
print("ERROR > getResponse FUNCTION UNINITIALIZED")
return
}
let success: Bool = unwrappedGetResponse(dataStringLines)
if (success) {
guard let unwrappedOnSuccess = self.onSuccess else {
print("ERROR > onSuccess FUNCTION UNINITIALIZED")
return
}
DispatchQueue.main.async {
unwrappedOnSuccess()
}
}
else {
print("ERROR > BAD SERVER RESPONSE")
}
}
task.resume()
}
Some additional information: I'm testing the code with an iPhone 5S running iOS 12.4.5. The app is targeting iOS 10.0.
Any idea on what could cause such inconsistent performance?

How to handle many API calls with Swift 3 GCD

I am building an swift app to interact with an MDM API to do large numbers of updates via PUT commands, and I am running in to issues with how to handle the massive numbers of API calls without overloading the servers.
I am parsing through a CSV, and each line is an update. If I run the commands asynchronously, it generates and sends ALL of the API calls immediately, which the server doesn't like.
But if I run the commands synchronously, it freezes my GUI which is less than ideal, as the end user doesn't know what's going on, how long is left, if things are failing, etc.
I have also tried creating my own NSOperation queue and setting the max number of items to like 5, and then putting the synchronous function in there, but that doesn't seem to work very well either. It still freezes the GUI with some really random UI updates that seem buggy at best.
The servers can handle 5-10 requests at a time, but these CSV files can be upwards of 5,000 lines sometimes.
So how can I limit the number of simultaneous PUT requests going out in my loop, while not having the GUI freeze on me? To be honest, I don't even really care if the end user can interact with the GUI while it's running, I just want to be able to provide feedback on the lines that have run so far.
I have a wrapper which a colleague wrote most of, and the async function looks like this:
func sendRequest(endpoint: String, method: HTTPMethod, base64credentials: String, dataType: DataType, body: Data?, queue: DispatchQueue, handler: #escaping (Response)->Swift.Void) {
let url = self.resourceURL.appendingPathComponent(endpoint)
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30.0)
request.httpMethod = "\(method)"
var headers = ["Authorization": "Basic \(base64credentials)"]
switch dataType {
case .json:
headers["Content-Type"] = "application/json"
headers["Accept"] = "application/json"
if let obj = body {
do {
request.httpBody = try JSONSerialization.data(withJSONObject: obj, options: JSONSerialization.WritingOptions(rawValue: 0))
} catch {
queue.async {
handler(.badRequest)
}
return
}
}
case .xml:
headers["Content-Type"] = "application/xml"
headers["Accept"] = "application/xml"
request.httpBody = body
/*if let obj = body {
request.httpBody = (obj as! XMLDocument).xmlData
}*/
}
request.allHTTPHeaderFields = headers
session.dataTask(with: request) {
var response: Response
if let error = $2 {
response = .error(error)
} else {
let httpResponse = $1 as! HTTPURLResponse
switch httpResponse.statusCode {
case 200..<299:
if let object = try? JSONSerialization.jsonObject(with: $0!, options: JSONSerialization.ReadingOptions(rawValue: 0)) {
response = .json(object)
} else if let object = try? XMLDocument(data: $0!, options: 0) {
response = .xml(object)
} else {
response = .success
}
default:
response = .httpCode(httpResponse.statusCode)
}
}
queue.async {
handler(response)
}
}.resume()
Then, there is a synchronous option which uses semaphore, which looks like this:
func sendRequestAndWait(endpoint: String, method: HTTPMethod, base64credentials: String, dataType: DataType, body: Data?) -> Response {
var response: Response!
let semephore = DispatchSemaphore(value: 0)
sendRequest(endpoint: endpoint, method: method, base64credentials: base64credentials, dataType: dataType, body: body, queue: DispatchQueue.global(qos: .default)) {
response = $0
semephore.signal()
}
semephore.wait()
return response
}
Usage information is as follows:
class ViewController: NSViewController {
let client = JSSClient(urlString: "https://my.mdm.server:8443/", allowUntrusted: true)
let credentials = JSSClient.Credentials(username: "admin", password: "ObviouslyNotReal")
func asynchronousRequestExample() {
print("Sending asynchronous request")
client.sendRequest(endpoint: "computers", method: .get, credentials: credentials, dataType: .xml, body: nil, queue: DispatchQueue.main) { (response) in
print("Response recieved")
switch response {
case .badRequest:
print("Bad request")
case .error(let error):
print("Receieved error:\n\(error)")
case .httpCode(let code):
print("Request failed with http status code \(code)")
case .json(let json):
print("Received JSON response:\n\(json)")
case .success:
print("Success with empty response")
case .xml(let xml):
print("Received XML response:\n\(xml.xmlString(withOptions: Int(XMLNode.Options.nodePrettyPrint.rawValue)))")
}
print("Completed")
}
print("Request sent")
}
func synchronousRequestExample() {
print("Sending synchronous request")
let response = client.sendRequestAndWait(endpoint: "computers", method: .get,credentials: credentials, dataType: .json, body: nil)
print("Response recieved")
switch response {
case .badRequest:
print("Bad request")
case .error(let error):
print("Receieved error:\n\(error)")
case .httpCode(let code):
print("Request failed with http status code \(code)")
case .json(let json):
print("Received JSON response:\n\(json)")
case .success:
print("Success with empty response")
case .xml(let xml):
print("Received XML response:\n\(xml.xmlString(withOptions: Int(XMLNode.Options.nodePrettyPrint.rawValue)))")
}
print("Completed")
}
override func viewDidAppear() {
super.viewDidAppear()
synchronousRequestExample()
asynchronousRequestExample()
}
I have modified the send functions slightly, so that they take base64 encoded credentials off the bat, and maybe one or two other things.
Can't you just chain operations to send 3/4 requests at a time per operation?
https://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift
Just so you know, NSOperation (also abstracted by Operation with Swift3) are running by default on background threads. Just be careful to not run heavy tasks in your completion block that might run tasks on the main thread (this will freeze your UI).
The only other case I see that can freeze your UI is by executing too many operations at once.
Well, I think I got this covered! I decided to climb out of the rabbit hole a ways and simplify things. I wrote my own session instead of relying on the wrapper, and set up semaphores in it, threw it in an OperationQueue and it seems to be working perfectly.
This was the video I followed to set up my simplified semaphores request. https://www.youtube.com/watch?v=j4k8sN8WdaM
I'll have to tweak the below code to be a PUT instead of the GET I've been using for testing, but that part is easy.
//print (row[0])
let myOpQueue = OperationQueue()
myOpQueue.maxConcurrentOperationCount = 3
let semaphore = DispatchSemaphore(value: 0)
var i = 0
while i < 10 {
let myURL = NSURL(string: "https://my.server.com/APIResources/computers/id/\(i)")
myOpQueue.addOperation {
let request = NSMutableURLRequest(url: myURL! as URL)
request.httpMethod = "GET"
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = ["Authorization" : "Basic 123456789ABCDEFG=", "Content-Type" : "text/xml", "Accept" : "text/xml"]
let session = Foundation.URLSession(configuration: configuration)
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) -> Void in
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
semaphore.signal()
self.lblLine.stringValue = "\(i)"
self.appendLogString(stringToAppend: "\(httpResponse.statusCode)")
print(myURL!)
}
if error == nil {
print("No Errors")
print("")
} else {
print(error!)
}
})
task.resume()
semaphore.wait()
}
i += 1
}

Making multiple asynchronous HTTP requests in succession and writing with Realm

I'm currently using Alamofire for requesting data and writing to disk with Realm. Specifically, I am fetching 24 source URLS from a Facebook Graph GET request and then making 24 separate requests to retrieve the data for each image. Once the data is retrieved, I am writing to disk with Realm.
here is how I am fetching the 24 sources:
FBAPI
Alamofire.request(.GET, FBPath.photos, parameters: params).responseJSON { response in
guard response.result.error == nil else {
print("error calling GET on \(FBPath.photos)")
print(response.result.error!)
completion(latestDate: nil, photosCount: 0, error: response.result.error)
return
}
if let value = response.result.value {
let json = JSON(value)
if let photos = json[FBResult.data].array {
for result in photos {
let manager = PTWPhotoManager()
manager.downloadAndSaveJsonData(result)
}
As you can see, I have a for loop iterating through each JSON containing the source url for the photo's image in which I then make another network request for each url, like so:
Manager
func downloadAndSaveJsonData(photoJSON : JSON) {
let source = photoJSON[FBResult.source].string
let id = photoJSON[FBResult.id].string
let created_time = photoJSON[FBResult.date.createdTime].string
let imageURL = NSURL(string: source!)
print("image requested")
Alamofire.request(.GET, imageURL!).response() {
(request, response, data, error) in
if (error != nil) {
print(error?.localizedDescription)
}
else {
print("image response")
let photo = PTWPhoto()
photo.id = id
photo.sourceURL = source
photo.imageData = data
photo.createdTime = photo.createdTimeAsDate(created_time!)
let realm = try! Realm()
try! realm.write {
realm.add(photo)
}
print("photo saved")
}
}
}
There seems to be a very long delay between when each image's data is requested and when I receive a response, and it also does not appear to be asynchronous. Is this a threading issue or is there a more efficient way to request an array of data like this? It should also be noted that I am making this network request from the Apple Watch itself.
These requests will happen mostly asynchronous as you wish. But there is some synchronization happening, you might been not aware of:
The response closures for Alamofire are dispatched to the main thread. So your network responses competes against any UI updates you do.
Realm write transactions are synchronous and exclusive, which is enforced via locks which will block the thread where they are executed on.
In combination this both means that you will block the main thread as long as the network requests succeed and keep coming, which would also render your app unresponsive.
I'd recommend a different attempt. You can use GCD's dispatch groups to synchronize different asynchronous tasks.
In the example below, the objects are all kept in memory until they are all downloaded.
A further improvement could it be to write the downloaded data onto disk instead and store just the path to the file in the Realm object. (There are plenty of image caching libraries, which can easily assist you with that.)
If you choose a path, which depends only on the fields of PWTPhoto (or properties of the data, you can get through a quick HEAD request), then you can check first whether this path exists already locally before downloading the file again. By doing that you save traffic when updating the photos or when not all photos could been successfully downloaded on the first attempt. (e.g. app is force-closed by the user, crashed, device is shutdown)
class PTWPhotoManager {
static func downloadAllPhotos(params: [String : AnyObject], completion: (latestDate: NSDate?, photosCount: NSUInteger, error: NSError?)) {
Alamofire.request(.GET, FBPath.photos, parameters: params).responseJSON { response in
guard response.result.error == nil else {
print("error calling GET on \(FBPath.photos)")
print(response.result.error!)
completion(latestDate: nil, photosCount: 0, error: response.result.error)
return
}
if let value = response.result.value {
let json = JSON(value)
if let photos = json[FBResult.data].array {
let group = dispatch_group_create()
var persistablePhotos = [PTWPhoto](capacity: photos.count)
let manager = PTWPhotoManager()
for result in photos {
dispatch_group_enter(group)
let request = manager.downloadAndSaveJsonData(result) { photo, error in
if let photo = photo {
persistablePhotos.add(photo)
dispatch_group_leave(group)
} else {
completion(latestDate: nil, photosCount: 0, error: error!)
}
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
let realm = try! Realm()
try! realm.write {
realm.add(persistablePhotos)
}
let latestDate = …
completion(latestDate: latestDate, photosCount: persistablePhotos.count, error: nil)
}
}
}
}
}
func downloadAndSaveJsonData(photoJSON: JSON, completion: (PTWPhoto?, NSError?) -> ()) -> Alamofire.Request {
let source = photoJSON[FBResult.source].string
let id = photoJSON[FBResult.id].string
let created_time = photoJSON[FBResult.date.createdTime].string
let imageURL = NSURL(string: source!)
print("image requested")
Alamofire.request(.GET, imageURL!).response() { (request, response, data, error) in
if let error = error {
print(error.localizedDescription)
completion(nil, error)
} else {
print("image response")
let photo = PTWPhoto()
photo.id = id
photo.sourceURL = source
photo.imageData = data
photo.createdTime = photo.createdTimeAsDate(created_time!)
completion(photo, nil)
}
}
}
}

Making an API call in Swift 2.0

I've been scouring examples looking to pull some ideas together, I've come up with this although I'm not getting any output. It never enters the do which leads me to believe I have an issue with my call.
Can anyone shed some light on this for me or lead me to an appropriate location with more information on API calls in swift 2.0? Examples of this are quite sparse.
let url : String = "http://www.fantasyfootballnerd.com/service/nfl-teams/json/test/"
let request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
print("Start")
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
let jsonResult: NSDictionary! = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
print("In method")
if (jsonResult != nil) {
// process jsonResult
print("Data added")
} else {
print("No Data")
// couldn't load JSON, look at error
}
}
catch {
print("Error Occured")
}
}
You're missing just one thing. You need to start the request:
// call this after you configure your session
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
// process results
}.resume()