What's wrong with my code that tries to check the type of a variable? [duplicate] - swift

This question already has answers here:
How to determine the type of a variable in Swift
(5 answers)
Closed 8 years ago.
What's wrong with my code that tries to check the type of a variable?
The following code produces error that says "'is' test is always true". Note that I don't want to set p to a value because it could be nil, hence the use of optional.
import Foundation
var p:String?
if p as String? {
println("p is a string type")
}
else {
println("p is not a string type")
}
Now if I test against String type, it won't even compile:
import Foundation
var p:String?
if p as String {
println("p is a string type")
}
else {
println("p is not a string type")
}
Is this a compiler bug? If not what did I do wrong?

Adding on to the answers that revolve around optional binding, there is a more direct way that Apple provides.
The is operator exists exactly for this purpose. However, it doesn't allow you to test trivial cases but rather for subclasses.
You can use is like this:
let a : Any = "string"
if a is String {
println("yes")
}
else {
println("no")
}
As expected, that prints yes.

You already know that p is an optional string. You don't need to convert it to a type, you can simply do Optional Binding:
if let aString = p {
println("p is a string: \(aString)")
}
else {
println("p is nil")
}
Normally you check if a variable is of a certain type using the as? operator:
var something : AnyObject = "Hello"
if let aString = something as? String {
println("something is a string: \(aString)")
}
but you do not use that mechanism when checking if an optional is nil.
This will also work if your object is an optional:
var something : AnyObject? = "Hello"
if let aString = something as? String {
println("something is a string: \(aString)")
}

Since p is an optional String, you can use it in your conditional test like so:
import Foundation
var p: String?
if p {
println("p has been assigned a String value")
}
else {
println("p is nil")
}
If p has been assigned a String value and you would like to use that value in the body of your if statement, then you can use 'optional binding' to assign its value to a local constant you can work with:
if let pValue = p {
println("p is assigned \(pValue)")
}
else {
println("p is nil")
}

Related

Forwarding function generic parameter to generic class type

