This sounds like a programming-newbie-question but how is this situation possible?
for typelessResultDict in results {
let resultDict = typelessResultDict as! Dictionary<String, Any>
let internalId = resultDict["internalId"] as? String?
let orderDate = resultDict["orderDate"] as? Date?
if internalId == nil || orderDate == nil {
#if DEBUG
fatalError("internalId/orderDate was nil")
#endif
continue
}
do {
try removeAllButOne(internalId: internalId!!, orderDate: orderDate!!)
} catch {
LOGGER.error("Could not deduplicate.")
}
}
In a for
loop, I am explicitly checking if
the variable orderDate
is nil
and either raise a fatalError
in DEBUG or at least continue
the loop, effectively canceling all other statements.
However, you can see that my code is executed and raises a fatal error because of force-unwrapping nil
a few lines later.
Is this a bug or do I need to revisit first semester computer science?
CodePudding user response:
Note that the types of internalId
and orderDate
are double optionals - String??
and Date??
respectively. This happens because resultDict["internalId"]
produces a Any?
(the key could be absent in the dictionary), and the safe cast to String?
produces a String??
, as there are 3 cases:
- the key is present in the dictionary, and the cast succeeds (both layers of optional would be non-nil)
- the key is absent in the dictionary (the outer optional would be non-nil, wrapping a
nil
as the inner optional) - the cast fails (the outer optional is nil)
== nil
only checks if the outermost layer of the optional is nil, but you are unwrapping both layers with two exclamation marks. The first exclamation mark is safe as ensured by your if statement, but the second is not.
You can avoid all of this if you just cast to String
and Date
instead:
let internalId = resultDict["internalId"] as? String
let orderDate = resultDict["orderDate"] as? Date
You can also use a guard
statement to eliminate the forced unwrapping:
guard let internalId = resultDict["internalId"] as? String,
let orderDate = resultDict["orderDate"] as? Date else {
#if DEBUG
fatalError("internalId/orderDate was nil")
#endif
continue
}
do {
try removeAllButOne(internalId: internalId, orderDate: orderDate)
} catch {
LOGGER.error("Could not deduplicate.")
}
CodePudding user response:
The problem is using 'as? String?' and 'as? Date?' that means your internalId and orderDate object types are :
String?? and Date??
They are like a box that eventually can contain an optional, that is another box. So when you check :
if internalId == nil || orderDate == nil
The answer is always false, because they really contains an optional object. Regardless of whether the optional si full or empty.
So instead of 'as? String?' you can use :
- as? String
Now your code will work.
I suggest you this syntax that is more clear :
let resultDict : [String : Any] = [:]
let internalDict = resultDict["InternalDict"] as? String
let orderDate = resultDict["OrderDate"] as? Date
if let internalDict = internalDict, let orderDate = orderDate
{
//They are not Nil
print(internalDict)
print(orderDate)
}
else
{
//One of them is Nil
fatalError("internalDict/orderDate was nil")
}
P.S. Please next time post text and not image.