Making Repeated Requests to API - swift

I know how to make a regular API call using swift. What I am not able to understand is how to make the API call to be repeated until required.
I want to call the API every one second
API Call Code Snippet:
let url = URL(string: "https://api.darksky.net/forecast/34eaef38915078ea03c22bb9063bd7ea/37.8267,-122.4233")
let request = URLRequest(url: url!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 10)
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: OperationQueue.main)
let task: URLSessionDataTask = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in
if let error = error {
print(error)
} else if let data = data,
let dataDictionary = try! JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
print("API Data:")
print(dataDictionary)
}
})
task.resume()
Note: This is not the actual API I will be calling

Ideally, for software solutions like financials you mentioned, the server must have support for some sort of Long Polling / websockets mechanism where once connection is established server feeds the client with new values whenever there are updates (refer : https://stackoverflow.com/a/12855533/1436617)
If server does not support : (Not the ideal solution) :
You can actually use recursion in this. On response (both success & failure) of the request again call the same function. That way you can continuously keep polling.
Remember to keep request timer short (5 or 10 seconds instead of 60 seconds) so that if there happens to be an network issue you can quickly make the next call.

Related

Swift URLSession not working for localhost calls

I'm writing a basic API call in Swift using URLRequests, and for whatever reason my call is never executed. I have multiple calls to an external server API using the same method and the functionality is just as expected, however, for my server running locally I get no response or even behavior within the dataTask closure.
I have tried any relevant solutions I could find online such as: Swift URL Session and URL Request not working and Swift 3, URLSession dataTask completionHandler not called. But none of these solutions seem to fix my issue. I know that the local API is working as any calls through Postman go through without fail, yet even after using the Swift snippet provided by Postman, I get no functionality.
func doFoo(id: String, completion: #escaping ([[Float]]) -> ()) {
let semaphore = DispatchSemaphore(value: 0)
var request = URLRequest(url: URL(string: "127.0.0.1:8080/doFoo/\(id)")!, timeoutInterval: Double.infinity)
request.httpMethod = "GET"
print("THIS IS REACHED")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
print("THIS IS NEVER REACHED")
guard let data = data else {
self.semaphore.signal()
return
}
do {
// Decode json using JSONDecoder
// Call completion with JSON data
} catch {
print(error)
}
self.semaphore.signal()
}
task.resume()
self.semaphore.wait()
}
Other posts suggest that this could be an issue with the thread or execution completing before the closure is executed; while I am not super familiar with how the request executes and the behavior of semaphores, my understanding is that they are a way to request threads and prevent the above from happening.
If anyone more familiar with these topics could help me identify and understand why this issue is occurring, it would be greatly appreciated!

Can timeout function be added when getting a string from a URL?

Sometimes I fetch information from a specific site.
But when the response is slow I would like to add a timeout function. I would like to know how.
Can I add a timeout function to the code below?
html = try String(contentsOf: url, encoding: String.Encoding.ascii)
You are not really supposed to use init(contentsOf:encoding:) to read from a remote URL. This initialiser is synchronous so while it is doing that your app's UI will freeze and the user won't be able to do anything, as you may have noticed.
You are supposed to use URLSession and URLRequest to fetch data from remote URLs. They are asynchronous so you get your data in a completion handler.
You can set a timeout in seconds when you create the URLRequest, and you will get an NSError in the completion handler if it timed out (among other reasons).
var request = URLRequest(url: URL(string: "https://example.com")!,timeoutInterval: 10)
request.addValue("text/plain", forHTTPHeaderField: "Content-Type")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
let result = String(data: data, encoding: .ascii)
// do something with result
}
task.resume()

POST Request with URLSession in Swift in command line app [duplicate]

