I am trying to do some parsing of HTML on client side using Swift inside Xcode Project. I first tested this function inside playgrounds for a variety of URLs, and it downloads instantly for all my use cases. However, running this inside my Xcode project for iOS (even when disabling ATS in my info.plist), the URLSession will not download anything for many of the URLs to common websites that worked in playgrounds. It will still download some, such as the html of apple.com. Can anybody explain what I might be missing or need to enable/disable to get this to work.
func fetchHTMLString(url: URL) {
let task = URLSession.shared.downloadTask(with: url) { localURL, urlResponse, error in
if let localURL = localURL {
if let string = try? String(contentsOf: localURL) {
print("String here")
self.sortData(htmlString: string)
} else {
print("couldnt get as string")
}
}
}
task.resume()
print("going")
}
Update, I attempted to change this function to use URLSession data task, and was able to successfully download. I am, however, now just curious to find out why this would allow the download to complete with data task and not with download task. Here's the code that works
func fetchHTMLString(url: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error \(error.localizedDescription)")
} else if let data = data, let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
if let string = String(data: data, encoding: .utf8) {
print(string)
self.sortData(htmlString: string)
print("String here")
} else {
print("couldn't get as string")
}
} else {
print("Error \(response.statusCode)")
}
} else {
print("No data or error returned.")
}
}
task.resume()
print("going")
}
```
I have a url that works in Postman and in browser, but not in app.
I have an if let url = URL(string: urlString) line, but apparently the URL(string: urlString) is returning nil so it doesn't enter the block. It doesn't actually throw an error so I can't actually search for an error.
I've tried looking at other people's similar problems but haven't found a solution. Any help would be appreciated. If you could point me to another post with a potential solution I'd appreciate that too. Thank you.
Here is my code. I've used this many times before with no problems.
func performRequest<T: Codable>(urlString: String, returnType: T.Type, completion: #escaping (Result<T, Error>) -> Void ) {
print("\n\(#function)")
if let url = URL(string: urlString) { // <--- FAILS RIGHT HERE
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, _, error) in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else { return }
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(T.self, from: data)
completion(.success(decodedData))
} catch let decodingErr {
completion(.failure(decodingErr))
}
}
task.resume()
} else {
print("Something went wrong with the url")
}
}
There was a space in my url that Swift wasn't accepting. Apparently Postman and browsers can still make the call with a space, but not Swift. Thank you to #workingdog for solving this.
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()
}
I am trying to downlod multiple JSON files with a URLSession and when I run the funtion one time it works. But the moment I call the getSMAPrices function from a loop it does not work and I can not find out why.
Here is the working download function that works if i call it.
func getSMAPrices(symbol: String) {
let urlString = "https://www.alphavantage.co/query?function=SMA&symbol=\(symbol)&interval=daily&time_period=9&series_type=close&apikey=KPLI12AW8JDXM77Y"
guard let url = URL(string: urlString) else {
return
}
dataTask = defaultSession.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else {
return
}
//Implement JSON decoding and parsing
do {
//Decode retrived data with JSONDecoder and assing type of Article object
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let stockData = try decoder.decode(SimpelMovingAvarage.self, from: data)
//Get back to the main queue
DispatchQueue.main.async {
print(stockData)
}
} catch let jsonError {
print(jsonError)
}
})
dataTask?.resume()
}
And here is my very simple loop that replaces a part in the URL every run cycle. But nothing happens.
public func scanSymbols() {
for symbol in self.symbols {
progress += 1
progresBar.maxValue = Double(symbols.count)
progresBar.doubleValue = progress
//This does not work
getSMAPrices(symbol: symbol.key)
}
}
It's because your dataTask variable appears to be an instance property and is getting overwritten every time you call this method.
I'm a newbie to Swift. I'm trying to simply read in a web page and I am getting the error "Initialization of immutable value 'task' was never used; consider replacing it with assignment to '_' or removing it" error on the "let task = " statement. Here's my code (please excuse my debugging statements). What am I doing wrong?
let urlPath = "http://www.stackoverflow.com"
let url = URL(string: urlPath)
let session = URLSession.shared
let task = session.dataTask(with:url!) { (data, response, error) -> Void in
if (error == nil) {
print("inside If")
print(data)
} else {
print("inside Else")
print(error)
}
print("after If Else")
}
What you are seeing is actally a warning, the compiler is telling you that you initialized a varaible but never used it. In general you can either replace the variable name with _ or remove the declaration entirely since you are not using it (or just ignore the warning). However in your case you actually need it, after you initialize the data task, you are not actually using it, which is why you aren't seeing any of your print statements being output. To fix this, call task.resume(). Doing this will also remove the warning since you are now actually using that variable.
let urlPath = "http://www.stackoverflow.com"
let url = URL(string: urlPath)
let session = URLSession.shared
let task = session.dataTask(with:url!) { (data, response, error) -> Void in
if (error == nil) {
print("inside If")
print(data)
} else {
print("inside Else")
print(error)
}
print("after If Else")
}
task.resume()