Networking in swift outside a playground or project? - swift

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!

Related

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

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

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

Strange error nw_protocol_get_quic_image_block_invoke dlopen libquic failed

I'm new to swift and iOS in general, please keep that in mind.
I get this error when opening the CFReadStream. It does not matter if I open the read or write streams, the app always fails.
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
let host: CFString = NSString(string: hostIP)
let port: UInt32 = UInt32(self.VNCport)
self.password = password
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, port, &readStream, &writeStream)
inputStream = readStream!.takeRetainedValue()
outputStream = writeStream!.takeRetainedValue()
if readStream == nil {
print("Erro read")
}
if writeStream == nil {
print("Erro write")
}
inputStream!.delegate = self
outputStream!.delegate = self
inputStream!.schedule(in: RunLoop.main, forMode: RunLoop.Mode.default)
outputStream!.schedule(in: RunLoop.main, forMode: RunLoop.Mode.default)
inputStream!.open()
outputStream!.open()
I made a clean project with just this function and a Button, the result is the same. No quic lib is used in the project.
Can someone help?
I faced the same error in a different context, in XCode 12.0.1 too. It might not be related, but I suspect its an issue with accessing the run loop of the main thread. I was just trying out some introductory code I found online, and faced the same issue, so this is a bug, rather than a problem with your code. Here's how you can get a piece of code that has the same issue:
git clone git#github.com:BestKora/CS193P-Fall-2017-Demo-iOS12.git
cd "CS193P-Fall-2017-Demo-iOS12/Cassini L10"
xed . # this opens XCode (CLI tool stands for XCode editor)
Having said that, by rewriting the code, I was able to prevent this issue. Maybe you can find something amongst the code below to fix your specific issue:
Specifically, instead of using the following (DispatchQueue.global)
private func fetchImage() {
if let url = imageURL {
spinner.startAnimating()
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
let urlContents = try? Data(contentsOf: url)
DispatchQueue.main.async {
if let imageData = urlContents, url == self?.imageURL {
self?.image = UIImage(data: imageData)
}
}
}
}
}
I use URLSession.shared.dataTask, and this error no longer happens:
private func fetchImage() {
print("fetching image")
if let url = imageURL {
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else {
return
}
// maybe try dispatch to main
DispatchQueue.main.async {
self.imageView.image = UIImage(data: data)
}
}
task.resume()
}
}
I had same issue in an Widget Target, but solved it by adding "public" to the variables.
Fixed struct is shown below.
struct LastCommitEntry: TimelineEntry {
public let date: Date
public let commit: Commit
}
This is just a Simulator message. It won't appear on a real device, and it doesn't affect the behavior of your app, so ignore it.
I was getting this issue in url session.
Thank you
Restarting my simulator did the trick.
For me, it started working when I encoded the data using String(data: safeData, encoding: .utf8) line:
if let safeData = data {
let dataString = String(data: safeData, encoding: .utf8)
print(dataString!)
}
In my case it was a problem with the model. I was working with Codable Model but it wasn't parsing the data properly. When I used the simulator the error was there but when I used my device it disappeared, neverthelesss, the collection view wasn't showing. When I change my model the error was corrected.
You'll need to adopt that specific delegate you're using in your class.
Like in below example:
class ViewController: UIViewController, UITextFieldDelegate
And don't forget to set your current class as the delegate, like below (for my own case):
IBOutlet weak var searchTextField: UITextField!
searchTextField.delegate = self

Use Swift URLSession example code on command line tool

I am trying to figure out the simplest way to make an HTTP request in Swift 4 from the command line. I have copied this code from the URLSession programming guide, and added a couple print statements. I can't figure out why the .dataTask is not executing.
print("Testing URLSession")
let sessionWithoutADelegate = URLSession(configuration: URLSessionConfiguration.default)
if let url = URL(string: "https://www.example.com/") {
print("Encoded url \(url)")
(sessionWithoutADelegate.dataTask(with: url) { (data, response, error) in
print("Executing dataTask")
if let error = error {
print("Error: \(error)")
} else if let response = response,
let data = data,
let string = String(data: data, encoding: .utf8) {
print("Response: \(response)")
print("DATA:\n\(string)\nEND DATA\n")
}
}).resume()
}
The objective is to retrieve data from a REST api, but I can't even make a simple GET request to work properly...
I finally figured out how to make it work using CFRunLoop:
let runLoop = CFRunLoopGetCurrent()
let task = session.dataTask(with: request) { (data, response, error) in
print("Retrieved data")
CFRunLoopStop(runLoop)
}
task.resume()
CFRunLoopRun()
print("done")

datataskwithURL() completion block not being called. Xcode swift

I am trying to write a basic function to make an HTTP Get request and parse the xml data that comes back. I already have completed the section for parsing XML from a local file, but i can't seem to get any data from the server. I tested this code below, but the completion block does not even run, for information to be passed back from the server. Any suggestions please.
func getDatafromURL (url: String) {
guard let urlforRequest = NSURL(string: url) else {
print("Error: cannot create URL")
return
}
let request = NSURLRequest(URL: urlforRequest)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
print(response)
print(error)
}
task.resume()
}