This question already has answers here:
Using NSURLSession from a Swift command line program
(3 answers)
Closed 2 years ago.
I am trying to send HTTP request with POST method in a command line app. Using JSON as body of the request. I am using session.uploadTask to send this request and use JSON data serialised from simple Dictionary. Maybe I missed something but it doesn't work. I even tried to write my request to console and it looks good -> it is the same format as iTranslate API wants.
//creating new session
let session = URLSession.shared
let url = URL(string: "https://api.itranslate.com/translate/v1")!
//setting httpMethod to POST
var request = URLRequest(url: url)
request.httpMethod = "POST"
//setting header
request.setValue("application/json", forHTTPHeaderField: "content-type")
//dictionary with json
let json = ["key": "...", "source": ["dialect":"en", "text": "How are you?"], "target": ["dialect": "es"]] as [String : Any]
//serialization from json to jsonData
let jsonData = try! JSONSerialization.data(withJSONObject: json, options: [])
let task = session.uploadTask(with: request, from: jsonData) { data, response, error in
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print(dataString)
}
}
task.resume()
In most apps, there is a “run loop” that is constantly running, responding to user interaction, keeping the app alive until the user explicitly terminates it. In that scenario, we can initiate the asynchronous network request, confident that the run loop is keeping our app alive, and when the network response comes in, our completion handler closure is called.
In a command line app, on the other hand, when it gets to the end of the code, the app simply terminates, never giving the asynchronous network request a chance to respond, and the closure will never be called.
You can, however, have the command line app wait for the request to finish before terminating, e.g.
let semaphore = DispatchSemaphore(value: 0)
let task = session.uploadTask(with: request, from: jsonData) { data, response, error in
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print(dataString)
}
semaphore.signal()
}
task.resume()
semaphore.wait()
Now, this technique (waiting on the main thread, thereby blocking that thread) is a very bad practice in standard apps, because it will block the main thread while the network request is running. In a typical app, blocking the main thread would freeze the UI, offering a substandard user experience (and the OS might even kill the app for not being responsive). We generally would never block the main thread.
But in a command line app, where you often don't worry about a graceful and responsive UI, blocking the main thread is less of an issue and the above can keep the app alive while the network request is running.

URLSession Response doesn't contain headers from last redirect

I have an URL that I, when called in a webbrowser, will redirect me 2 times and in the response header of the second redirect it will send the Information that I want to extract.
So to automatically extract that information in swift, I wrote this short piece of code that makes the HTTP Request and then prints the response headers:
printv(text: "Loading JSID Location")
req = URLRequest.init(url: JSIDLocation!)
var task : URLSessionDataTask
task = URLSession.shared.dataTask(with: req) {(data, response, error) in
if let res = response as? HTTPURLResponse {
res.allHeaderFields.forEach { (arg0) in
let (key, value) = arg0
self.printv(text: "\(key): \(value)")
}
}
self.printv(text: String.init(data: data!, encoding: String.Encoding.utf8)!)
}
task.resume()
(printv is a function that will format the string and print it to a label)
So when I run this, I expect it to print the response headers and the body of the last redirect, but what actually happens is that i just prints response headers and body of the original URL. As those don't contain the information im looking for, that won't help me. I already googled my problem, and I found out that HTTP Redirects by default are activated in URLSessions and that you'd had to mess with URLSessionDelegates in order to deactivate them but that's definetly not something I did.
Thank you for your help!
If you want redirect information, you need to become the URLSessionDataTaskDelegate.
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
Then you need to implement, the redirection delegate function and be sure to call the completion handler with the given new redirect request:
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: #escaping (URLRequest?) -> Void) {
// operate on response to learn about the headers here
completionHandler(request)
}

Partially downloading data in Swift

I'm trying to develop a download accelerator in Swift. It should get the file's size and divide it to n parts. Then it should download them at once by running multiple threads, and then merge the parts.
I read C# - Creating a Download Accelerator, unfortunately it doesn't help me.
I can do the multiple thread part easily by
DispatchQueue.main.async {
// The new thread
}
but the other part is harder. I usually download a file like this:
try Data(contentsOf: URL(string: assetsUrl!)!)
or I can do the thing that is explained in this answer
class Downloader {
class func load(url: URL, to localUrl: URL, completion: #escaping () -> ()) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = try! URLRequest(url: url, method: .get)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl)
completion()
} catch (let writeError) {
print("error writing file \(localUrl) : \(writeError)")
}
} else {
print("Failure: %#", error?.localizedDescription);
}
}
task.resume()
}
}
But this is not C - it's very simplistic and doesn't accept many arguments. How can I make it get "first 200_000 bytes" from the server?
First of all, the server needs to implement HTTP range requests. If it doesn't, and you don't control the server, then you will not be able to do this.
If the server supports HTTP range requests, then you need to specify the range with request headers, as explained here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
The essentials are that you first send a HEAD request to figure out whether the server supports HTTP range requests. This is determined by whether the response includes the Accept-Ranges header, with a non-zero value.
If the server supports HTTP range requests, then you can make a request for the resource, with the Range header set for example to a value of bytes=0-1023 (depends which format the Accept-Ranges header specified, in this case bytes)