WatchKit SMS with preset body - swift

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.

Related

safe unwrap URL type

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.

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 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

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.

Chaining Optionals in Swift

Up until now, I've been unwrapping Optionals in Swift 2.1 like so:
#IBOutlet var commentTextView: UITextView!
if let comment = user["comment"] as? String {
commentTextView.text = comment
}
I never really thought about it, but I think the reason I was doing this was because I was worried that this statement would throw an error if user["comment"] returned something other than a String:
commentTextView.text = user["comment"] as? String
If user["comment"] isn't a String, will the variable on the left of the assignment operator be assigned and throw an error or will the assignment be skipped?
I guess user is in fact a dictionary [String: Any] and what you really do with if let comment = user["comment"] as? String { ... } is not just unwrapping the optional but a conditional type casting (and then unwrapping an optional result of it):
Use the conditional form of the type cast operator (as?) when you are not sure if the downcast will succeed. This form of the operator will always return an optional value, and the value will be nil if the downcast was not possible. This enables you to check for a successful downcast.
Now, to answer your question, if user["comment"] isn't a String then the result will be that commentTextView.text will be assigned nil value, which is bad because its type is String! (implicitly unwrapped optional) about which we hold a promise that it will never be nil. So, yes, there will be an error, an exception actually, but not at the place you would like it to be but at the moment your application will try to access its value assuming that it's not going to be nil.
What you should really do depends on a particular case.
E.g. if you can make user to be a dictionary like [String: String], then you would be able to truly get to unwrapping the optionals and use something like if let comment = user["comment"] { ... }. Or, if you are totally sure that the value for "comment" key will always be there, then you could just do let comment = user["comment"]!.
But if that's not possible then you have to stick with down-casting and the only other thing you can do is to use forced form of it, that is commentTextView.text = user["comment"] as! String. This one at least will produce an exception right at the spot in case if the value at "comment" happens to be not a String but something else.
nil will be assigned to the variable.
If the type of the variable is a non-optional, you'll get a runtime error.
However if user["comment"] is a String you'll get a compiler error about missing ! or ?.
First we need to know of what type the dictionary "user" is.
I assume it is of an unknown type like [String: AnyObject], otherwise why would you try to unwrap it as an String. Let us write a short test to see what happens:
let dict: [String: AnyObject] = ["SomeKey" : 1]
if let y = dict["SomeKey"] as? String {
print(y)
}
You can see clearly that the value of "SomeKey" is an Integer. Trying to unwrap it as an String triggers no error, the "if" statement is just skipped. If an assignment actually happened is hard to prove (maybe by looking at the assembler code) because the variable "y" simply does not exist after the if statement. I assume it will not be created at all.
If the type of the dictionary is known as [String: String] you can omit the try to unwrap it as a String because it's always clear that the type is String.
let dict2: [String: String] = ["SomeKey" : "SomeValue"]
if let y = dict2["WrongKey"] {
// In this case print(y) will not be called because the subscript operator of the dictionary returns nil
print(y)
}
// In this case print(y) will be called because the key is correct
if let y = dict2["SomeKey"] {
print(y)
}