iOS 12 SDK Универсальная функция возвращает Optional.some (nil) - PullRequest
0 голосов
/ 21 сентября 2018

Используя Xcode 10, но не мигрировал на Swift 4.2, поэтому мой проект все еще работает с Swift 4.1.

Предположим, у меня есть следующее расширение на Dictionary:

extension Dictionary where Key: ExpressibleByStringLiteral {
    func find<T>(key: Key) -> T? {
        return self[key] as? T
    }
}

Я использую эту функцию для доступа к значениям в хэш-карте безопасным способом типа, например:

 let dict: [String: Any] = ["foo": "bar"]
 let foo: String? = dict.find(key: "foo") // prints "bar"

Мои проблемные поверхности, когда я хотел бы получить тип Any, возвращенный из моей функции find,например:

 let bar: Any? = dict.find(key: "bar")

До Xcode 10, эта функция возвращала мне простой и понятный nil, если ключ не был найден в хэш-карте.

Однако, после Xcode 10 он возвращает Optional.some(nil).

Я понимаю, что любые типы могут быть инициализированы следующим образом:

let foo: Any = Optional<String>.none

Я думаю, в моембывает что-то похожее.У кого-нибудь есть идея, как ее обойти и все же вернуть nil из функции find?

1 Ответ

0 голосов
/ 22 сентября 2018

Это связано с преднамеренным изменением ( # 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] является запахом кода, убедительно указывая на то, чтоВместо этого следует использовать более сильный тип.Если ключи действительно не могут быть произвольными строками (а не фиксированным набором статически известных ключей), и значения могут действительно быть любого типа для определенного ключа - почти наверняка естьлучший тип, который вы можете использовать для моделирования ваших данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...