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.
Related
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()
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.
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.
My class has a property of type NSURL that is initialized from a string. The string is known at compile time.
For the class to operate appropriately, it must be set to its intended value at initialization (not later), so there is no point in defining it as an optional (implicitly unwrapped or otherwise):
class TestClass: NSObject {
private let myURL:NSURL
...
Assuming that NSURL(string:) (which returns NSURL?) will never fail if passed a valid URL string that is known at compile time, I can do something like this:
override init() {
myURL = NSURL(string: "http://www.google.com")!
super.init()
}
However, I somehow don't feel comfortable around the forced unwrapping and would like to guard the URL initialization somehow. If I try this:
guard myURL = NSURL(string: "http://www.google.com") else {
fatalError()
}
Value of optional type 'NSURL?' not unwrapped; did you mean to use '!'
or '?'?
(Note: there's no way to add a ! or ? anywhere the code above that will fix the error. Conditional unwrapping only happens with guard let... guard var..., and myURL is already defined)
I understand why this fails: Even a successful call to NSURL(string:) is returning the (valid) NSURL wrapped inside an optional NSURL?, so I still need to unwrap it somehow before assigning to myURL (which is non-optional, hence not compatible for assignment as-is).
I can get around this by using an intermediate variable:
guard let theURL = NSURL(string: "http://www.google.com") else {
fatalError()
}
myURL = theURL
...but this is obviously not elegant at all.
What should I do?
Update Another approach, that doesn't use guard, would be to use a switch, as optionals map to the Optional enum:
init?() {
switch URL(string: "http://www.google.com") {
case .none:
myURL = NSURL()
return nil
case let .some(url):
myURL = url
}
}
although you'd still get a url local variable.
Original answer
You can declare your initializer as a failable one and return nil in case the url string parsing fails, instead of throwing a fatal error. This will make it more clear to clients your the class that the initializer might fail at some point. You still won't get rid of the guard, though.
init?() {
guard let url = URL(string: "http:www.google.com") else {
// need to set a dummy value due to a limitation of the Swift compiler
myURL = URL()
return nil
}
myURL = url
}
This add a little complexity on the caller side, as it will need to check if the object creation succeeded, but it's the recommended pattern in case the object initializer can fail constructing the object. You'd also need to give up the NSObject inheritance as you cannot override the init with a failable version (init?).
You can find out more details about failable initializers on the Swift blog, Apple's documentation, or this SO question.
I know that if I want to use the variable text outside of this try block in Swift, I would write this code thusly,
var text = NSString()
do {
text = try NSString( contentsOfURL: url, encoding: NSUTF8StringEncoding ) }
catch let errOpening as NSError {
// ...
}
From days of yore when storage was meted by bytes, my mantra has been to use constants if at all possible. So in the instance where text will be loaded once and never changed, my gut tells me to make it a constant.
do {
let text = try NSString( contentsOfURL: url, encoding: NSUTF8StringEncoding ) }
catch let errOpening as NSError {
// ...
}
But then I can't use the loaded text outside of the try block. Is there any way to have text be treated as a constant outside of the try block in this context by Swift? Or is this just the yearning of an old man coding in an old style, and I should use var, forget about it, and move on?
Many thanks in advance!
You can do:
let text: String
do {
text = try String(contentsOfURL: url, encoding: NSUTF8StringEncoding)
}
catch let error as NSError {
// ...
}
(I’ve used String, the native string type in Swift, rather than NSString.)
Assuming later code makes use of text, the catch block must either assign something to it or return from the enclosing function, as constants must be initialised before being used.
Bear in mind that the method you’re using to retrieve the contents of a URL as a string does so synchronously, so if this code runs on the main thread you will block the UI. Look up the NSURLSession docs for details on asynchronously loading the content of URLs, which will avoid blocking the UI.