Это связано с преднамеренным изменением ( # 13910 ), когда компилятор теперь более консервативен с развертыванием необязательного значения, которое приводится к универсальному типу заполнителя.Теперь результаты, которые вы получаете, более соответствуют тем, которые вы получили бы в неуниверсальном контексте (см. SR-8704 для дальнейшего обсуждения).
Например:
// note the constraint `where Key : ExpressibleByStringLiteral` is needlessly restrictive.
extension Dictionary where Key : ExpressibleByStringLiteral {
func find<T>(key: Key) -> T? {
return self[key] as? T
}
}
let dict: [String: Any] = ["foo": "bar"]
let genericBar: Any? = dict.find(key: "bar")
print(genericBar as Any) // in Swift 4.1: nil, in Swift 4.2: Optional(nil)
// `T` in the above example is inferred to be `Any`.
// Let's therefore substitute `as? T` with `as? Any`.
let nonGenericBar = dict["bar"] as? Any
print(nonGenericBar as Any) // in both versions: Optional(nil)
Как видите, теперь вы получаете Optional(nil)
независимо от того, использовался ли общий заполнитель для выполнения приведения, что делает поведение более согласованным.
Причина , почему вы в итоге получаете Optional(nil)
, заключается в том, что вы выполняете условное приведение необязательного значения.Условное приведение само по себе приводит к необязательному значению, указывающему на успех или неудачу, а помещение полученного необязательного значения в регистр успеха дает вам необязательное двойное завершение.И поскольку вы приводите к Any
, который может представлять значение Optional
, не нужно выполнять разворачивание, чтобы «подогнать» значение в результирующем типе.
Если вы хотите сгладитьрезультирующий необязательный параметр в одиночно обернутый необязательный параметр можно объединить nil
:
extension Dictionary {
func find<T>(key: Key) -> T? {
return (self[key] as? T?) ?? nil
}
}
или развернуть значение перед преобразованием, например, с помощью guard
:
extension Dictionary {
func find<T>(key: Key) -> T? {
guard let value = self[key] else { return nil }
return value as? T
}
}
или, мой предпочтительный подход, используя flatMap(_:)
:
extension Dictionary {
func find<T>(key: Key) -> T? {
return self[key].flatMap { $0 as? T }
}
}
Несмотря на это, я часто считаю, что использование [String: Any]
является запахом кода, убедительно указывая на то, чтоВместо этого следует использовать более сильный тип.Если ключи действительно не могут быть произвольными строками (а не фиксированным набором статически известных ключей), и значения могут действительно быть любого типа для определенного ключа - почти наверняка естьлучший тип, который вы можете использовать для моделирования ваших данных.