Reputation:
In a program I'm making, Im trying to access web content to display the weather. I'm struggling with the concept of optionals and I'm wondering why this line of code works when a "?" is added, but not without.
@IBAction func pressedSearch(sender: AnyObject) {
var urlString = "http://www.weather-forecast.com/locations/" + cityField.text.stringByReplacingOccurrencesOfString(" ", withString: "") + "/forecasts/latest"
var url = NSURL(string: urlString)
println(urlString)
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) in
var urlContent = NSString(data: data, encoding: NSUTF8StringEncoding)
var contentArray = urlContent?.componentsSeparatedByString("<span class=\"phrase\">") as Array<String>
println(contentArray[1])
}
task.resume()
}
Here is the line of code where the "?" was added to make the code work:
var contentArray = urlContent?.componentsSeparatedByString("<span class=\"phrase\">") as Array<String>
What does the "?" do to fix the error, and why do we have to add "?" and "!" to some variables and not others throughout swift?
Upvotes: 1
Views: 77
Reputation: 536047
The problem is that in this line:
var urlContent = NSString(data: data, encoding: NSUTF8StringEncoding)
...you are calling a method that is a failable initializer in Objective-C/Cocoa. It can return nil to indicate failure (e.g. because that data couldn't be decoded using that encoding).
Therefore, in Swift, if there is no failure, this method returns an Optional wrapping a string (because only when there is an Optional can nil be used in Swift).
Therefore, you must thereafter treat it as an Optional - because Optional wrapping a String is not a String - it is an Optional. Thus, you unwrap to access the String.
Upvotes: 1
Reputation: 13316
Your line of code is an example of "optional chaining". It is used when you try to access a method or a property of a variable that might be nil
. Take this as an example:
class Person {
let firstName = "John"
var lastName: String? = nil
}
let person = Person()
Now suppose you try to perform an operation on person
's last name...you want an uppercase version of it, or something. Your code might look like this:
let ucLastName = person.lastName.uppercaseString
But lastName
is nil
. You can't do anything with it because it doesn't exist. If the code above were able to compile, it would crash the program when you ran it.
To prevent the crash, the compiler requires you to insert a ?
after lastName
, like this:
let ucLastName = person.lastName?.uppercaseString
That will compile. Why? Because inserting the ?
tells the compiler to ignore everything after the ?
if lastName
is nil
and return nil
to ucLastName
- the uppercaseString
property is never even accessed once the compiler determines that lastName == nil
.
In your case, the variable urlContent
is an optional - it is akin to lastName
in my example. You may not realize it is an optional, because you didn't declare it as one, but it is, because it was returned by a function, NSString(data: data, encoding: NSUTF8StringEncoding)
that returns an optional as a result. So even if you didn't realize it was optional, the compiler knew it, and told you so by requiring you to insert the ?
value in the code.
Requiring the ?
is good because:
nil
variablesnil
. You are on notice that a runtime crash is coming if you handle it wrong.Consider what would happen if you weren't required to insert the ?
. Your program would crash at runtime, and you would have to debug it by searching through the code for something that went wrong, and sometimes very few clues as to what happened. The ?
, and the compiler warnings that go along with it, allow you to solve the problem at compile time, instead of debugging runtime crashes.
Upvotes: 0
Reputation: 14040
Here's some code to help you understand ?
and !
better (including what you're telling the machine):
var foo = resultOfAFunctionThatReturnsAQuestionMarkValue()
foo.accessFunction() // "<silence>..."
// this could break if foo is nil
var bar = resultOfAFunctionThatReturnsAQuestionMarkValue()
bar?.accessFunction() // "Hey, bro. This might be nil.
// If it is, just ignore the line."
// accessFunction() might not be called,
// but it won't explode.
var asdf = resultOfAFunctionThatReturnsAQuestionMarkValue()
asdf!.accessFunction() // "Yo, dude. I know for sure this is not nil.
// Don't bother checking first. It's cool."
// this could break if asdf if nil
// You just lied to the computer.
// (They will remember.)
Upvotes: 2
Reputation: 7501
From the official documentation
Return Value
An NSString object initialized by converting the bytes in data into Unicode characters using encoding. The returned object may be different from the original receiver. Returns nil if the initialization fails for some reason (for example if data does not represent valid data for encoding).
Using NSString(data:, encoding:)
doesn't always return a NSString, it can return nil. This is why the type of urlContent
is NSString?
instead of NSString
. Therefore, you need that question mark in the next line.
For more info about ? and !, see here : What is an optional value in Swift?
TL;DR : "?" implies a variable can be nil, if you want to access it, ignoring that nil possibility, you need "!"
Upvotes: 0