I'm building an app that only consume a Web Service. For that, I use a method dataTask (URLSession.shared.dataTask).
I'm not waiting for information, only a process is triggered with the next code:
let endPoint = "http://host/service
let url = URL(string: endPoint)
let task = URLSession.shared.dataTask(with: url!) {_, _, _ in
}
task.resume()
When the method dataTask executes, Xcode show me the error:
"Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"
Is there a way to skip the return completionHandler (data, response, error)?
A completion handler is needed, but does not have to be specified when creating the data task object. In that case, you must define a URLSessionDataDelegate that will handle the response.
"A URLSession object need not have a delegate. If no delegate is assigned, when you create tasks in that session, you must provide a completion handler block to obtain the data.
Completion handler blocks are primarily intended as an alternative to using a custom delegate. If you create a task using a method that takes a completion handler block, the delegate methods for response and data delivery are not called."
(https://developer.apple.com/documentation/foundation/urlsessiondatadelegate).
As for the crash, it seems to be related to the force unwrapping (the ! symbol) used in the when declaring the task. You could use a guard condition to abort safely if this error is happening.
guard let url = URL(string: endPoint) else { return }
URLSession.shared.dataTask(with: url) {_, _, _ in
}.resume()
Related
I declare a string in the beginning
var testString: String?
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
let xml = SWXMLHash.parse(data)
testString = xml["root"]["schedule"]["date"].element?.text
}
But outside the NSURLSession, testString is nil. How can I make it so that it does not become nil and I can actually use the value?
For example, I want to use
println (testString)
AFTER the method block. But it is nil
The reason your variable is nil is because closures are executed asynchronously. That means that the rest of the code after the network request will continue to be called as normal, but the code containing parameters data, response and error is only called when the network request is finished.
To work around this, try putting whatever you are trying to do with the variable inside the closure, so your println(testString) would be inside the curly brackets.
I have the following unwrapping line in my code:
UIApplication.sharedApplication().openURL((NSURL(string: url)!))
Sometimes there occurs this fatal error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I know why this error sometimes occurs, but is there a way to make a try - catch statement around this line?
No, this is not what try and catch are for. ! means "if this is nil, then crash." If you don't mean that, then don't use ! (hint: you very seldom want to use !). Use if-let or guard-let:
if let url = NSURL(string: urlString) {
UIApplication.sharedApplication().openURL(url)
}
If you already have a try block and want to turn this situation into a throw, that's what guard-let is ideal for:
guard let url = NSURL(string: urlString) else { throw ...your-error... }
// For the rest of this scope, you can use url normally
UIApplication.sharedApplication().openURL(url)
Recently, I attempted to write my own Telegram Bot API. However, the project has seem to have hit a brick wall with URLSession (formerly NSURLSession) issues.
The call structure is as follows:
getMe() -> getData() -> NSURLSession
Ideally, I would like to have the data returned from NSURLSession passed back to getMe() for the application to process. However, this has not proven possible with the methods I have tried.
Below is the code I have been using. synthesiseURL() generates the URL that the app should open the session to in order to perform the action on the Telegram Bot API. A template of the URL generated by synthesiseURL() is https://api.telegram.org/bot\(token)/\(tgMethod).
// NSURLSession getData: gets data from Telegram Bot API
func getData(tgMethod: String, arguments: [String] = [String](), caller: String = #function) {
let url = synthesiseURL(tgMethod: "getMe"), request = NSMutableURLRequest(url: url)
var receivedData = String()
let session = URLSession.shared.dataTask(with: request as URLRequest) { data, response, err in
if err != nil {print(err!.localizedDescription); return}
DispatchQueue.main.async {
receivedData = String(data: data!, encoding: String.Encoding.nonLossyASCII)!
print(receivedData)
}
}
session.resume()
}
I have been trying to get getData to pass receivedData, which contains the Bot API's response, back to the function getMe.
func getMe() -> String {
HTTPInterface(botToken: token).get(tgMethod: "getMe")
return [???] // here's where the data from getData() should come
}
I have tried completion handlers, callbacks, asynchronous calls to the main thread etc, but none seem to be working as expected (getMe() returns an empty string).
Why is this so, and can it be fixed?
The fundamental issue is that your getMe() function is declared as having an immediate String return type, but it depends on a delayed / asynchronous call to get that string. The timeline looks something like this:
getMe() is called by some client code
getMe() kicks of the method that launches a URLSession to get the data
getMe() moves to the next line of execution and returns a string (still empty at this point). The getMe() function has now returned and the client code execution has continued forward with the empty String result
The URLSession completes with data, but execution has already moved on so the data doesn't get used anywhere
The easiest fix is to make your getMe function not have a return type, but to also call back to a closure parameter when the URLSession data comes back, something like:
func getMe(callback:String->()) {
//getData and pass a closure that executes the callback closure with the String data that comes back
}
The less easy fix is to use a technique like dispatch semaphores to prevent getMe() from returning a result until the URLSession data comes back. But this sort of approach is likely to stall your main thread and is unlikely to be the right choice.
I'm upgrading my code to Swift 2 using error handling with try-catch. I've stuck with closure (NSURLSession), I can't throw inside it.
Generally I'm using such code:
let request = NSURLRequest()
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
throw(ErrorType) // here some errortype enum
}
}
But I'm receiving the error: "Cannot invoke dataTaskWithRequest with an argument list of type …". How can I throw from closure?
You can't throw inside a closure because the closure can be called later, when the function is already executed.
In your example the closure is called asynchronously after the URLRequest got a response, at this time the calling function is already executed.
I declare a string in the beginning
var testString: String?
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
let xml = SWXMLHash.parse(data)
testString = xml["root"]["schedule"]["date"].element?.text
}
But outside the NSURLSession, testString is nil. How can I make it so that it does not become nil and I can actually use the value?
For example, I want to use
println (testString)
AFTER the method block. But it is nil
The reason your variable is nil is because closures are executed asynchronously. That means that the rest of the code after the network request will continue to be called as normal, but the code containing parameters data, response and error is only called when the network request is finished.
To work around this, try putting whatever you are trying to do with the variable inside the closure, so your println(testString) would be inside the curly brackets.