Swift, URLSession downloading certain data in playgrounds, but not in Xcode project - swift

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")
}
```

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()
}

How to use a JSON file from an Onine server like turbo360?

I am using a do catch scenario to try a JSONDecoder(), the only >problem is that I keep catching the error, but when I review my code I >can't see the error, maybe I need another set of eyes to help me out >of this one!
I placed my JSON file in a storage folder in turbo360, I've also tried gitHub, but neither is working, I believe.
import UIKit
class ViewController: UIViewController {
final let url = URL(string: "https://storage.turbo360.co/portfolio-website-hxoc6m/actors")
override func viewDidLoad() {
super.viewDidLoad()
downloadJson()
}
func downloadJson() {
guard let downloadURL = url else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("something is wrong")
return
}
print("downloaded")
print(downloadURL)
do
{
let decoder = JSONDecoder()
let actors = try decoder.decode(Actors.self, from: data)
print(actors)
} catch {
print("Something wrong after downloaded")
}
}.resume()
}
}
I supposed to get: JSONDonloadingSwift4.Actors
as confirmation that my JSON file has been accessed and decoded
Your JSON is invalid. You are missing an opening " on the last image URL. Fix that and as long as your Actor definition matches you should be good. jsonlint is very useful for checking JSON structure.

Networking in swift outside a playground or project?

I was trying to build a program that I would regularly run to check if a website has changed. It's working in Swift Playgrounds, however, if I try to just copy it over to a .swift file and to then run it directly in the terminal, it doesn't return a response. In fact, it doesn't seem to be doing anything.
Here's my code:
func getSite(website: String) {
let url = URL(string: website)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error?.localizedDescription)
} else {
if let data = data, let host = response?.url?.host, let scheme = response?.url?.scheme {
print("Retrieved data from \(host) over \(scheme)...")
let site = String(data: data, encoding: .utf8)
print(site)
}
}
}
task.resume()
}
Any ideas?
Thanks!
Ok, I just figured it out - Alexander's comment was a huge help.
I just needed to set up a semaphore:
var semaphore = DispatchSemaphore(value: 0)
and semaphore.wait() right after running the async thread. At the end of the completion handler, I put semaphore.signal(). That's it!

getting output from URLSession in XCUITest

I'm trying to grab a value from a URL inside a XCUI Test. However it's not outputting so I'm not sure if there's anything I'm supposed to be doing aside from I've already tried in the code below:
import XCTest
class ExampleUITests: XCTestCase {
func testExample() {
print("We are in XCUITest textExample right now")
let urlString = "https://api.ipify.org/"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data
else {
print("error found in data")
return
}
print("*******")
let outputStr = String(data: data, encoding: String.Encoding.utf8) as String!
print (outputStr)
}.resume()
}
}
The problem is, response from server is returned after test is finished. You should add waiting for network response in the test.
At the start of the test add:
let expectation = expectationWithDescription("")
At the end of the test add:
waitForExpectationsWithTimeout(5.0) { (error) in
if error != nil {
XCTFail(error.localizedDescription)
}
}
And in the network completion block add:
expectation.fulfill()
You can get more info about waining for async task in the test for example here How can I get XCTest to wait for async calls in setUp before tests are run?.

Alamofire causes crash on iPhone 5

I currently give some users access to my app via TestFlight so that they can test it. On every iPhone the App works perfectly without any problems but on the iPhone 5 the app crashes every time. In the xCode simulator everything works properly but not on a real device.
It looks like Alamofire causes the crash. The problem is in Alamofire>Source>Features>ResponseSerialization.swift>response(queue:completionHandler:)
Here is the code from the function:
/// Adds a handler to be called once the request has finished.
///
/// - parameter queue: The queue on which the completion handler is dispatched.
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
#discardableResult
public func response(queue: DispatchQueue? = nil, completionHandler: #escaping (DefaultDataResponse) -> Void) -> Self {
delegate.queue.addOperation {
(queue ?? DispatchQueue.main).async {
var dataResponse = DefaultDataResponse(
request: self.request,
response: self.response,
data: self.delegate.data,
error: self.delegate.error
)
dataResponse.add(self.delegate.metrics)
completionHandler(dataResponse)
}
}
return self
}
completionHandler(dataResponse) looks like it is the problem.
Below there is also a screenshot from xCode
Is this a Alamofire related problem? Because on every other device (5s, 6 Plus, 7 Plus, SE and 7) it works without any problems. The crash occur when the iPhone is connected to WiFi and when it uses the mobile network.
Thanks for any tips!
EDIT:
This should be the code section that is called when starting the application:
Alamofire.request("https://app.site.tld/mobile/ios", parameters: parameters).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.data)")
if response.response != nil{
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)")
let weatherDataArr = utf8Text.components(separatedBy: "~")
guard let hash = String(weatherDataArr[0]) else {
completion(nil)
return
}
do {
//working with received data.
//let currData: wData = try wData(hash: hash, ....)
completion(currData)
} catch {
print("error creating Object: \(error)")
completion(nil)
}
}
else {
completion(nil)
}
}else {
completion(nil)
}
}
Just found a warning in xCode that says:
'catch' block is unreachable because no errors are thrown in 'do' block
First simplify to remove clutter
Alamofire.request("https://app.site.tld/mobile/ios", parameters: parameters).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.data)")
var result: wData? = nil
defer { completion(result) }
guard response.response != nil else { return }
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)")
let weatherDataArr = utf8Text.components(separatedBy: "~")
guard let hash = String(weatherDataArr[0]) else { return }
//working with received data.
//let currData: wData = try wData(hash: hash, ....)
result = currData
}
}
Then we can ask ourselves
How are you 100% sure there are elements in weatherDataArr? If not, use guard let hash = weatherDataArr.first else { return }
What happens under //working with received data.?