3

I created some test code to show the problem I am having.

This compiles fine in a playground, however, when I try to put it into a project, Xcode gives the following warning: Treating a forced downcast to 'String' as optional will never produce 'nil' on line 30. I am given two suggestions to fix the problem:

  1. Use 'as?' to perform a conditional downcast to 'String', which makes absolutely no sense. However, it does compile without warnings/errors, which seems strange because it is assigning an optional value to a non-optional type of String.

    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.

    From the Swift language guide.

    Unless it thinks I might want to assign nil if the conversion fails (therefore removing the dictionary entry), this makes no sense. Especially because I am sure it will succeed because I literally just checked to see if it was a String.

  2. Add parentheses around the cast to silence this warning, which seems pointless, but does silence the warning. This seems like a strange thing to do, but then again, it may just be a poor way of confirming that you really want to do what you are trying to do.


Which option is right, or neither? What is causing this warning?

Coder-256
  • 5,212
  • 2
  • 23
  • 51
  • There are a lot of issues with this code. First of all you need to explicitly cast the return types in your functions so they return an `AnyObject?`. Second, you should avoid explicit unwrapping of `Optional`, using `!`, as much as possible. –  Jun 05 '16 at 15:46
  • @originaluser2 Good point, I updated my code. – Coder-256 Jun 05 '16 at 15:54
  • @ColGraff, the point is that the functions may return values of different types (these are just examples). Also, why should I avoid explicit unwrapping of optionals (if I already tested if they were nil)? – Coder-256 Jun 05 '16 at 15:54
  • There are better, less error-prone ways of testing and unwrapping such as `if let unwrapped = wrapped {}` –  Jun 05 '16 at 15:55
  • @ColGraff [That's not true](http://stackoverflow.com/a/29324173/3398839). – Coder-256 Jun 05 '16 at 15:59
  • Yes, they produce the same byte code but the real reason to avoid it is problems down the line. First of all, using optional biding keeps both the test and the unwrapping in one line. Later edits are less likely to change this structure in an error-producing manner. Second, explicitly unwrapping is a run-time error, an edit to the code could make it so that the check no longer covers the unwrapping and results in a crash when the application is being used rather than when you are developing it. Optional binding avoids a run-time crash because the test and the unwrapping are a single operation. –  Jun 05 '16 at 16:14

2 Answers2

6

The correct solution is to use optional binding instead of the forced unwrap operator !. Actually you can incorporate the check value != nil into the switch statement:

for (key, value) in dict {
    switch value {
    case let s as String:
        newDict[key] = s
    case let i as Int:
        newDict[key] = String(i)
    case let b as Bool:
        newDict[key] = b ? "1" : "0"
    case let v?:   // value is not `nil`
        newDict[key] = String(v)
    default:       // value is `nil`
        break
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I was aware of that method, but it seems to work the same way as mine. However, it does fix the problem! – Coder-256 Jun 05 '16 at 15:57
  • @Coder256: The warning really seems caused by the fact that the type of the LHS is an optional. But note that the compiler might not "see" that you already checked the type of the variable. Optional binding avoids these problems. – Martin R Jun 05 '16 at 16:21
0

Here is your code, modified to show how you can cast a function result to match AnyObject? and avoid explicitly unwrapped optionals:

func returnsAString() -> AnyObject? {
  return "I am a String." as? AnyObject
}

func returnsAnInt() -> AnyObject? {
  return Int(123) as? AnyObject
}

func returnsABool() -> AnyObject? {
  return true as? AnyObject
}

func returnsNilBool() -> AnyObject? {
  return nil as Bool? as? AnyObject
}

var dict    : [String : AnyObject?] = [String : AnyObject?]()
var newDict : [String : String    ] = [String : String    ]()

dict["string"] = returnsAString()
dict["int"] = returnsAnInt()
dict["bool"] = returnsABool()
dict["nil"] = returnsNilBool()

for (key, value) in dict {
    switch value {
    case let value as String:
      newDict[key] = value
    case let value as Int:
      newDict[key] = String(value)
    case let value as Bool:
      newDict[key] = (value ? "1" : "0")
    default:
      newDict[key] = "nil"
    }
}

print("Dict: \(dict)")
print("newDict: \(newDict)")

// Dict: ["nil": nil, "int": Optional(123), "bool": Optional(1), "string": Optional(I am a String.)]
// newDict: ["nil": "nil", "int": "123", "bool": "1", "string": "I am a String."]