safe unwrap URL type - swift

Im forming a url to pass in next to request, in return my app crashes, because it unexpectedly finds nil, when debuggin -> i do have url, so my question, how can i avoid using force unwrap here ?
when i use guard or if let, what should i return instead ? URL ? it will be optional again.
Making
var url: URL? not good for me also.
public var url: URL {
var components = URLComponents()
components.scheme = scheme
components.host = ipAddress
components.path = endpoint
components.port = port
components.queryItems = urlQueryItems
return components.url!.removingPercentEncoding!
}

Never use an exclamation mark. It means "crash me". You can hardly be surprised when you do crash, because that is precisely what you said to do.
You have no useful value to return if components.url turns out to be nil, which can definitely happen (as you have discovered). Therefore, simply change your public var url: URL to return URL? instead. Then just return components.url. This puts the onus on the caller to check whether url is nil, and it can proceed to unwrap safely by using the usual if let url dance.

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.

Swift 3.0 down casting and best practices

My question here is mostly in trying to be a better programmer, not that my code doesn't work.
Is downcasting something considered good enough (as in 'best practices') when writing your code? I'll give an example below with URLSession. I understand Swift gives you the tools to do it with as! or as?, but something tells me we shouldn't be doing it (or there are better ways to do it). I just can't see them. For example, consider this code that retrieves a web page from a HTTP GET request:
guard let url = URL(string: apiEndpoint) else {
return
}
if let scheme = url.scheme {
if scheme != "http" || scheme != "https" {
return
}
}
// More code
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
// Error handling here
}
else {
guard let httpResponse = response as? HTTPURLResponse else {
return
}
// Code here
}
}.resume()
So, the above code works, but my question is on the guard let httpResponse = response as? HTTPURLResponse statement. dataTask is supposed to return an URLResponse object back to the delegate, in this case, my variable response. However, this class obviously doesn't have a status code. So if I do the following, Swift will give me a compile error:
guard let httpResponse = response else {
return nil
}
statusCode = response.statusCode
Because response is supposed to be an URLResponse object, so it fails Xcode's check.
In order to obtain that information I need to either force down casting to HTTPURLResponse with as! (I've checked that the request is http/https earlier) or check with an optional as I did above.
Now, I don't want to use big wrappers like Alamofire because my API is very very simple. I want to write my own wrapper around URLResponse and return an object that other parts of my code can use. I don't know how the AF guys solve this problem. What would be the Swift way of dealing with this? Is down casting fine as a "best practice"? Is there a better way of getting the HTTPURLResponse object?
First of all, strictly spokenĀ a block based API doesn't have a delegate.
URLSessionDataTask is a quite versatile class and can be used for other purposes than an HTTP request hence the response object is the more generic URLResponse class and it is optional.
In case of an HTTP request the API returns the more specific subclass HTTPURLResponse so the object must be casted down to get access to the specific properties of HTTPURLResponse like statusCode. So yes, down casting is fine as a "best practice".
Here are two suggestions to be a better programmer
Variable names are supposed to start with a lowercase letter e.g. urlString
As already mentioned in a comment use more descriptive variable names than a single character.
There is another serious error in the code: The completion handler has no return value so you will get a compiler error Unexpected non-void return value in void function.
Side-note: The check for the scheme and for nil is not needed since the literal string https://www.google.com clearly contains the scheme and is a valid URL.

Swift Impossible Type Inference

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

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.