I have created enum with associated value and I want to be able to dynamically update associated value. As far as I know Swift doesn't support that at the moment.
Because of that I used following approach:
enum PersonInfo {
class EnumValue<T> {
var value: T
init(_ value: T) {
self.value = value
}
}
// Instead of using String or Bool or any other type directly, use EnumValue wrapper
case firstName(EnumValue<String>)
case lastName(EnumValue<String>)
case isAdult(EnumValue<Bool>)
}
I want to add function that would update EnumValue.value property in following way:
func updateAssociatedValue<V>(_ updateValue: V) {
let mirror = Mirror(reflecting: self)
for associatedValue in mirror.children {
guard let value = associatedValue.value as? EnumValue<V> else {
continue
}
value.value = updateValue
}
}
Problem is that this guard statement always fails (guard let value = associatedValue.value as? EnumValue<V>) and I can't figure it out why.
On the other hand, when I write updateAssociatedValue with typed type then things work properly:
// This works
func updateAssociatedValue(_ updateValue: String) {
let mirror = Mirror(reflecting: self)
for associatedValue in mirror.children {
guard let value = associatedValue.value as? EnumValue<String> else {
continue
}
value.value = updateValue
}
}
Things compile normally but during the runtime guard statement always fails. Am I using generic value in some incorrect way? Should I use somehow updateValue.Type or updateValue.self (I tried but it didn't work).
Example of usage:
var array: [PersonInfo] = [
.firstName(PersonInfo.EnumValue("John")),
.lastName(PersonInfo.EnumValue("Doe")),
.isAdult(PersonInfo.EnumValue(false))
]
print(array)
// John, Doe, false
array.first?.updateAssociatedValue("Mike")
print(array)
// Mike, Doe, false
I can always reassign enum value in array but if possible I want to avoid that. That's the reason for asking this question.

How to get the First Character in a name

I have below func in my class.
static func getFirstCharInName(strName: String) -> String {
let firstCharInName = String(strName.first)
return firstCharInName.trim()
}
I encountered this err:
Value of optional type 'Character?' must be unwrapped to a value of type 'Character'
What seems to be the problem?
Thanks
func getFirstCharInName(strName: String) -> String {
let indexStartOfText = strName.index(strName.startIndex, offsetBy: 0)
let indexEndOfText = strName.index(strName.startIndex, offsetBy: 0)
let firstChar = String(strName[indexStartOfText...indexEndOfText])
return firstChar
}
This error means that the expression has optional value (the value can be nil) that is not yet unwrapped, strName.first returns an optional value of Character?, but your function demands a returning type of String which is not an optional type.
So, in order to fix this, you need to unwrap the optional value strName.first, it seems like you are not familiar with optionals, here's the code for your case (choose one from two options):
func getFirstCharInName(strName: String) -> String {
// option 1: force unwrap - can cause fatal error
return String(strName.first!)
// option 2: optional binding
if let firstCharInName = strName.first {
return String(firstCharInName)
} else {
// if the optional value is nil, return an empty string
return ""
}
}
PS. I don't really understand the function trim() in your question, but if you mean to strip away the blank spaces like " ", you can do:
firstCharInName.trimmingCharacters(in: .whitespaces)
Avoid the optional simply with prefix, it's totally safe. if there is no first character you'll get an empty string.
static func getFirstChar(in name: String) -> String { // the function name getFirstChar(in name is swiftier
return String(name.prefix(1))
}
I don't know what the trim function is supposed to do.
It means that value of optional type 'Character?' (as result of your part of code strName.first) must be unwrapped to a value of type 'Character' before you will be gonna cast it to String type.
You may use this variant:
func getFirstCharInName(strName: String) -> String {
return strName.count != 0 ? String(strName.first!) : ""
}
As you can see, the exclamation point is in the string strName.first! retrieves the optional variable as it was needed.
you can do something like that:
extension String {
var firstLetter: String {
guard !self.isEmpty else { return "" }
return String(self[self.startIndex...self.startIndex])
}
}
then
let name = "MilkBottle"
let first = name.firstLetter // "M"

know the Datatype in Swift

i am new to swift i just started with the basics. In one of the Blog i saw a simple task which goes like this read a line from the stdin and check whether it is a integer,float,String.
I tried with the following code
let input = readLine()
var result = test(input)
print (result)
func test (obj:Any) -> String {
if obj is Int { return "This input is of type Intger." }
else if obj is String { return "This input is of type String." }
else { return "This input is something else. " }
}
when the input of 3245 is given it stores in the string format. and returns output as string.
how to overcome it..?
The readLine function returns a value of type String?. So your input variable can only be a String. It will never be Int or anything else.
If you want to see if the entered value is a valid number, you can try to convert the string to an Int.
if let input = readLine() {
if let num = Int(input) {
// the user entered a valid integer
} else {
// the user entered something other than an integer
}
}
As others have pointed out, readline() always returns a String?. It's up to you to parse that into whatever format you use it.
This is how I would do this:
let line = readLine()
switch line {
case let s? where Int(s) != nil:
print("This input is of type Intger.")
case let s? where Float(s) != nil:
print("This input is of type Float.")
case let s? where s.hasPrefix("\"") && s.hasSuffix("\""):
print("This input is of type String.")
default: print("This input is something else. ")
}
It exploits the ability of Int and Float's initializers to test the validity of a String, which almost entirely defeats the purpose of this exercise. But hey, it works, right? 😄
You can find of the type of object as
if let intt = obj as? Int {
// obj is a String. Do something with intt
}
else if let str = obj as? String {
// obj is a String. Do something with str
}
else {
//obj is something else
}

Swift Optionals - Variable binding in a condition requires an initializer

I am new to Swift and trying to figure out the Optional concept. I have a small piece of code in Playground which is giving me "Variable binding in a condition requires an initializer" error. Can someone please explain why and how do I fix it?
I only want to print "Yes" or "No" depending on if "score1" has a value or not. Here is the code:
import Cocoa
class Person {
var score1: Int? = 9
func sum() {
if let score1 {
print("yes")
} else {
print("No")
}
}//end sum
}// end person
var objperson = person()
objperson.sum()
The if let statement takes an optional variable. If it is nil, the else block or nothing is executed. If it has a value, the value is assigned to a different variable as a non-optional type.
So, the following code would output the value of score1 or "No" if there is none:
if let score1Unwrapped = score1
{
print(score1Unwrapped)
}
else
{
print("No")
}
A shorter version of the same would be:
print(score1 ?? "No")
In your case, where you don't actually use the value stored in the optional variable, you can also check if the value is nil:
if score1 != nil {
...
}
Writing
if let score1 {
doesn't make sense. If you want to see if score has a value, use
if score1 != nil {
or
if let score = score1 {
The last case binds a new non-optional constant score to score1. This lets you use score inside the if statement.
The code in your question is similar to something I saw in the swift book and documentation and they are correct.
Your playground is just using an old version of swift which currently doesn't support this syntax. Using a beta version of XCode should fix
https://www.reddit.com/r/swift/comments/vy7jhx/unwrapping_optionals/
the problem is that if let assumes you want to create a constant score1 with some value. If you just want to check if it contains a value, as in not nil you should just do it like below:
if score1! != nil {
// println("yes")
So your full code would look like this:
class Person {
var score1: Int? = 9
func sum() {
if score1 != nil {
println("yes")
}
else {
println("no")
}
}
}
var objperson = Person()
objperson.sum()
You can unwrap it using this:
import Cocoa
class Person {
var score1: Int? = 9
func sum() {
print("\(score1 != nil ? "YES" : "NO")")
}
}
And then call it like:
var objperson = Person()
objperson.sum()

unwrapping multiple optionals in if statement

I want to unwrap two optionals in one if statement, but the compiler complaints about an expected expression after operator at the password constant.
What could be the reason?
if let email = self.emailField?.text && let password = self.passwordField?.text
{
//do smthg
}
Done in Swift.
Great news. Unwrapping multiple optionals in a single line is now supported in Swift 1.2 (XCode 6.3 beta, released 2/9/15).
No more tuple/switch pattern matching needed. It's actually very close to your original suggested syntax (thanks for listening, Apple!)
if let email = emailField?.text, password = passwordField?.text {
}
Another nice thing is you can also add where for a "guarding condition":
var email: String? = "baz#bar.com"
var name: String? = "foo"
if let n = name, e = email where contains(e, "#") {
println("name and email exist, email has #")
}
Reference: XCode 6.3 Beta Release Notes
Update for Swift 3:
if let email = emailField?.text, let password = passwordField?.text {
}
each variable must now be preceded by a let keyword
How about wrapping the optionals in a tuple and using switch to pattern match?
switch (self.emailField?.text, self.passwordField?.text) {
case let (.Some(email), .Some(password)):
// unwrapped 'email' and 'password' strings available here
default:
break
}
It's definitely a bit noisier, but at least it could also be combined with a where clause as well.
The usage
if let x = y {
}
is not equivalent to
if (let x = y) { // this is actually not allowed
}
"if let" is effectively a two-word keyword, which is equivalent to
if y != nil {
let x = y!
// rest of if let block
}
Before Swift 1.2
Like #James, I've also created an unwrap function, but this one uses the existing if let for control flow, instead of using a closure:
func unwrap<T1, T2>(optional1: T1?, optional2: T2?) -> (T1, T2)? {
switch (optional1, optional2) {
case let (.Some(value1), .Some(value2)):
return (value1, value2)
default:
return nil
}
}
This can be used like so:
if let (email, password) = unwrap(self.emailField?.text, self.passwordField?.text)
{
// do something
}
From: https://gist.github.com/tomlokhorst/f9a826bf24d16cb5f6a3
Note that if you want to handle more cases (like when one of the two fields is nil), you're better off with a switch statement.
Swift 4
if let suggestions = suggestions, let suggestions1 = suggestions1 {
XCTAssert((suggestions.count > suggestions1.count), "TEST CASE FAILED: suggestion is nil. delete sucessful");
}
I can't explain why the above code doesn't work, but this would be good a replacement:
if let email = self.emailField?.text
{
if let password = self.passwordField?.text
{
//do smthg
}
}
Based on #Joel's answer, I've created a helper method.
func unwrap<T, U>(a:T?, b:U?, handler:((T, U) -> ())?) -> Bool {
switch (a, b) {
case let (.Some(a), .Some(b)):
if handler != nil {
handler!(a, b)
}
return true
default:
return false
}
}
// Usage
unwrap(a, b) {
println("\($0), \($1)")
}