Swift Impossible Type Inference - swift

I am trying to load a file from my app bundle in Swift 3, and I came across a weird situation with the Swift type inferencing. If I use the following code, I get an error on the third line that says Value of optional type "String?" not unwrapped.
let url = NSURL(fileURLWithPath:Bundle.main.bundlePath)
let url2 = url.appendingPathComponent("foo.txt")
let path:String = url2?.path
To fix the error I unwrap the value on the third line by changing it to:
let path:String = url2?.path!
I now get the error Cannot force unwrap value of a non-optional type 'String'. It seems like Swift can't determine whether the path property is a String or a String?. The autocomplete feature in Xcode says it is a String, but the docs say it is a String?.
The suggested fix by Xcode for the first error was to replace url2?.path with (url2?.path)!, which finally ended up working, but I have no idea why this works and the other ways don't.
let path:String = (url2?.path)!
What is going on? Is this a type inference bug in Swift, or am I missing something super obvious

In Swift, Optional chaining like:
let path:String = url2?.path!
... is interpreted as:
let path:String = url2 != nil ? url2!.path!
: nil
As you see the type of path is non-Optional String, so the expression causes error.
(url2's type is URL?, so the type of property path is String, not String?.)
This is not a direct answer to your question, but I would re-write your code as:
let url = Bundle.main.bundleURL
let url2 = url.appendingPathComponent("foo.txt")
let path:String = url2.path
Shorter, and no worry about Optionals.

You forgot to unwrap url2
appendingPathComponent returns an optional value and you are trying to access it without unwrapping it.
So,
let url2 = url.appendingPathComponent("foo.txt")!
or
guard let url2 = url.appendingPathComponent("foo.txt") else { }
should fix it
EDIT
let path:String? = url2?.path
works also

You can also do this:
let url = Bundle.main.url(forResource: "foo", withExtension: "txt")!
let path:String = url.path

Related

Swift Optionals: When should you be expecting them

So I am teaching myself Swift and I get optionals when I am declaring them, so for example:
var thisString:String?
I would have to either force unwrap thisString, or use
if let anotherString = thisString {
or use
guard if let another string = thisString else { return }
or nil coalesce it
let anotherString = thisString ?? "Something else"
But where I am getting hung up is there are times I create something that I don't think it an optional but the compiler does.
For example, why is the URL an optional here?
let myURL = URL(string: "https://itunes.apple.com/search?term=U2")
var myRequest = URLRequest(url: myURL!)
I didn't declare it as an optional and it clearly has a value. So why does the compiler see this as an optional? If I didn't force unwrap it I would get this error:
Value of optional type 'URL?' not unwrapped; did you mean to use '!' or '?'?
Is there some standard that I am missing? I've gone over both the docs and Swift Programming Book from Big Nerd and I still don't seem to get this part.
Thanks for the assist.
But where I am getting hung up is there are times I create something that I don't think it an optional but the compiler does.
But what you think is not what matters. What matters is what the API call you are making actually returns. You find that out by reading the docs.
So why does the compiler see this as an optional
Well, let's read the docs. You are calling URL(string:). That is the same as URL's init(string:). Look at the docs:
https://developer.apple.com/documentation/foundation/url/1779737-init
The declaration is
init?(string: String)
See the question mark? That means, "This call returns an Optional." So you should expect an Optional here.
The compiler can't determine if the url that you define in the string is valid or not.
Suppose instead of:
let myURL = URL(string: "https://itunes.apple.com/search?term=U2")
You miss typed the myURL definition as:
let myURL = URL(string: "https:/itunes.apple.com/search?term=U2")
The string contains a malformed URL, so the program would crash the moment you went to define myURL.
let myURL = URL(string: "https://itunes.apple.com/search?term=U2")
Here you are creating an url from string.That string might not be a valid string.All the strings are not valid url. So you are getting an optional because if that string can be turned in to a valid url then that url will be returned else nil will be returned. See the Apple documentation here.The initializer what you are using is a failable initializer itself.
init?(string: String)
#Keshav is correct, to get a better idea, hold the option button and click on the 'string' part of the init function for the URL class. You will see in the swift reference the init declaration is init?(string: String). This means that a optional is returned. Any function can return a optional, you can have func returnMyString(_ myString: String) -> String {} or func returnMyString(_ myString: String) -> String? {}. Both of those functions are pretty much the same, except the second one returns a optional.
URL has optional initializers. For example you can have
class A {
init?() {
return nil //You will get Optional<A>.none
}
}
A() === Optional<A>.none //true
which kind of implies that initialization failed. Such initializers wrap returned object into Optional. In Swift nil == Optional<Any>.none so you can speak of them interchangeably.
For example if you will attempt to construct a URL with something that is not an actual url, it will return nil.
let notAURL = URL(string: "{") //notAURL will be nil
On a side note: I believe optional initializers are a very poor design choice since they don't communicate anything about what went wrong inside the init. If init is fallible, it should throw. I don't understand why Swift designers allow optional initializers and I see why it births a lot of confusion.

Subscription error that cannot subscript value of type

I am converting my project from Swift 2 to Swift 3 and I am stuck with one weird error:
Cannot subscript a value of type 'inout [[String : AnyObject]]' (aka
'inout Array>')
What does the "inout" even mean in this error?
This is the code part:
var productsValue = [[String:AnyObject]]()
cell.snusProductImageView.kf.setImage(with: NSURL(string: productsValue[indexPath.row]["productUrl"] as! String)!)
What is causing this? I don't understand the error at all since it is so board. Can you guys help me?
I tried using guard and let but it doesn't work for some reason.
I understand that productsValue is a [String:AnyObject]. That means that["productUrl"] is an AnyObject and I can't assign an AnyObject where a [String:AnyObject] is expected. But why it worked in Swift 2?
I solved the error like this:
var productsValue = [[String:AnyObject]]()
if productsValue[indexPath.row]["productUrl"] != nil {
var productValue = self.productsValue[indexPath.row]["productUrl"] as! String
cell.snusProductImageView.kf.setImage(with: NSURL(string: productValue) as! Resource?)
}
I don't know if this fixes it and why does it silence the error but yeah.

WatchKit SMS with preset body

I found this question which should have helped me, but the solution there is not working for me, and I am not sure if something has changed or if the problem is with my code.
let messageBody = "hello"
let urlSafeBody = messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
print("URLSAFEBODY: \(urlSafeBody)")
WKExtension.sharedExtension().openSystemURL(NSURL(string: "sms:&body=\(urlSafeBody)")!)
When this code is executed, I get the message that optional urlSafeBody was force unwrapped while nil, leading to a crash. Why is urlSafeBody nil? I know that I'm force unwrapping it, but I don't understand why it is ever nil after being assigned explicitly.
It's not urlSafeBody that is nil. As you can see from your print statement, it contains an optional string:
URLSAFEBODY: Optional("hello")
That will actually turn out to be a problem for the chain in the next statement, since you haven't unwrapped that string before it is interpolated.
If you examined your NSURL string URL, you'd see it contained:
sms:&body=Optional("hello")
This is going to cause NSURL initialization to fail, because its string URL is malformed. The fatal error then happens because you force-unwrapped the nil result from NSURL(string:)
How to resolve this:
You want to conditionally unwrap any strings which might be nil. You can do this via if let or guard let optional binding:
let messageBody = "hello"
let urlSafeBody = messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:&body=\(urlSafeBody)") {
WKExtension.sharedExtension().openSystemURL(url)
}
Notice that the urlSafeBody was unwrapped before it was used in the string interpolation, and url was also unwrapped after initialization.
Since it's certain that the url is not nil, it can be safely passed to openSystemURL.
You should always strive to avoid force-unwrapping variables which may be nil, as that will certainly lead to a crash.

How to guard initialization of property that may fail

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.

NSUUID with UUIDString initializer

I am attempting to make a NSUUID with a specified value:
let uuidString = "000000-0000-0000-0000-000000000000"
let beaconUUID:NSUUID = NSUUID(UUIDString: uuidString)
The second line gives an error message:
Extra Argument 'UUIDString' in call
Looking at the documentation it seems that it should work. Any ideas?
I hace also added as String after uuidString, same issue.
The error message is misleading. init?(UUIDString string: String) is a
failable initializer" and returns an optional which has to be unwrapped. So
let beaconUUID:NSUUID = NSUUID(UUIDString: uuidString)!
or simply
let beaconUUID = NSUUID(UUIDString: uuidString)!
works. If there is any chance that the initialization fails then use optional binding:
if let beaconUUID = NSUUID(UUIDString: uuidString) {
// ...
} else {
// failed
}
For more information, see
"Failable Initializers"
in the Swift blog,
"Declarations" in "The Swift Programming Language".
NSUUID(UUIDString: uuidString)
return an optional so you have to declare your variable as optional as well.
let uuidString = "000000-0000-0000-0000-000000000000"
let beaconUUID:NSUUID? = NSUUID(UUIDString: uuidString)
Actually what is the "!" in swift? I know it is represent NOT meaning, but how come in this case it uses like this? Anyone can explain it to me, I'm new to Swift programming language.